langwatch 1.9.0 → 1.11.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.
Files changed (36) hide show
  1. package/dist/agent/index.d.mts +301 -0
  2. package/dist/agent/index.d.ts +301 -0
  3. package/dist/agent/index.js +1468 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/agent/index.mjs +1424 -0
  6. package/dist/agent/index.mjs.map +1 -0
  7. package/dist/{chunk-NFFB3DWW.js → chunk-2PD3YA5H.js} +17 -6
  8. package/dist/chunk-2PD3YA5H.js.map +1 -0
  9. package/dist/{chunk-HUMHSRVP.js → chunk-AJDFFRSW.js} +12 -12
  10. package/dist/{chunk-HUMHSRVP.js.map → chunk-AJDFFRSW.js.map} +1 -1
  11. package/dist/{chunk-4XQHESU7.mjs → chunk-BS5CNZ5S.mjs} +17 -6
  12. package/dist/chunk-BS5CNZ5S.mjs.map +1 -0
  13. package/dist/{chunk-A52LX4C4.mjs → chunk-VMNKKJVI.mjs} +2 -2
  14. package/dist/cli/bundle.js +722 -379
  15. package/dist/{implementation-BvwOB77X.d.ts → implementation-D58yPTJW.d.ts} +1 -1
  16. package/dist/{implementation-C1F0eCHp.d.mts → implementation-aczLxVpV.d.mts} +1 -1
  17. package/dist/index.d.mts +166 -3
  18. package/dist/index.d.ts +166 -3
  19. package/dist/index.js +292 -60
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +261 -29
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/observability-sdk/index.d.mts +3 -3
  24. package/dist/observability-sdk/index.d.ts +3 -3
  25. package/dist/observability-sdk/index.js +2 -2
  26. package/dist/observability-sdk/index.mjs +1 -1
  27. package/dist/observability-sdk/instrumentation/langchain/index.d.mts +1 -1
  28. package/dist/observability-sdk/instrumentation/langchain/index.d.ts +1 -1
  29. package/dist/observability-sdk/setup/node/index.js +3 -3
  30. package/dist/observability-sdk/setup/node/index.mjs +2 -2
  31. package/dist/{types-C3NpCWi_.d.mts → types-BmMuUDrO.d.mts} +3858 -657
  32. package/dist/{types-jx13nj4M.d.ts → types-D_YNU-VL.d.ts} +3858 -657
  33. package/package.json +8 -1
  34. package/dist/chunk-4XQHESU7.mjs.map +0 -1
  35. package/dist/chunk-NFFB3DWW.js.map +0 -1
  36. /package/dist/{chunk-A52LX4C4.mjs.map → chunk-VMNKKJVI.mjs.map} +0 -0
@@ -0,0 +1,1468 @@
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/agent/index.ts
31
+ var agent_exports = {};
32
+ __export(agent_exports, {
33
+ AGENT_TRANSPORTS: () => AGENT_TRANSPORTS,
34
+ AgentParameterError: () => AgentParameterError,
35
+ DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
36
+ MAX_TIMEOUT_MS: () => MAX_TIMEOUT_MS,
37
+ PROTOCOL_VERSION: () => PROTOCOL_VERSION,
38
+ connectAgent: () => connectAgent,
39
+ normalizeReply: () => normalizeReply,
40
+ parameterSpecsFromSchema: () => parameterSpecsFromSchema,
41
+ resolveConnectUrl: () => resolveConnectUrl,
42
+ resolveEnvironment: () => resolveEnvironment,
43
+ resolveHttpConnectUrl: () => resolveHttpConnectUrl,
44
+ resolveTransport: () => resolveTransport,
45
+ sanitizeEnvironment: () => sanitizeEnvironment,
46
+ toParameterSchema: () => toParameterSchema
47
+ });
48
+ module.exports = __toCommonJS(agent_exports);
49
+
50
+ // src/logger/index.ts
51
+ var logLevelOrder = {
52
+ debug: 0,
53
+ info: 1,
54
+ warn: 2,
55
+ error: 3
56
+ };
57
+ var ConsoleLogger = class {
58
+ constructor(options = { level: "warn" }) {
59
+ this.debug = (message, ...args) => {
60
+ if (this.shouldLog("debug")) console.debug(this.format(message), ...args);
61
+ };
62
+ this.info = (message, ...args) => {
63
+ if (this.shouldLog("info")) console.info(this.format(message), ...args);
64
+ };
65
+ this.warn = (message, ...args) => {
66
+ if (this.shouldLog("warn")) console.warn(this.format(message), ...args);
67
+ };
68
+ this.error = (message, ...args) => {
69
+ if (this.shouldLog("error")) console.error(this.format(message), ...args);
70
+ };
71
+ this.level = options.level;
72
+ this.prefix = options.prefix;
73
+ }
74
+ shouldLog(level) {
75
+ return logLevelOrder[level] >= logLevelOrder[this.level];
76
+ }
77
+ format(message) {
78
+ return this.prefix ? `[${this.prefix}] ${message}` : message;
79
+ }
80
+ };
81
+
82
+ // src/agent/client.ts
83
+ var import_api = require("@opentelemetry/api");
84
+
85
+ // src/agent/identity.ts
86
+ var os = __toESM(require("os"));
87
+ var import_node_crypto = require("crypto");
88
+
89
+ // package.json
90
+ var version = "1.11.0";
91
+
92
+ // src/internal/constants.ts
93
+ var LANGWATCH_SDK_VERSION = version;
94
+ var DEFAULT_ENDPOINT = "https://app.langwatch.ai";
95
+
96
+ // src/internal/endpoint.ts
97
+ var isSet = (value) => typeof value === "string" && value.trim() !== "";
98
+ var normalizeEndpoint = (endpoint) => {
99
+ const trimmed = endpoint.trim();
100
+ let end = trimmed.length;
101
+ while (end > 0 && trimmed[end - 1] === "/") end--;
102
+ return trimmed.slice(0, end);
103
+ };
104
+ var resolveEndpoint = (endpoint) => {
105
+ for (const candidate of [endpoint, process.env.LANGWATCH_ENDPOINT]) {
106
+ if (!isSet(candidate)) continue;
107
+ const normalized = normalizeEndpoint(candidate);
108
+ if (normalized !== "") return normalized;
109
+ }
110
+ return normalizeEndpoint(DEFAULT_ENDPOINT);
111
+ };
112
+
113
+ // src/agent/identity.ts
114
+ var DEFAULT_ENVIRONMENT = "development";
115
+ var ENVIRONMENT_MAX_LENGTH = 32;
116
+ var ENVIRONMENT_VARIABLES = [
117
+ "LANGWATCH_AGENT_ENVIRONMENT",
118
+ "APP_ENV",
119
+ "ENVIRONMENT",
120
+ "NODE_ENV"
121
+ ];
122
+ var isSet2 = (value) => typeof value === "string" && value.trim() !== "";
123
+ function sanitizeEnvironment(name) {
124
+ const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, ENVIRONMENT_MAX_LENGTH).replace(/-+$/g, "");
125
+ return cleaned === "" ? DEFAULT_ENVIRONMENT : cleaned;
126
+ }
127
+ function resolveEnvironment({
128
+ explicit,
129
+ env = process.env
130
+ }) {
131
+ if (isSet2(explicit)) return sanitizeEnvironment(explicit);
132
+ for (const name of ENVIRONMENT_VARIABLES) {
133
+ const value = env[name];
134
+ if (isSet2(value)) return sanitizeEnvironment(value);
135
+ }
136
+ return DEFAULT_ENVIRONMENT;
137
+ }
138
+ var isTruthy = (value) => {
139
+ if (!isSet2(value)) return false;
140
+ const lowered = value.trim().toLowerCase();
141
+ return lowered !== "0" && lowered !== "false" && lowered !== "no" && lowered !== "off";
142
+ };
143
+ function resolveEnabled({
144
+ explicit,
145
+ env = process.env
146
+ }) {
147
+ const flag = env.LANGWATCH_AGENT_CONNECT;
148
+ if (isSet2(flag) && !isTruthy(flag)) return false;
149
+ if (explicit !== void 0) return explicit;
150
+ return !isTruthy(env.CI);
151
+ }
152
+ function resolveInstanceLabel({
153
+ explicit,
154
+ env = process.env
155
+ }) {
156
+ if (isSet2(explicit)) return explicit.trim();
157
+ const fromEnv = env.LANGWATCH_AGENT_INSTANCE_LABEL;
158
+ return isSet2(fromEnv) ? fromEnv.trim() : void 0;
159
+ }
160
+ var HOST_LABEL_MAX_LENGTH = 24;
161
+ function hostLabel(hostname) {
162
+ return hostname.toLowerCase().replace(/\.(local|lan|home|localdomain)$/i, "").replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, HOST_LABEL_MAX_LENGTH);
163
+ }
164
+ var readHostname = (machine) => {
165
+ try {
166
+ return hostLabel(machine.hostname());
167
+ } catch {
168
+ return "";
169
+ }
170
+ };
171
+ var readUsername = (machine) => {
172
+ try {
173
+ return machine.userInfo().username;
174
+ } catch {
175
+ return "";
176
+ }
177
+ };
178
+ function buildInstance({
179
+ label,
180
+ machine = os
181
+ }) {
182
+ return {
183
+ id: `inst_${(0, import_node_crypto.randomUUID)().replace(/-/g, "")}`,
184
+ hostname: readHostname(machine),
185
+ username: readUsername(machine),
186
+ pid: process.pid,
187
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
188
+ ...label ? { label } : {},
189
+ inFlightCallIds: []
190
+ };
191
+ }
192
+ var SDK_IDENTITY = {
193
+ name: "langwatch-typescript",
194
+ version: LANGWATCH_SDK_VERSION,
195
+ language: "typescript"
196
+ };
197
+ var USER_AGENT = `langwatch-typescript/${LANGWATCH_SDK_VERSION}`;
198
+ var CONNECT_PATH = "/api/v1/agents/connect";
199
+ function resolveConnectUrl(endpoint) {
200
+ const base = resolveEndpoint(endpoint);
201
+ const socketBase = base.replace(
202
+ /^http(s?):\/\//i,
203
+ (_match, secure) => secure ? "wss://" : "ws://"
204
+ );
205
+ return `${socketBase}${CONNECT_PATH}`;
206
+ }
207
+ function resolveHttpConnectUrl(endpoint) {
208
+ return `${resolveEndpoint(endpoint)}${CONNECT_PATH}`;
209
+ }
210
+ function buildConnectHeaders({
211
+ apiKey,
212
+ projectId
213
+ }) {
214
+ const headers = {
215
+ Authorization: `Bearer ${apiKey}`,
216
+ "User-Agent": USER_AGENT
217
+ };
218
+ if (isSet2(projectId)) headers["X-Project-Id"] = projectId;
219
+ return headers;
220
+ }
221
+
222
+ // src/agent/protocol.ts
223
+ var PROTOCOL_VERSION = 1;
224
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
225
+ var isString = (value) => typeof value === "string";
226
+ var isMessageList = (value) => Array.isArray(value) && value.every((item) => isRecord(item) && isString(item.role));
227
+ var isStringList = (value) => Array.isArray(value) && value.every(isString);
228
+ var readRegistered = (frame) => {
229
+ if (!Array.isArray(frame.agents) || !isString(frame.instanceId)) return null;
230
+ const agents = [];
231
+ for (const entry of frame.agents) {
232
+ if (!isRecord(entry) || !isString(entry.name)) return null;
233
+ const id = isString(entry.id) ? entry.id : isString(entry.agentId) ? entry.agentId : null;
234
+ if (id === null) return null;
235
+ agents.push({
236
+ name: entry.name,
237
+ environment: isString(entry.environment) ? entry.environment : "",
238
+ id,
239
+ url: isString(entry.url) ? entry.url : "",
240
+ parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : []
241
+ });
242
+ }
243
+ return {
244
+ type: "registered",
245
+ protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
246
+ agents,
247
+ heartbeatIntervalMs: typeof frame.heartbeatIntervalMs === "number" ? frame.heartbeatIntervalMs : 1e4,
248
+ instanceId: frame.instanceId
249
+ };
250
+ };
251
+ var readRefused = (frame) => ({
252
+ type: "refused",
253
+ protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
254
+ code: isString(frame.code) ? frame.code : "agent_register_refused",
255
+ message: isString(frame.message) ? frame.message : "The platform refused the registration.",
256
+ ...isRecord(frame.meta) ? { meta: frame.meta } : {}
257
+ });
258
+ var readParams = (value) => {
259
+ if (!isRecord(value)) return {};
260
+ const params = {};
261
+ for (const [name, item] of Object.entries(value)) {
262
+ if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
263
+ params[name] = item;
264
+ }
265
+ }
266
+ return params;
267
+ };
268
+ var readDeadline = (value) => {
269
+ if (typeof value === "number" && Number.isFinite(value)) return value;
270
+ if (isString(value)) {
271
+ const parsed = Date.parse(value);
272
+ return Number.isNaN(parsed) ? null : parsed;
273
+ }
274
+ return null;
275
+ };
276
+ var readCall = (frame) => {
277
+ if (!isString(frame.callId) || !isString(frame.agentId)) return null;
278
+ const messages = isMessageList(frame.messages) ? frame.messages : [];
279
+ const run = isRecord(frame.run) ? frame.run : {};
280
+ return {
281
+ type: "call",
282
+ protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
283
+ callId: frame.callId,
284
+ agentId: frame.agentId,
285
+ threadId: isString(frame.threadId) ? frame.threadId : "",
286
+ messages,
287
+ newMessages: isMessageList(frame.newMessages) ? frame.newMessages : messages,
288
+ params: readParams(frame.params),
289
+ session: frame.session === void 0 ? null : frame.session,
290
+ traceparent: isString(frame.traceparent) ? frame.traceparent : null,
291
+ deadlineAt: readDeadline(frame.deadlineAt),
292
+ run: {
293
+ ...isString(run.scenarioRunId) ? { scenarioRunId: run.scenarioRunId } : {},
294
+ ...isString(run.scenarioName) ? { scenarioName: run.scenarioName } : {},
295
+ ...isString(run.batchRunId) ? { batchRunId: run.batchRunId } : {}
296
+ }
297
+ };
298
+ };
299
+ function parseServerFrame(raw) {
300
+ let parsed;
301
+ try {
302
+ parsed = JSON.parse(raw);
303
+ } catch {
304
+ return null;
305
+ }
306
+ if (!isRecord(parsed) || !isString(parsed.type)) return null;
307
+ switch (parsed.type) {
308
+ case "registered":
309
+ return readRegistered(parsed);
310
+ case "refused":
311
+ return readRefused(parsed);
312
+ case "call":
313
+ return readCall(parsed);
314
+ case "cancel":
315
+ return isString(parsed.callId) ? {
316
+ type: "cancel",
317
+ protocol: typeof parsed.protocol === "number" ? parsed.protocol : PROTOCOL_VERSION,
318
+ callId: parsed.callId
319
+ } : null;
320
+ default:
321
+ return null;
322
+ }
323
+ }
324
+ function serializeFrame(frame) {
325
+ return JSON.stringify(frame);
326
+ }
327
+ function traceIdFromTraceparent(traceparent) {
328
+ if (!traceparent) return null;
329
+ const parts = traceparent.trim().split("-");
330
+ const traceId = parts[1];
331
+ if (parts.length < 4 || !traceId || !/^[0-9a-f]{32}$/i.test(traceId)) return null;
332
+ return traceId.toLowerCase();
333
+ }
334
+
335
+ // src/agent/schema.ts
336
+ var AgentParameterError = class extends Error {
337
+ constructor(message) {
338
+ super(message);
339
+ this.code = "agent_parameter_invalid";
340
+ this.name = "AgentParameterError";
341
+ }
342
+ };
343
+ var ACCEPTED_FORMS = `parameters must be a definition map ({ model: { options: [...], default: '...' } }), a Standard JSON Schema object (one with "~standard".jsonSchema), or a JSON Schema object ({ type: 'object', properties })`;
344
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
345
+ var isStandardJsonSchema = (value) => {
346
+ if (!isRecord2(value)) return false;
347
+ const standard = value["~standard"];
348
+ return isRecord2(standard) && isRecord2(standard.jsonSchema);
349
+ };
350
+ var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
351
+ var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
352
+ var definitionType = (definition) => {
353
+ if (definition.type) return definition.type;
354
+ if (definition.options) return "string";
355
+ const value = definition.default;
356
+ if (typeof value === "number") return "number";
357
+ if (typeof value === "boolean") return "boolean";
358
+ return "string";
359
+ };
360
+ var definitionMapToSchema = (definitions) => {
361
+ const properties = {};
362
+ const required = [];
363
+ for (const [name, definition] of Object.entries(definitions)) {
364
+ const property = { type: definitionType(definition) };
365
+ if (definition.options) property.enum = [...definition.options];
366
+ if (definition.default !== void 0) property.default = definition.default;
367
+ else required.push(name);
368
+ if (definition.description !== void 0) property.description = definition.description;
369
+ properties[name] = property;
370
+ }
371
+ const schema = { type: "object", properties };
372
+ if (required.length > 0) schema.required = required;
373
+ return schema;
374
+ };
375
+ var readStandardJsonSchema = (input) => {
376
+ const converter = input["~standard"].jsonSchema;
377
+ const options = { target: "draft-2020-12" };
378
+ const schema = converter.input?.(options) ?? converter.output?.(options);
379
+ if (schema === void 0) {
380
+ throw new AgentParameterError(`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`);
381
+ }
382
+ if (!isRecord2(schema)) {
383
+ throw new AgentParameterError(`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`);
384
+ }
385
+ return schema;
386
+ };
387
+ function toParameterSchema(input) {
388
+ if (input === void 0) return { type: "object", properties: {} };
389
+ if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
390
+ if (isJsonSchemaObject(input)) return input;
391
+ if (isDefinitionMap(input)) return definitionMapToSchema(input);
392
+ throw new AgentParameterError(ACCEPTED_FORMS);
393
+ }
394
+ var specType = (property) => {
395
+ const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
396
+ if (type === "number" || type === "integer") return "number";
397
+ if (type === "boolean") return "boolean";
398
+ return "string";
399
+ };
400
+ var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
401
+ function parameterSpecsFromSchema(schema) {
402
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
403
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
404
+ const specs = [];
405
+ for (const [name, raw] of Object.entries(properties)) {
406
+ const property = isRecord2(raw) ? raw : {};
407
+ const spec = { name, type: specType(property) };
408
+ const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
409
+ if (options && options.length > 0) spec.options = options;
410
+ const fallback = scalar(property.default);
411
+ if (fallback !== void 0) spec.default = fallback;
412
+ if (typeof property.description === "string") spec.description = property.description;
413
+ spec.required = fallback === void 0 && required.has(name);
414
+ specs.push(spec);
415
+ }
416
+ return specs;
417
+ }
418
+ var coerce = ({
419
+ spec,
420
+ value
421
+ }) => {
422
+ if (spec.type === "number") {
423
+ const asNumber = typeof value === "number" ? value : Number(value);
424
+ if (typeof value === "boolean" || !Number.isFinite(asNumber) || String(value).trim() === "") {
425
+ throw new AgentParameterError(`parameter "${spec.name}" must be a number, got ${JSON.stringify(value)}`);
426
+ }
427
+ return asNumber;
428
+ }
429
+ if (spec.type === "boolean") {
430
+ if (typeof value === "boolean") return value;
431
+ if (value === "true") return true;
432
+ if (value === "false") return false;
433
+ throw new AgentParameterError(`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`);
434
+ }
435
+ const asString = typeof value === "string" ? value : String(value);
436
+ if (spec.options && !spec.options.includes(asString)) {
437
+ throw new AgentParameterError(
438
+ `parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
439
+ );
440
+ }
441
+ return asString;
442
+ };
443
+ function resolveParameterValues({
444
+ specs,
445
+ supplied
446
+ }) {
447
+ const values = { ...supplied ?? {} };
448
+ for (const spec of specs) {
449
+ const value = values[spec.name];
450
+ if (value === void 0) {
451
+ if (spec.default !== void 0) {
452
+ values[spec.name] = spec.default;
453
+ continue;
454
+ }
455
+ if (!spec.required) continue;
456
+ throw new AgentParameterError(`parameter "${spec.name}" is required and the run did not supply it`);
457
+ }
458
+ values[spec.name] = coerce({ spec, value });
459
+ }
460
+ return values;
461
+ }
462
+ var issuePath = (issue) => (issue.path ?? []).map(
463
+ (segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
464
+ ).join(".");
465
+ async function validateParameterValues({
466
+ schema,
467
+ values
468
+ }) {
469
+ const standard = schema["~standard"];
470
+ const result = await standard.validate?.(values);
471
+ if (result === void 0) return values;
472
+ if (result.issues) {
473
+ const detail = result.issues.map((issue) => {
474
+ const path = issuePath(issue);
475
+ return path ? `${path}: ${issue.message}` : issue.message;
476
+ }).join("; ");
477
+ throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
478
+ }
479
+ const parsed = isRecord2(result.value) ? result.value : {};
480
+ return { ...values, ...parsed };
481
+ }
482
+ function createParameterReader({
483
+ input,
484
+ specs
485
+ }) {
486
+ const schema = isStandardJsonSchema(input) ? input : void 0;
487
+ return async (supplied) => {
488
+ const values = resolveParameterValues({ specs, supplied });
489
+ return schema ? validateParameterValues({ schema, values }) : values;
490
+ };
491
+ }
492
+
493
+ // src/agent/transport.ts
494
+ var import_node_module = require("module");
495
+ var AGENT_TRANSPORTS = ["websocket", "http"];
496
+ var INSTANCE_TOKEN_HEADER = "X-Agent-Instance-Token";
497
+ var isSet3 = (value) => typeof value === "string" && value.trim() !== "";
498
+ function resolveTransport({
499
+ explicit,
500
+ env = process.env
501
+ }) {
502
+ const candidate = isSet3(explicit) ? explicit : env.LANGWATCH_AGENT_TRANSPORT;
503
+ return isSet3(candidate) && candidate.trim().toLowerCase() === "http" ? "http" : "websocket";
504
+ }
505
+ var NoWebSocketError = class extends Error {
506
+ constructor() {
507
+ super("the ws package is not installed, so no socket can carry the API key header");
508
+ this.name = "NoWebSocketError";
509
+ }
510
+ };
511
+ var textOf = (data) => {
512
+ if (typeof data === "string") return data;
513
+ if (Buffer.isBuffer(data)) return data.toString("utf8");
514
+ if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
515
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
516
+ return String(data);
517
+ };
518
+ var wrapWs = (socket) => {
519
+ const closeListeners = [];
520
+ let closed = false;
521
+ const emitClose = (code) => {
522
+ if (closed) return;
523
+ closed = true;
524
+ for (const listener of closeListeners) listener(code);
525
+ };
526
+ socket.on("close", (code) => emitClose(code));
527
+ return {
528
+ send: (data) => socket.send(data),
529
+ close: (code, reason) => socket.close(code, reason),
530
+ terminate: () => socket.terminate(),
531
+ onOpen: (listener) => socket.on("open", listener),
532
+ onMessage: (listener) => socket.on("message", (data) => listener(textOf(data))),
533
+ onClose: (listener) => closeListeners.push(listener),
534
+ onError: (listener) => socket.on("error", listener),
535
+ onPing: (listener) => socket.on("ping", listener),
536
+ onUpgradeRefused: (listener) => socket.on("unexpected-response", (request, response) => {
537
+ listener(response.statusCode ?? 0);
538
+ response.resume();
539
+ request.destroy();
540
+ emitClose(1006);
541
+ })
542
+ };
543
+ };
544
+ var wsConstructor = () => {
545
+ try {
546
+ const loaded = (0, import_node_module.createRequire)(__filename)("ws");
547
+ return typeof loaded.WebSocket === "function" ? loaded.WebSocket : null;
548
+ } catch {
549
+ return null;
550
+ }
551
+ };
552
+ var defaultSocketFactory = ({ url, headers }) => {
553
+ const Ws = wsConstructor();
554
+ if (!Ws) throw new NoWebSocketError();
555
+ return wrapWs(new Ws(url, { headers }));
556
+ };
557
+ var SESSION_LOST_CLOSE_CODE = 1012;
558
+ var POST_RETRY_DELAYS_MS = [250, 500, 1e3];
559
+ var EMPTY_POLL_FLOOR_MS = 250;
560
+ var CLOSE_DEADLINE_MS = 500;
561
+ var describe = (error) => error instanceof Error ? error.message : String(error);
562
+ var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms).unref());
563
+ var HttpLongPollSocket = class {
564
+ constructor(options) {
565
+ this.messageListeners = [];
566
+ this.closeListeners = [];
567
+ this.errorListeners = [];
568
+ this.pingListeners = [];
569
+ this.inFlight = /* @__PURE__ */ new Set();
570
+ this.polls = new AbortController();
571
+ this.frames = new AbortController();
572
+ this.outbox = Promise.resolve();
573
+ this.token = null;
574
+ this.closed = false;
575
+ this.closeEmitted = false;
576
+ this.settleRegistered = () => void 0;
577
+ this.url = options.url;
578
+ this.headers = options.headers;
579
+ const fetchImpl = options.fetch ?? globalThis.fetch;
580
+ if (typeof fetchImpl !== "function") {
581
+ throw new Error("the HTTP transport needs a global fetch; run on Node 20 or later");
582
+ }
583
+ this.fetchImpl = fetchImpl;
584
+ this.registered = new Promise((resolve) => {
585
+ this.settleRegistered = resolve;
586
+ });
587
+ }
588
+ send(data) {
589
+ let frame;
590
+ try {
591
+ frame = JSON.parse(data);
592
+ } catch {
593
+ return;
594
+ }
595
+ if (frame.type === "register") {
596
+ void this.register(data);
597
+ return;
598
+ }
599
+ if (frame.type === "ack" && typeof frame.callId === "string") this.inFlight.add(frame.callId);
600
+ if (frame.type === "result" && typeof frame.callId === "string") this.inFlight.delete(frame.callId);
601
+ this.outbox = this.outbox.then(() => this.registered).then(() => this.post(data)).catch(() => void 0);
602
+ }
603
+ /**
604
+ * Stops polling, lets the frames already queued go out, then reports the
605
+ * close. The wait is bounded: on the deadline the frame requests are aborted
606
+ * and the close is reported anyway, so a request that never answers cannot
607
+ * hold the connection open.
608
+ */
609
+ close(code = 1e3) {
610
+ this.closed = true;
611
+ this.polls.abort();
612
+ const deadline = setTimeout(() => {
613
+ this.frames.abort();
614
+ this.emitClose(code);
615
+ }, CLOSE_DEADLINE_MS);
616
+ deadline.unref();
617
+ void this.outbox.finally(() => {
618
+ clearTimeout(deadline);
619
+ this.emitClose(code);
620
+ });
621
+ }
622
+ terminate() {
623
+ this.closed = true;
624
+ this.polls.abort();
625
+ this.frames.abort();
626
+ this.emitClose(1006);
627
+ }
628
+ onOpen(listener) {
629
+ setTimeout(() => {
630
+ if (!this.closed) listener();
631
+ }, 0);
632
+ }
633
+ onMessage(listener) {
634
+ this.messageListeners.push(listener);
635
+ }
636
+ onClose(listener) {
637
+ this.closeListeners.push(listener);
638
+ }
639
+ onError(listener) {
640
+ this.errorListeners.push(listener);
641
+ }
642
+ onPing(listener) {
643
+ this.pingListeners.push(listener);
644
+ }
645
+ requestHeaders() {
646
+ return {
647
+ ...this.headers,
648
+ "Content-Type": "application/json",
649
+ ...this.token ? { [INSTANCE_TOKEN_HEADER]: this.token } : {}
650
+ };
651
+ }
652
+ async register(data) {
653
+ let response;
654
+ try {
655
+ response = await this.fetchImpl(`${this.url}/register`, {
656
+ method: "POST",
657
+ headers: this.requestHeaders(),
658
+ body: data,
659
+ signal: this.polls.signal
660
+ });
661
+ } catch (error) {
662
+ if (!this.closed) this.fail(`could not reach ${this.url}/register (${describe(error)})`, 1006);
663
+ return;
664
+ }
665
+ const body = await this.jsonOf(response);
666
+ const frame = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
667
+ if (!frame) {
668
+ this.fail(`the register request was answered with HTTP ${response.status}`, 1006);
669
+ return;
670
+ }
671
+ if (typeof body?.instanceToken === "string") this.token = body.instanceToken;
672
+ this.settleRegistered();
673
+ this.emitMessage(JSON.stringify(frame));
674
+ if (frame.type !== "registered") return;
675
+ if (!this.token) {
676
+ this.fail("the register answer carried no instance token", 1006);
677
+ return;
678
+ }
679
+ void this.pollLoop();
680
+ }
681
+ async pollLoop() {
682
+ while (!this.closed && this.token) {
683
+ const query = this.inFlight.size > 0 ? `?inFlight=${encodeURIComponent([...this.inFlight].join(","))}` : "";
684
+ const startedAt = Date.now();
685
+ let response;
686
+ try {
687
+ response = await this.fetchImpl(`${this.url}/poll${query}`, {
688
+ method: "GET",
689
+ headers: this.requestHeaders(),
690
+ signal: this.polls.signal
691
+ });
692
+ } catch (error) {
693
+ if (!this.closed) this.fail(`the poll failed (${describe(error)})`, 1006);
694
+ return;
695
+ }
696
+ if (this.closed) return;
697
+ if (response.status === 410) {
698
+ this.fail("the platform no longer knows this instance, registering again", SESSION_LOST_CLOSE_CODE);
699
+ return;
700
+ }
701
+ const body = await this.jsonOf(response);
702
+ if (!response.ok) {
703
+ const answered = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
704
+ if (answered) this.emitMessage(JSON.stringify(answered));
705
+ if (answered?.type === "refused") {
706
+ return;
707
+ }
708
+ this.fail(`the poll was answered with HTTP ${response.status}`, 1006);
709
+ return;
710
+ }
711
+ const frames = Array.isArray(body?.frames) ? body.frames : [];
712
+ for (const frame of frames) {
713
+ const entry = frame;
714
+ if (entry.type === "cancel" && typeof entry.callId === "string") this.inFlight.delete(entry.callId);
715
+ this.emitMessage(JSON.stringify(frame));
716
+ }
717
+ for (const listener of this.pingListeners) listener();
718
+ if (frames.length === 0) {
719
+ const elapsedMs = Date.now() - startedAt;
720
+ if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
721
+ }
722
+ }
723
+ }
724
+ async post(data) {
725
+ if (!this.token) return;
726
+ for (let attempt = 0; ; attempt += 1) {
727
+ if (this.frames.signal.aborted) return;
728
+ let response = null;
729
+ try {
730
+ response = await this.fetchImpl(`${this.url}/frames`, {
731
+ method: "POST",
732
+ headers: this.requestHeaders(),
733
+ body: `{"frames":[${data}]}`,
734
+ signal: this.frames.signal
735
+ });
736
+ } catch {
737
+ if (this.frames.signal.aborted) return;
738
+ response = null;
739
+ }
740
+ if (response?.ok) return;
741
+ if (response?.status === 410) {
742
+ this.fail("the platform no longer knows this instance, registering again", SESSION_LOST_CLOSE_CODE);
743
+ return;
744
+ }
745
+ if (response && response.status < 500) return;
746
+ const delay = POST_RETRY_DELAYS_MS[attempt];
747
+ if (delay === void 0) {
748
+ this.fail(`a frame could not be posted after ${attempt} retries`, 1006);
749
+ return;
750
+ }
751
+ await wait(delay);
752
+ }
753
+ }
754
+ async jsonOf(response) {
755
+ try {
756
+ const parsed = await response.json();
757
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
758
+ } catch {
759
+ return null;
760
+ }
761
+ }
762
+ fail(message, code) {
763
+ for (const listener of this.errorListeners) listener(new Error(message));
764
+ this.closed = true;
765
+ this.polls.abort();
766
+ this.frames.abort();
767
+ this.emitClose(code);
768
+ }
769
+ emitMessage(data) {
770
+ for (const listener of this.messageListeners) listener(data);
771
+ }
772
+ emitClose(code) {
773
+ if (this.closeEmitted) return;
774
+ this.closeEmitted = true;
775
+ this.settleRegistered();
776
+ for (const listener of this.closeListeners) listener(code);
777
+ }
778
+ };
779
+
780
+ // src/agent/client.ts
781
+ var RECONNECT_BASE_MS = 1e3;
782
+ var RECONNECT_MAX_MS = 3e4;
783
+ var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
784
+ function reconnectDelayMs({
785
+ attempt,
786
+ baseMs = RECONNECT_BASE_MS,
787
+ maxMs = RECONNECT_MAX_MS,
788
+ random = Math.random
789
+ }) {
790
+ const exponential = Math.min(maxMs, baseMs * 2 ** Math.min(attempt, 16));
791
+ const jittered = exponential * (0.75 + random() * 0.5);
792
+ return Math.round(Math.min(maxMs, Math.max(baseMs, jittered)));
793
+ }
794
+ var NOT_CONNECTED = "not connected to LangWatch";
795
+ function refusalAdvice(frame) {
796
+ switch (frame.code) {
797
+ case "project_required": {
798
+ const projects = Array.isArray(frame.meta?.projects) ? frame.meta.projects : [];
799
+ const listed = projects.map((project) => {
800
+ const entry = project;
801
+ const id = typeof entry.id === "string" ? entry.id : "";
802
+ const name = typeof entry.name === "string" ? entry.name : "";
803
+ return name && id ? `${name} (${id})` : id || name;
804
+ }).filter((line) => line !== "");
805
+ return `the API key reaches more than one project. Set LANGWATCH_PROJECT_ID to one of: ${listed.length > 0 ? listed.join(", ") : "the projects the key reaches"}.`;
806
+ }
807
+ case "api_key_invalid":
808
+ return "the API key is not valid. Set LANGWATCH_API_KEY to a key from the project settings.";
809
+ case "key_type_not_allowed":
810
+ return "this key type cannot connect agents. Set LANGWATCH_API_KEY to a personal or project API key.";
811
+ case "permission_denied":
812
+ return "the API key cannot manage scenarios. Use a key with the scenarios:manage permission.";
813
+ case "protocol_invalid":
814
+ return `${frame.message} Update the langwatch package to a version that speaks protocol ${PROTOCOL_VERSION} or later.`;
815
+ case "replica_count_unsupported":
816
+ return `${frame.message} Connected agents on a LangWatch deployment without Redis need one app replica.`;
817
+ case "parameters_invalid":
818
+ case "environment_invalid":
819
+ return frame.message;
820
+ default:
821
+ return `${frame.message} (${frame.code})`;
822
+ }
823
+ }
824
+ var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
825
+ var CLOSE_GRACE_MS = 500;
826
+ var describeError = (error) => error instanceof Error ? error.message : String(error);
827
+ var AgentClient = class {
828
+ constructor(config) {
829
+ this.agents = [];
830
+ this.byId = /* @__PURE__ */ new Map();
831
+ this.inFlight = /* @__PURE__ */ new Map();
832
+ this.upgradeStatus = null;
833
+ this.transportAnnounced = false;
834
+ this.socket = null;
835
+ this.registered = false;
836
+ this.stopped = false;
837
+ /** True while a socket is closed on purpose to register the full agent list again. */
838
+ this.restarting = false;
839
+ this.attempt = 0;
840
+ this.connectTimer = null;
841
+ this.watchdog = null;
842
+ this.heartbeatIntervalMs = 1e4;
843
+ this.closeWaiters = [];
844
+ this.lastError = null;
845
+ this.failureNoticeAt = null;
846
+ this.gaveUp = false;
847
+ this.instance = buildInstance({ label: config.instanceLabel });
848
+ this.url = resolveConnectUrl(config.endpoint);
849
+ this.httpUrl = resolveHttpConnectUrl(config.endpoint);
850
+ this.activeTransport = resolveTransport({ explicit: config.transport });
851
+ this.headers = buildConnectHeaders({ apiKey: config.apiKey, projectId: config.projectId });
852
+ this.logger = config.logger;
853
+ this.openSocket = config.socketFactory ?? defaultSocketFactory;
854
+ this.backoff = config.backoff ?? { baseMs: RECONNECT_BASE_MS, maxMs: RECONNECT_MAX_MS };
855
+ this.failureNoticeIntervalMs = config.failureNoticeIntervalMs ?? FAILURE_NOTICE_INTERVAL_MS;
856
+ }
857
+ get instanceId() {
858
+ return this.instance.id;
859
+ }
860
+ /** The transport the client speaks now. */
861
+ get transport() {
862
+ return this.activeTransport;
863
+ }
864
+ get isRegistered() {
865
+ return this.registered;
866
+ }
867
+ /** True once the client gave up: refused, or no socket implementation. No timer is left behind. */
868
+ get isStopped() {
869
+ return this.stopped;
870
+ }
871
+ /** True while a reconnect is scheduled with a timer that keeps the process up. */
872
+ get hasPendingConnect() {
873
+ return this.connectTimer?.hasRef() ?? false;
874
+ }
875
+ /** True while the client is between attempts or inside one, with the process kept up. */
876
+ get isRetrying() {
877
+ if (this.stopped) return false;
878
+ return this.connectTimer ? this.connectTimer.hasRef() : this.socket !== null;
879
+ }
880
+ /** Adds an agent and connects on the next tick, or reconnects when a socket is already open. */
881
+ addAgent(runtime) {
882
+ this.agents.push(runtime);
883
+ if (this.gaveUp) {
884
+ this.logger.debug(`agent "${runtime.name}" ${NOT_CONNECTED}: the connection gave up earlier in this process`);
885
+ return;
886
+ }
887
+ this.stopped = false;
888
+ if (this.socket) {
889
+ this.restartSocket();
890
+ return;
891
+ }
892
+ this.scheduleConnect(0);
893
+ }
894
+ /** Closes the socket and connects again at once, keeping the reconnect loop. */
895
+ restartSocket() {
896
+ const socket = this.socket;
897
+ if (!socket) return;
898
+ this.restarting = true;
899
+ try {
900
+ socket.close(1e3, "agents changed");
901
+ } catch {
902
+ this.restarting = false;
903
+ this.socket = null;
904
+ this.scheduleConnect(0);
905
+ }
906
+ }
907
+ /** Removes an agent; the last one leaving deregisters and closes the socket. */
908
+ async removeAgent(runtime) {
909
+ const index = this.agents.indexOf(runtime);
910
+ if (index !== -1) this.agents.splice(index, 1);
911
+ for (const [id, agent] of this.byId) if (agent === runtime) this.byId.delete(id);
912
+ if (this.agents.length === 0) {
913
+ await this.disconnect();
914
+ return;
915
+ }
916
+ this.restartSocket();
917
+ }
918
+ /** Sends deregister, closes the socket and stops reconnecting. */
919
+ async disconnect() {
920
+ this.stopped = true;
921
+ this.clearTimers();
922
+ const socket = this.socket;
923
+ if (!socket) return;
924
+ if (this.registered) this.send({ type: "deregister", protocol: PROTOCOL_VERSION });
925
+ const closed = new Promise((resolve) => this.closeWaiters.push(resolve));
926
+ try {
927
+ socket.close(1e3, "deregister");
928
+ } catch {
929
+ }
930
+ const grace = new Promise((resolve) => {
931
+ const timer = setTimeout(() => {
932
+ try {
933
+ socket.terminate();
934
+ } catch {
935
+ }
936
+ resolve();
937
+ }, CLOSE_GRACE_MS);
938
+ timer.unref();
939
+ });
940
+ await Promise.race([closed, grace]);
941
+ }
942
+ /** Deregister with no wait, for a process that is already exiting. */
943
+ shutdownNow() {
944
+ this.stopped = true;
945
+ this.clearTimers();
946
+ if (!this.socket) return;
947
+ try {
948
+ if (this.registered) this.send({ type: "deregister", protocol: PROTOCOL_VERSION });
949
+ this.socket.close(1e3, "deregister");
950
+ } catch {
951
+ }
952
+ }
953
+ agentNames() {
954
+ return this.agents.map((agent) => `"${agent.name}"`).join(", ") || "the agent";
955
+ }
956
+ /**
957
+ * The reconnect timer keeps its ref on purpose: a script whose only job is
958
+ * the agent must stay up while it retries. Giving up clears every timer.
959
+ */
960
+ scheduleConnect(delayMs) {
961
+ if (this.stopped || this.connectTimer || this.socket) return;
962
+ this.connectTimer = setTimeout(() => {
963
+ this.connectTimer = null;
964
+ this.connect();
965
+ }, delayMs);
966
+ }
967
+ clearTimers() {
968
+ if (this.connectTimer) {
969
+ clearTimeout(this.connectTimer);
970
+ this.connectTimer = null;
971
+ }
972
+ if (this.watchdog) {
973
+ clearTimeout(this.watchdog);
974
+ this.watchdog = null;
975
+ }
976
+ }
977
+ giveUp(reason) {
978
+ this.stopped = true;
979
+ this.gaveUp = true;
980
+ this.clearTimers();
981
+ this.logger.warn(`agent ${this.agentNames()} ${NOT_CONNECTED}: ${reason}`);
982
+ }
983
+ connect() {
984
+ if (this.stopped || this.socket) return;
985
+ let socket;
986
+ try {
987
+ socket = this.activeTransport === "http" ? new HttpLongPollSocket({ url: this.httpUrl, headers: this.headers }) : this.openSocket({ url: this.url, headers: this.headers });
988
+ } catch (error) {
989
+ if (error instanceof NoWebSocketError) {
990
+ this.giveUp(
991
+ "the ws package is not installed. Run npm install ws; the platform reads the API key from a request header, and only ws can send it."
992
+ );
993
+ return;
994
+ }
995
+ this.lastError = describeError(error);
996
+ this.onClosed();
997
+ return;
998
+ }
999
+ this.socket = socket;
1000
+ socket.onOpen(() => this.guard(() => this.send(this.registerFrame())));
1001
+ socket.onMessage((data) => this.guard(() => this.onMessage(data)));
1002
+ socket.onError((error) => {
1003
+ this.lastError = describeError(error);
1004
+ });
1005
+ socket.onPing(() => this.armWatchdog());
1006
+ socket.onUpgradeRefused?.((status) => {
1007
+ this.upgradeStatus = status;
1008
+ });
1009
+ socket.onClose((code) => this.guard(() => this.onClosed(code)));
1010
+ }
1011
+ onClosed(code) {
1012
+ const wasRegistered = this.registered;
1013
+ const restarting = this.restarting;
1014
+ this.restarting = false;
1015
+ this.socket = null;
1016
+ this.registered = false;
1017
+ if (this.watchdog) {
1018
+ clearTimeout(this.watchdog);
1019
+ this.watchdog = null;
1020
+ }
1021
+ for (const waiter of this.closeWaiters.splice(0)) waiter();
1022
+ if (this.stopped) return;
1023
+ if (this.upgradeStatus !== null && this.activeTransport === "websocket") {
1024
+ const status = this.upgradeStatus;
1025
+ this.upgradeStatus = null;
1026
+ this.activeTransport = "http";
1027
+ this.logger.warn(
1028
+ `the WebSocket upgrade to ${this.url} was answered with HTTP ${status}; using the HTTP transport at ${this.httpUrl} instead`
1029
+ );
1030
+ this.scheduleConnect(0);
1031
+ return;
1032
+ }
1033
+ if (restarting) {
1034
+ this.scheduleConnect(0);
1035
+ return;
1036
+ }
1037
+ const delay = reconnectDelayMs({ attempt: this.attempt, ...this.backoff });
1038
+ this.attempt += 1;
1039
+ this.noteDisconnected({ wasRegistered, code });
1040
+ this.scheduleConnect(delay);
1041
+ }
1042
+ /**
1043
+ * One warning when the platform cannot be reached, one when a live
1044
+ * connection is lost, and silence while the retries run: the same notice
1045
+ * repeats only after the notice interval, and a reconnect resets it.
1046
+ */
1047
+ noteDisconnected({ wasRegistered, code }) {
1048
+ const now = Date.now();
1049
+ if (wasRegistered) {
1050
+ this.logger.warn(
1051
+ `lost the connection to LangWatch${code === void 0 ? "" : ` (${code})`}, reconnecting with backoff`
1052
+ );
1053
+ this.failureNoticeAt = now;
1054
+ return;
1055
+ }
1056
+ const stale = this.failureNoticeAt === null || now - this.failureNoticeAt >= this.failureNoticeIntervalMs;
1057
+ if (!stale) return;
1058
+ this.failureNoticeAt = now;
1059
+ const cause = this.lastError ? ` (${this.lastError})` : "";
1060
+ this.logger.warn(
1061
+ `agent ${this.agentNames()} ${NOT_CONNECTED}: could not reach ${this.url}${cause}. Check LANGWATCH_ENDPOINT and the network; retrying with backoff.`
1062
+ );
1063
+ }
1064
+ armWatchdog() {
1065
+ if (this.watchdog) clearTimeout(this.watchdog);
1066
+ const socket = this.socket;
1067
+ if (!socket) return;
1068
+ this.watchdog = setTimeout(() => {
1069
+ this.watchdog = null;
1070
+ this.logger.warn("no heartbeat from LangWatch, reconnecting");
1071
+ try {
1072
+ socket.terminate();
1073
+ } catch {
1074
+ }
1075
+ }, Math.max(15e3, this.heartbeatIntervalMs * 3));
1076
+ }
1077
+ registerFrame() {
1078
+ return {
1079
+ type: "register",
1080
+ protocol: PROTOCOL_VERSION,
1081
+ sdk: SDK_IDENTITY,
1082
+ instance: { ...this.instance, inFlightCallIds: [...this.inFlight.keys()] },
1083
+ agents: this.agents.map((agent) => agent.register)
1084
+ };
1085
+ }
1086
+ send(frame) {
1087
+ const socket = this.socket;
1088
+ if (!socket) return;
1089
+ try {
1090
+ socket.send(serializeFrame(frame));
1091
+ } catch (error) {
1092
+ this.logger.debug(`could not send ${frame.type}: ${describeError(error)}`);
1093
+ }
1094
+ }
1095
+ onMessage(data) {
1096
+ const frame = parseServerFrame(data);
1097
+ if (!frame) {
1098
+ this.logger.debug("dropped a frame the SDK does not know");
1099
+ return;
1100
+ }
1101
+ this.armWatchdog();
1102
+ switch (frame.type) {
1103
+ case "registered":
1104
+ this.onRegistered(frame);
1105
+ return;
1106
+ case "refused":
1107
+ this.onRefused(frame);
1108
+ return;
1109
+ case "call":
1110
+ void this.onCall(frame);
1111
+ return;
1112
+ case "cancel": {
1113
+ const call = this.inFlight.get(frame.callId);
1114
+ if (!call) return;
1115
+ call.cancelled = true;
1116
+ this.releaseCall({ callId: frame.callId, entry: call });
1117
+ return;
1118
+ }
1119
+ }
1120
+ }
1121
+ onRefused(frame) {
1122
+ this.giveUp(refusalAdvice(frame));
1123
+ try {
1124
+ this.socket?.close(1e3, frame.code);
1125
+ } catch {
1126
+ }
1127
+ }
1128
+ onRegistered(frame) {
1129
+ this.registered = true;
1130
+ this.attempt = 0;
1131
+ if (this.failureNoticeAt !== null) {
1132
+ this.logger.info("connected to LangWatch");
1133
+ this.failureNoticeAt = null;
1134
+ }
1135
+ if (this.activeTransport === "http" && !this.transportAnnounced) {
1136
+ this.transportAnnounced = true;
1137
+ this.logger.info(`connected to LangWatch over HTTP long polling at ${this.httpUrl}`);
1138
+ }
1139
+ this.heartbeatIntervalMs = frame.heartbeatIntervalMs;
1140
+ if (frame.instanceId && frame.instanceId !== this.instance.id) this.instance.id = frame.instanceId;
1141
+ this.byId.clear();
1142
+ for (const entry of frame.agents) {
1143
+ const runtime = this.agents.find(
1144
+ (agent) => agent.name === entry.name && agent.environment === entry.environment
1145
+ );
1146
+ if (!runtime) continue;
1147
+ this.byId.set(entry.id, runtime);
1148
+ this.logger.info(
1149
+ `agent "${entry.name}" (${entry.environment}) is online${entry.url ? `: ${entry.url}` : ""}`
1150
+ );
1151
+ for (const note of entry.parameterNotes) this.logger.warn(`agent "${entry.name}": ${note}`);
1152
+ }
1153
+ this.armWatchdog();
1154
+ }
1155
+ async onCall(frame) {
1156
+ const runtime = this.byId.get(frame.agentId);
1157
+ if (!runtime) {
1158
+ this.sendError({
1159
+ callId: frame.callId,
1160
+ code: "agent_call_failed",
1161
+ message: `no agent registered as ${frame.agentId}`
1162
+ });
1163
+ return;
1164
+ }
1165
+ const busy = [...this.inFlight.values()].filter((call2) => call2.runtime === runtime).length;
1166
+ if (busy >= runtime.concurrency) {
1167
+ this.sendError({
1168
+ callId: frame.callId,
1169
+ code: "agent_busy",
1170
+ message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
1171
+ });
1172
+ return;
1173
+ }
1174
+ if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
1175
+ this.sendError({
1176
+ callId: frame.callId,
1177
+ code: "agent_call_timeout",
1178
+ message: "the call deadline passed before it started"
1179
+ });
1180
+ return;
1181
+ }
1182
+ const entry = { runtime, cancelled: false, timer: null };
1183
+ this.inFlight.set(frame.callId, entry);
1184
+ let params;
1185
+ try {
1186
+ params = await runtime.readParams(frame.params);
1187
+ } catch (error) {
1188
+ this.releaseCall({ callId: frame.callId, entry });
1189
+ this.sendError({
1190
+ callId: frame.callId,
1191
+ code: "agent_parameter_invalid",
1192
+ message: describeError(error)
1193
+ });
1194
+ return;
1195
+ }
1196
+ if (entry.cancelled) {
1197
+ this.releaseCall({ callId: frame.callId, entry });
1198
+ return;
1199
+ }
1200
+ this.send({ type: "ack", protocol: PROTOCOL_VERSION, callId: frame.callId });
1201
+ this.armCallDeadline({ frame, entry, runtime });
1202
+ const parent = frame.traceparent ? import_api.propagation.extract(import_api.context.active(), { traceparent: frame.traceparent }) : import_api.context.active();
1203
+ const traceId = import_api.trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
1204
+ const call = {
1205
+ messages: frame.messages,
1206
+ newMessages: frame.newMessages,
1207
+ threadId: frame.threadId,
1208
+ session: frame.session,
1209
+ params,
1210
+ traceId
1211
+ };
1212
+ try {
1213
+ const result = await import_api.context.with(parent, () => runtime.run(call));
1214
+ if (entry.cancelled) return;
1215
+ this.send({
1216
+ type: "result",
1217
+ protocol: PROTOCOL_VERSION,
1218
+ callId: frame.callId,
1219
+ output: result.output,
1220
+ ...result.session === void 0 ? {} : { session: result.session }
1221
+ });
1222
+ } catch (error) {
1223
+ if (entry.cancelled) return;
1224
+ const code = error instanceof AgentParameterError ? error.code : "agent_call_failed";
1225
+ const message = describeError(error);
1226
+ this.logger.warn(`agent "${runtime.name}" call ${frame.callId} failed: ${message}`);
1227
+ this.sendError({ callId: frame.callId, code, message });
1228
+ } finally {
1229
+ this.releaseCall({ callId: frame.callId, entry });
1230
+ }
1231
+ }
1232
+ /** Frees the slot the call holds and drops its deadline timer. */
1233
+ releaseCall({ callId, entry }) {
1234
+ if (entry.timer) {
1235
+ clearTimeout(entry.timer);
1236
+ entry.timer = null;
1237
+ }
1238
+ if (this.inFlight.get(callId) === entry) this.inFlight.delete(callId);
1239
+ }
1240
+ /**
1241
+ * The call ends on its deadline: one timeout result, and the slot is free
1242
+ * from that moment. A handler that never returns then costs one call, not
1243
+ * every call after it.
1244
+ */
1245
+ armCallDeadline({
1246
+ frame,
1247
+ entry,
1248
+ runtime
1249
+ }) {
1250
+ const fromDeadline = frame.deadlineAt === null ? Infinity : frame.deadlineAt - Date.now();
1251
+ const limit = Math.min(fromDeadline, runtime.timeoutMs);
1252
+ if (!Number.isFinite(limit)) return;
1253
+ const timer = setTimeout(() => {
1254
+ entry.timer = null;
1255
+ if (entry.cancelled) return;
1256
+ entry.cancelled = true;
1257
+ this.releaseCall({ callId: frame.callId, entry });
1258
+ this.logger.warn(
1259
+ `agent "${runtime.name}" call ${frame.callId} passed its ${limit} ms limit`
1260
+ );
1261
+ this.sendError({
1262
+ callId: frame.callId,
1263
+ code: "agent_call_timeout",
1264
+ message: `the call passed the ${limit} ms limit of agent "${runtime.name}"`
1265
+ });
1266
+ }, Math.max(0, limit));
1267
+ timer.unref();
1268
+ entry.timer = timer;
1269
+ }
1270
+ sendError({ callId, code, message }) {
1271
+ this.send({ type: "result", protocol: PROTOCOL_VERSION, callId, error: { code, message } });
1272
+ }
1273
+ guard(action) {
1274
+ try {
1275
+ action();
1276
+ } catch (error) {
1277
+ this.logger.error(`agent client error: ${describeError(error)}`);
1278
+ }
1279
+ }
1280
+ };
1281
+ var shared = null;
1282
+ var sharedKey = null;
1283
+ var hooksInstalled = false;
1284
+ var noticesGiven = /* @__PURE__ */ new Set();
1285
+ var testOverrides = {};
1286
+ var signalHandlers = /* @__PURE__ */ new Map();
1287
+ var onShutdownSignal = (signal) => {
1288
+ const client = shared;
1289
+ const finish = () => {
1290
+ const handler = signalHandlers.get(signal);
1291
+ if (handler) process.removeListener(signal, handler);
1292
+ if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
1293
+ };
1294
+ if (!client) {
1295
+ finish();
1296
+ return;
1297
+ }
1298
+ void client.disconnect().finally(finish);
1299
+ };
1300
+ var onBeforeExit = () => {
1301
+ shared?.shutdownNow();
1302
+ };
1303
+ var installShutdownHooks = () => {
1304
+ if (hooksInstalled) return;
1305
+ hooksInstalled = true;
1306
+ for (const signal of SHUTDOWN_SIGNALS) {
1307
+ const handler = () => onShutdownSignal(signal);
1308
+ signalHandlers.set(signal, handler);
1309
+ process.on(signal, handler);
1310
+ }
1311
+ process.on("beforeExit", onBeforeExit);
1312
+ };
1313
+ function warnOnce({ logger, key, message }) {
1314
+ if (noticesGiven.has(key)) {
1315
+ logger.debug(message);
1316
+ return;
1317
+ }
1318
+ noticesGiven.add(key);
1319
+ logger.warn(message);
1320
+ }
1321
+ function getSharedClient(config) {
1322
+ const key = `${config.endpoint ?? ""}|${config.projectId ?? ""}|${config.apiKey}`;
1323
+ if (shared) {
1324
+ if (sharedKey !== key) {
1325
+ warnOnce({
1326
+ logger: config.logger,
1327
+ key: "credentials-differ",
1328
+ message: "connectAgent: this process already has an agent connection with other credentials or endpoint; the first ones are used"
1329
+ });
1330
+ }
1331
+ return shared;
1332
+ }
1333
+ shared = new AgentClient({ ...config, ...testOverrides });
1334
+ sharedKey = key;
1335
+ installShutdownHooks();
1336
+ return shared;
1337
+ }
1338
+
1339
+ // src/agent/define.ts
1340
+ var DEFAULT_TIMEOUT_MS = 12e4;
1341
+ var MAX_TIMEOUT_MS = 3e5;
1342
+ var isMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.role === "string";
1343
+ function normalizeReply(reply) {
1344
+ if (typeof reply === "string") return { output: reply };
1345
+ if (Array.isArray(reply) && reply.every(isMessage)) return { output: reply };
1346
+ if (isMessage(reply)) return { output: reply };
1347
+ if (typeof reply === "object" && reply !== null && "output" in reply) {
1348
+ const { output, session } = reply;
1349
+ const normalized = normalizeReply(output);
1350
+ return session === void 0 ? normalized : { output: normalized.output, session };
1351
+ }
1352
+ throw new Error(
1353
+ "the agent handler must return a string, a message, a list of messages, or { output, session }"
1354
+ );
1355
+ }
1356
+ var clampTimeout = ({ timeoutMs, logger, name }) => {
1357
+ if (timeoutMs === void 0) return DEFAULT_TIMEOUT_MS;
1358
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return DEFAULT_TIMEOUT_MS;
1359
+ if (timeoutMs > MAX_TIMEOUT_MS) {
1360
+ logger.warn(`agent "${name}": timeoutMs ${timeoutMs} is above the ${MAX_TIMEOUT_MS} cap, using the cap`);
1361
+ return MAX_TIMEOUT_MS;
1362
+ }
1363
+ return Math.floor(timeoutMs);
1364
+ };
1365
+ var readApiKey = (explicit) => {
1366
+ const candidate = explicit ?? process.env.LANGWATCH_API_KEY;
1367
+ return typeof candidate === "string" && candidate.trim() !== "" ? candidate.trim() : void 0;
1368
+ };
1369
+ var readProjectId = (explicit) => {
1370
+ const candidate = explicit ?? process.env.LANGWATCH_PROJECT_ID;
1371
+ return typeof candidate === "string" && candidate.trim() !== "" ? candidate.trim() : void 0;
1372
+ };
1373
+ function connectAgent(options, handler) {
1374
+ const logger = options.logger ?? new ConsoleLogger({ level: "info", prefix: "LangWatch" });
1375
+ const name = options.name?.trim();
1376
+ if (!name) throw new Error("connectAgent needs a name");
1377
+ const environment = resolveEnvironment({ explicit: options.environment });
1378
+ const parameters = toParameterSchema(options.parameters);
1379
+ const specs = parameterSpecsFromSchema(parameters);
1380
+ const timeoutMs = clampTimeout({ timeoutMs: options.timeoutMs, logger, name });
1381
+ const concurrency = Math.max(
1382
+ 1,
1383
+ Math.floor(options.concurrency ?? (environment === DEFAULT_ENVIRONMENT ? 1 : 4))
1384
+ );
1385
+ const runHandler = async (call) => normalizeReply(await handler(call));
1386
+ const readParams2 = createParameterReader({ input: options.parameters, specs });
1387
+ const invoke = async (call) => {
1388
+ const params = await readParams2(call.params);
1389
+ return runHandler({
1390
+ messages: call.messages,
1391
+ newMessages: call.newMessages ?? call.messages,
1392
+ threadId: call.threadId ?? `local_${Date.now().toString(36)}`,
1393
+ session: call.session ?? null,
1394
+ params,
1395
+ traceId: call.traceId ?? ""
1396
+ });
1397
+ };
1398
+ const runtime = {
1399
+ name,
1400
+ environment,
1401
+ register: {
1402
+ name,
1403
+ environment,
1404
+ parameters,
1405
+ concurrency,
1406
+ timeoutMs,
1407
+ ...options.sticky ? { sticky: true } : {}
1408
+ },
1409
+ readParams: readParams2,
1410
+ concurrency,
1411
+ timeoutMs,
1412
+ run: runHandler
1413
+ };
1414
+ let detach;
1415
+ if (!resolveEnabled({ explicit: options.enabled })) {
1416
+ logger.debug(`agent "${name}" not connected to LangWatch: the connection is disabled`);
1417
+ } else {
1418
+ const apiKey = readApiKey(options.apiKey);
1419
+ if (!apiKey) {
1420
+ warnOnce({
1421
+ logger,
1422
+ key: "no-api-key",
1423
+ message: `agent "${name}" not connected to LangWatch: no API key. Set LANGWATCH_API_KEY to run simulations against it.`
1424
+ });
1425
+ } else {
1426
+ const client = getSharedClient({
1427
+ apiKey,
1428
+ endpoint: options.endpoint,
1429
+ projectId: readProjectId(options.projectId),
1430
+ instanceLabel: resolveInstanceLabel({ explicit: options.instanceLabel }),
1431
+ transport: options.transport,
1432
+ logger
1433
+ });
1434
+ client.addAgent(runtime);
1435
+ detach = () => client.removeAgent(runtime);
1436
+ }
1437
+ }
1438
+ const connected = Object.assign(invoke, {
1439
+ environment,
1440
+ parameters,
1441
+ disconnect: async () => {
1442
+ if (!detach) return;
1443
+ const release = detach;
1444
+ detach = void 0;
1445
+ await release();
1446
+ }
1447
+ });
1448
+ Object.defineProperty(connected, "name", { value: name, configurable: true });
1449
+ return connected;
1450
+ }
1451
+ // Annotate the CommonJS export names for ESM import in node:
1452
+ 0 && (module.exports = {
1453
+ AGENT_TRANSPORTS,
1454
+ AgentParameterError,
1455
+ DEFAULT_TIMEOUT_MS,
1456
+ MAX_TIMEOUT_MS,
1457
+ PROTOCOL_VERSION,
1458
+ connectAgent,
1459
+ normalizeReply,
1460
+ parameterSpecsFromSchema,
1461
+ resolveConnectUrl,
1462
+ resolveEnvironment,
1463
+ resolveHttpConnectUrl,
1464
+ resolveTransport,
1465
+ sanitizeEnvironment,
1466
+ toParameterSchema
1467
+ });
1468
+ //# sourceMappingURL=index.js.map