langwatch 1.18.0 → 1.20.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 (91) hide show
  1. package/README.md +25 -11
  2. package/dist/agent/index.d.mts +122 -79
  3. package/dist/agent/index.d.ts +122 -79
  4. package/dist/agent/index.js +523 -300
  5. package/dist/agent/index.js.map +1 -1
  6. package/dist/agent/index.mjs +522 -301
  7. package/dist/agent/index.mjs.map +1 -1
  8. package/dist/chunk-5VYEG3RS.js +117 -0
  9. package/dist/chunk-5VYEG3RS.js.map +1 -0
  10. package/dist/{chunk-O4J3ZPZY.js → chunk-5ZNEXSAB.js} +7912 -2564
  11. package/dist/chunk-5ZNEXSAB.js.map +1 -0
  12. package/dist/chunk-BDQ2RG7R.mjs +852 -0
  13. package/dist/chunk-BDQ2RG7R.mjs.map +1 -0
  14. package/dist/chunk-BL3GHEKF.js +59 -0
  15. package/dist/chunk-BL3GHEKF.js.map +1 -0
  16. package/dist/chunk-C7IWJ2GJ.mjs +59 -0
  17. package/dist/chunk-C7IWJ2GJ.mjs.map +1 -0
  18. package/dist/chunk-EQ5CFQGJ.js +852 -0
  19. package/dist/chunk-EQ5CFQGJ.js.map +1 -0
  20. package/dist/{chunk-NPEK4RSD.mjs → chunk-ET5KYCG2.mjs} +7757 -2409
  21. package/dist/chunk-ET5KYCG2.mjs.map +1 -0
  22. package/dist/{chunk-MVQJMRM6.mjs → chunk-FKYYEGJ7.mjs} +120 -112
  23. package/dist/chunk-FKYYEGJ7.mjs.map +1 -0
  24. package/dist/{chunk-JJASOJOK.js → chunk-HCK7TCSK.js} +138 -130
  25. package/dist/chunk-HCK7TCSK.js.map +1 -0
  26. package/dist/{chunk-LCG5Y2EG.js → chunk-N76BG37W.js} +15 -101
  27. package/dist/chunk-N76BG37W.js.map +1 -0
  28. package/dist/chunk-SERULD4Z.mjs +117 -0
  29. package/dist/chunk-SERULD4Z.mjs.map +1 -0
  30. package/dist/{chunk-73QOHSHU.js → chunk-WDUUAIG3.js} +2 -2
  31. package/dist/chunk-WDUUAIG3.js.map +1 -0
  32. package/dist/{chunk-WMDQVWA6.mjs → chunk-WSGVZ3J2.mjs} +22 -108
  33. package/dist/chunk-WSGVZ3J2.mjs.map +1 -0
  34. package/dist/{chunk-57L2K66L.mjs → chunk-XQ4CP2JK.mjs} +2 -2
  35. package/dist/chunk-XQ4CP2JK.mjs.map +1 -0
  36. package/dist/cli/bundle.js +766 -479
  37. package/dist/implementation-B4Z2ksnA.d.mts +261 -0
  38. package/dist/implementation-BFUgzz5C.d.ts +261 -0
  39. package/dist/index.d.mts +398 -1123
  40. package/dist/index.d.ts +398 -1123
  41. package/dist/index.js +7613 -3566
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +7419 -3372
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/observability-sdk/index.d.mts +16 -76
  46. package/dist/observability-sdk/index.d.ts +16 -76
  47. package/dist/observability-sdk/index.js +8 -5
  48. package/dist/observability-sdk/index.js.map +1 -1
  49. package/dist/observability-sdk/index.mjs +8 -5
  50. package/dist/observability-sdk/instrumentation/langchain/index.d.mts +3 -5
  51. package/dist/observability-sdk/instrumentation/langchain/index.d.ts +3 -5
  52. package/dist/observability-sdk/instrumentation/langchain/index.js +160 -112
  53. package/dist/observability-sdk/instrumentation/langchain/index.js.map +1 -1
  54. package/dist/observability-sdk/instrumentation/langchain/index.mjs +148 -100
  55. package/dist/observability-sdk/instrumentation/langchain/index.mjs.map +1 -1
  56. package/dist/observability-sdk/semconv/index.d.mts +37 -0
  57. package/dist/observability-sdk/semconv/index.d.ts +37 -0
  58. package/dist/observability-sdk/semconv/index.js +121 -0
  59. package/dist/observability-sdk/semconv/index.js.map +1 -0
  60. package/dist/observability-sdk/semconv/index.mjs +121 -0
  61. package/dist/observability-sdk/semconv/index.mjs.map +1 -0
  62. package/dist/observability-sdk/setup/node/index.d.mts +73 -313
  63. package/dist/observability-sdk/setup/node/index.d.ts +73 -313
  64. package/dist/observability-sdk/setup/node/index.js +6 -4
  65. package/dist/observability-sdk/setup/node/index.js.map +1 -1
  66. package/dist/observability-sdk/setup/node/index.mjs +5 -3
  67. package/dist/types-B696jWYQ.d.mts +16010 -0
  68. package/dist/types-Bo6OvFct.d.ts +16010 -0
  69. package/dist/{types-VOZv9LYO.d.mts → types-CoS5PmeU.d.mts} +22 -59
  70. package/dist/{types-VOZv9LYO.d.ts → types-CoS5PmeU.d.ts} +22 -59
  71. package/dist/{types-DLfhJb38.d.mts → types-RkiBQZ9j.d.mts} +1 -6
  72. package/dist/{types-DLfhJb38.d.ts → types-RkiBQZ9j.d.ts} +1 -6
  73. package/dist/values-1yj9l0Sa.d.mts +112 -0
  74. package/dist/values-1yj9l0Sa.d.ts +112 -0
  75. package/package.json +95 -64
  76. package/dist/chunk-57L2K66L.mjs.map +0 -1
  77. package/dist/chunk-73QOHSHU.js.map +0 -1
  78. package/dist/chunk-HY2N6TO5.mjs +0 -566
  79. package/dist/chunk-HY2N6TO5.mjs.map +0 -1
  80. package/dist/chunk-JJASOJOK.js.map +0 -1
  81. package/dist/chunk-LCG5Y2EG.js.map +0 -1
  82. package/dist/chunk-MVQJMRM6.mjs.map +0 -1
  83. package/dist/chunk-NPEK4RSD.mjs.map +0 -1
  84. package/dist/chunk-O4J3ZPZY.js.map +0 -1
  85. package/dist/chunk-UEJSGC3P.js +0 -566
  86. package/dist/chunk-UEJSGC3P.js.map +0 -1
  87. package/dist/chunk-WMDQVWA6.mjs.map +0 -1
  88. package/dist/implementation-BvX-7vox.d.ts +0 -716
  89. package/dist/implementation-CQ__V0Vc.d.mts +0 -716
  90. package/dist/types-CNEUr6Hm.d.ts +0 -36682
  91. package/dist/types-DfPAHmAh.d.mts +0 -36682
@@ -1,4 +1,4 @@
1
- // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.26_tsx@4.22.4_typescript@6.0.3_yaml@2.9.0/node_modules/tsup/assets/esm_shims.js
1
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.26_supports-color@10.2.2_tsx@4.22.4_typescript@6.0.3_yaml@2.9.0/node_modules/tsup/assets/esm_shims.js
2
2
  import path from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  var getFilename = () => fileURLToPath(import.meta.url);
@@ -40,11 +40,11 @@ var ConsoleLogger = class {
40
40
  import { context, propagation, trace } from "@opentelemetry/api";
41
41
 
42
42
  // src/agent/identity.ts
43
- import * as os from "os";
44
43
  import { randomUUID } from "crypto";
44
+ import * as os from "os";
45
45
 
46
46
  // package.json
47
- var version = "1.18.0";
47
+ var version = "1.20.0";
48
48
 
49
49
  // src/internal/constants.ts
50
50
  var LANGWATCH_SDK_VERSION = version;
@@ -102,7 +102,9 @@ function resolveEnabled({
102
102
  env = process.env
103
103
  }) {
104
104
  const flag = env.LANGWATCH_AGENT_CONNECT;
105
- if (isSet2(flag) && !isTruthy(flag)) return false;
105
+ if (isSet2(flag)) {
106
+ if (!isTruthy(flag)) return false;
107
+ }
106
108
  if (explicit !== void 0) return explicit;
107
109
  return !isTruthy(env.CI);
108
110
  }
@@ -185,24 +187,37 @@ var isStringList = (value) => Array.isArray(value) && value.every(isString);
185
187
  var readScope = (value) => {
186
188
  if (!isRecord(value)) return { kind: "shared" };
187
189
  if (value.kind === "owner") return { kind: "owner" };
188
- if (value.kind === "host" && isString(value.hostLabel)) return { kind: "host", hostLabel: value.hostLabel };
190
+ if (value.kind === "host" && isString(value.hostLabel))
191
+ return { kind: "host", hostLabel: value.hostLabel };
189
192
  return { kind: "shared" };
190
193
  };
194
+ var readRegisteredAgent = (entry) => {
195
+ if (!isRecord(entry)) return null;
196
+ if (!isString(entry.name)) return null;
197
+ let id = null;
198
+ if (isString(entry.id)) {
199
+ id = entry.id;
200
+ } else if (isString(entry.agentId)) {
201
+ id = entry.agentId;
202
+ }
203
+ if (id === null) return null;
204
+ return {
205
+ name: entry.name,
206
+ environment: isString(entry.environment) ? entry.environment : "",
207
+ id,
208
+ url: isString(entry.url) ? entry.url : "",
209
+ parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
210
+ scope: readScope(entry.scope)
211
+ };
212
+ };
191
213
  var readRegistered = (frame) => {
192
- if (!Array.isArray(frame.agents) || !isString(frame.instanceId)) return null;
214
+ if (!Array.isArray(frame.agents)) return null;
215
+ if (!isString(frame.instanceId)) return null;
193
216
  const agents = [];
194
217
  for (const entry of frame.agents) {
195
- if (!isRecord(entry) || !isString(entry.name)) return null;
196
- const id = isString(entry.id) ? entry.id : isString(entry.agentId) ? entry.agentId : null;
197
- if (id === null) return null;
198
- agents.push({
199
- name: entry.name,
200
- environment: isString(entry.environment) ? entry.environment : "",
201
- id,
202
- url: isString(entry.url) ? entry.url : "",
203
- parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
204
- scope: readScope(entry.scope)
205
- });
218
+ const agent = readRegisteredAgent(entry);
219
+ if (agent === null) return null;
220
+ agents.push(agent);
206
221
  }
207
222
  return {
208
223
  type: "registered",
@@ -238,12 +253,15 @@ var readDeadline = (value) => {
238
253
  return null;
239
254
  };
240
255
  var readCall = (frame) => {
241
- if (!isString(frame.callId) || !isString(frame.agentId)) return null;
256
+ if (!isString(frame.callId)) return null;
257
+ if (!isString(frame.agentId)) return null;
242
258
  const messages = isMessageList(frame.messages) ? frame.messages : [];
243
259
  const run = isRecord(frame.run) ? frame.run : {};
244
260
  return {
245
261
  type: "call",
246
262
  protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
263
+ // A platform that predates hooks sends no kind: every call is a turn.
264
+ kind: isString(frame.kind) ? frame.kind : "turn",
247
265
  callId: frame.callId,
248
266
  agentId: frame.agentId,
249
267
  threadId: isString(frame.threadId) ? frame.threadId : "",
@@ -251,6 +269,7 @@ var readCall = (frame) => {
251
269
  newMessages: isMessageList(frame.newMessages) ? frame.newMessages : messages,
252
270
  params: readParams(frame.params),
253
271
  session: frame.session === void 0 ? null : frame.session,
272
+ state: isRecord(frame.state) ? frame.state : null,
254
273
  traceparent: isString(frame.traceparent) ? frame.traceparent : null,
255
274
  deadlineAt: readDeadline(frame.deadlineAt),
256
275
  run: {
@@ -267,7 +286,8 @@ function parseServerFrame(raw) {
267
286
  } catch {
268
287
  return null;
269
288
  }
270
- if (!isRecord(parsed) || !isString(parsed.type)) return null;
289
+ if (!isRecord(parsed)) return null;
290
+ if (!isString(parsed.type)) return null;
271
291
  switch (parsed.type) {
272
292
  case "registered":
273
293
  return readRegistered(parsed);
@@ -296,164 +316,6 @@ function traceIdFromTraceparent(traceparent) {
296
316
  return traceId.toLowerCase();
297
317
  }
298
318
 
299
- // src/agent/schema.ts
300
- var AgentParameterError = class extends Error {
301
- constructor(message) {
302
- super(message);
303
- this.code = "agent_parameter_invalid";
304
- this.name = "AgentParameterError";
305
- }
306
- };
307
- 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 })`;
308
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
309
- var isStandardJsonSchema = (value) => {
310
- if (!isRecord2(value)) return false;
311
- const standard = value["~standard"];
312
- return isRecord2(standard) && isRecord2(standard.jsonSchema);
313
- };
314
- var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
315
- var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
316
- var definitionType = (definition) => {
317
- if (definition.type) return definition.type;
318
- if (definition.options) return "string";
319
- const value = definition.default;
320
- if (typeof value === "number") return "number";
321
- if (typeof value === "boolean") return "boolean";
322
- return "string";
323
- };
324
- var definitionMapToSchema = (definitions) => {
325
- const properties = {};
326
- const required = [];
327
- for (const [name, definition] of Object.entries(definitions)) {
328
- const property = { type: definitionType(definition) };
329
- if (definition.options) property.enum = [...definition.options];
330
- if (definition.default !== void 0) property.default = definition.default;
331
- else required.push(name);
332
- if (definition.description !== void 0) property.description = definition.description;
333
- properties[name] = property;
334
- }
335
- const schema = { type: "object", properties };
336
- if (required.length > 0) schema.required = required;
337
- return schema;
338
- };
339
- var readStandardJsonSchema = (input) => {
340
- const converter = input["~standard"].jsonSchema;
341
- const options = { target: "draft-2020-12" };
342
- const schema = converter.input?.(options) ?? converter.output?.(options);
343
- if (schema === void 0) {
344
- throw new AgentParameterError(`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`);
345
- }
346
- if (!isRecord2(schema)) {
347
- throw new AgentParameterError(`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`);
348
- }
349
- return schema;
350
- };
351
- function toParameterSchema(input) {
352
- if (input === void 0) return { type: "object", properties: {} };
353
- if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
354
- if (isJsonSchemaObject(input)) return input;
355
- if (isDefinitionMap(input)) return definitionMapToSchema(input);
356
- throw new AgentParameterError(ACCEPTED_FORMS);
357
- }
358
- var specType = (property) => {
359
- const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
360
- if (type === "number" || type === "integer") return "number";
361
- if (type === "boolean") return "boolean";
362
- return "string";
363
- };
364
- var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
365
- function parameterSpecsFromSchema(schema) {
366
- const properties = isRecord2(schema.properties) ? schema.properties : {};
367
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
368
- const specs = [];
369
- for (const [name, raw] of Object.entries(properties)) {
370
- const property = isRecord2(raw) ? raw : {};
371
- const spec = { name, type: specType(property) };
372
- const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
373
- if (options && options.length > 0) spec.options = options;
374
- const fallback = scalar(property.default);
375
- if (fallback !== void 0) spec.default = fallback;
376
- if (typeof property.description === "string") spec.description = property.description;
377
- spec.required = fallback === void 0 && required.has(name);
378
- specs.push(spec);
379
- }
380
- return specs;
381
- }
382
- var coerce = ({
383
- spec,
384
- value
385
- }) => {
386
- if (spec.type === "number") {
387
- const asNumber = typeof value === "number" ? value : Number(value);
388
- if (typeof value === "boolean" || !Number.isFinite(asNumber) || String(value).trim() === "") {
389
- throw new AgentParameterError(`parameter "${spec.name}" must be a number, got ${JSON.stringify(value)}`);
390
- }
391
- return asNumber;
392
- }
393
- if (spec.type === "boolean") {
394
- if (typeof value === "boolean") return value;
395
- if (value === "true") return true;
396
- if (value === "false") return false;
397
- throw new AgentParameterError(`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`);
398
- }
399
- const asString = typeof value === "string" ? value : String(value);
400
- if (spec.options && !spec.options.includes(asString)) {
401
- throw new AgentParameterError(
402
- `parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
403
- );
404
- }
405
- return asString;
406
- };
407
- function resolveParameterValues({
408
- specs,
409
- supplied
410
- }) {
411
- const values = { ...supplied ?? {} };
412
- for (const spec of specs) {
413
- const value = values[spec.name];
414
- if (value === void 0) {
415
- if (spec.default !== void 0) {
416
- values[spec.name] = spec.default;
417
- continue;
418
- }
419
- if (!spec.required) continue;
420
- throw new AgentParameterError(`parameter "${spec.name}" is required and the run did not supply it`);
421
- }
422
- values[spec.name] = coerce({ spec, value });
423
- }
424
- return values;
425
- }
426
- var issuePath = (issue) => (issue.path ?? []).map(
427
- (segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
428
- ).join(".");
429
- async function validateParameterValues({
430
- schema,
431
- values
432
- }) {
433
- const standard = schema["~standard"];
434
- const result = await standard.validate?.(values);
435
- if (result === void 0) return values;
436
- if (result.issues) {
437
- const detail = result.issues.map((issue) => {
438
- const path2 = issuePath(issue);
439
- return path2 ? `${path2}: ${issue.message}` : issue.message;
440
- }).join("; ");
441
- throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
442
- }
443
- const parsed = isRecord2(result.value) ? result.value : {};
444
- return { ...values, ...parsed };
445
- }
446
- function createParameterReader({
447
- input,
448
- specs
449
- }) {
450
- const schema = isStandardJsonSchema(input) ? input : void 0;
451
- return async (supplied) => {
452
- const values = resolveParameterValues({ specs, supplied });
453
- return schema ? validateParameterValues({ schema, values }) : values;
454
- };
455
- }
456
-
457
319
  // src/agent/transport.ts
458
320
  import { createRequire } from "module";
459
321
 
@@ -470,11 +332,7 @@ var CREDENTIAL_HEADERS = [
470
332
  "x-project-id"
471
333
  ];
472
334
  var LangWatchRedirectError = class extends Error {
473
- constructor({
474
- url,
475
- location,
476
- status
477
- }) {
335
+ constructor({ url, location, status }) {
478
336
  super(
479
337
  `LangWatch refused to follow a redirect from ${url} to ${location ?? "an unknown location"} (HTTP ${status}). Set the endpoint to the final URL.`
480
338
  );
@@ -621,7 +479,19 @@ var upgrade = async ({
621
479
  const location = first.headers.get("location");
622
480
  const refused = refusalOf({ url, response: first });
623
481
  const target = location === null ? null : schemeUpgradeTarget({ url, location });
624
- if (location === null || first.status === 303 || target === null || isStream(init?.body)) {
482
+ if (location === null) {
483
+ discard(spare);
484
+ throw refused;
485
+ }
486
+ if (first.status === 303) {
487
+ discard(spare);
488
+ throw refused;
489
+ }
490
+ if (target === null) {
491
+ discard(spare);
492
+ throw refused;
493
+ }
494
+ if (isStream(init?.body)) {
625
495
  discard(spare);
626
496
  throw refused;
627
497
  }
@@ -671,7 +541,10 @@ function resolveTransport({
671
541
  env = process.env
672
542
  }) {
673
543
  const candidate = isSet3(explicit) ? explicit : env.LANGWATCH_AGENT_TRANSPORT;
674
- return isSet3(candidate) && candidate.trim().toLowerCase() === "http" ? "http" : "websocket";
544
+ if (!isSet3(candidate)) return "websocket";
545
+ const normalized = candidate.trim().toLowerCase();
546
+ if (normalized === "http") return "http";
547
+ return "websocket";
675
548
  }
676
549
  var NoWebSocketError = class extends Error {
677
550
  constructor() {
@@ -768,14 +641,14 @@ var HttpLongPollSocket = class {
768
641
  return;
769
642
  }
770
643
  if (frame.type === "ack" && typeof frame.callId === "string") this.inFlight.add(frame.callId);
771
- if (frame.type === "result" && typeof frame.callId === "string") this.inFlight.delete(frame.callId);
644
+ if (frame.type === "result" && typeof frame.callId === "string")
645
+ this.inFlight.delete(frame.callId);
772
646
  this.outbox = this.outbox.then(() => this.registered).then(() => this.post(data)).catch(() => void 0);
773
647
  }
774
648
  /**
775
- * Stops polling, lets the frames already queued go out, then reports the
776
- * close. The wait is bounded: on the deadline the frame requests are aborted
777
- * and the close is reported anyway, so a request that never answers cannot
778
- * hold the connection open.
649
+ * Stops polling, lets queued frames go out, then reports close. Bounded: on
650
+ * the deadline the frame requests are aborted and close is reported anyway,
651
+ * so an unanswering request cannot hold the connection open.
779
652
  */
780
653
  close(code = 1e3) {
781
654
  this.closed = true;
@@ -830,7 +703,8 @@ var HttpLongPollSocket = class {
830
703
  signal: this.polls.signal
831
704
  });
832
705
  } catch (error) {
833
- if (!this.closed) this.fail(`could not reach ${this.url}/register (${describe(error)})`, 1006);
706
+ if (!this.closed)
707
+ this.fail(`could not reach ${this.url}/register (${describe(error)})`, 1006);
834
708
  return;
835
709
  }
836
710
  const body = await this.jsonOf(response);
@@ -851,46 +725,66 @@ var HttpLongPollSocket = class {
851
725
  }
852
726
  async pollLoop() {
853
727
  while (!this.closed && this.token) {
854
- const query = this.inFlight.size > 0 ? `?inFlight=${encodeURIComponent([...this.inFlight].join(","))}` : "";
855
- const startedAt = Date.now();
856
- let response;
857
- try {
858
- response = await this.fetchImpl(`${this.url}/poll${query}`, {
859
- method: "GET",
860
- headers: this.requestHeaders(),
861
- signal: this.polls.signal
862
- });
863
- } catch (error) {
864
- if (!this.closed) this.fail(`the poll failed (${describe(error)})`, 1006);
865
- return;
866
- }
867
- if (this.closed) return;
868
- if (response.status === 410) {
869
- this.fail("the platform no longer knows this instance, registering again", SESSION_LOST_CLOSE_CODE);
870
- return;
871
- }
872
- const body = await this.jsonOf(response);
873
- if (!response.ok) {
874
- const answered = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
875
- if (answered) this.emitMessage(JSON.stringify(answered));
876
- if (answered?.type === "refused") {
877
- return;
878
- }
879
- this.fail(`the poll was answered with HTTP ${response.status}`, 1006);
880
- return;
881
- }
882
- const frames = Array.isArray(body?.frames) ? body.frames : [];
883
- for (const frame of frames) {
884
- const entry = frame;
885
- if (entry.type === "cancel" && typeof entry.callId === "string") this.inFlight.delete(entry.callId);
886
- this.emitMessage(JSON.stringify(frame));
887
- }
888
- for (const listener of this.pingListeners) listener();
889
- if (frames.length === 0) {
890
- const elapsedMs = Date.now() - startedAt;
891
- if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
892
- }
728
+ if (!await this.pollOnce()) return;
729
+ }
730
+ }
731
+ /** One long poll: delivers what it answered, and says whether polling goes on. */
732
+ async pollOnce() {
733
+ const query = this.inFlight.size > 0 ? `?inFlight=${encodeURIComponent([...this.inFlight].join(","))}` : "";
734
+ const startedAt = Date.now();
735
+ let response;
736
+ try {
737
+ response = await this.fetchImpl(`${this.url}/poll${query}`, {
738
+ method: "GET",
739
+ headers: this.requestHeaders(),
740
+ signal: this.polls.signal
741
+ });
742
+ } catch (error) {
743
+ if (!this.closed) this.fail(`the poll failed (${describe(error)})`, 1006);
744
+ return false;
745
+ }
746
+ if (this.closed) return false;
747
+ if (response.status === 410) {
748
+ this.fail(
749
+ "the platform no longer knows this instance, registering again",
750
+ SESSION_LOST_CLOSE_CODE
751
+ );
752
+ return false;
893
753
  }
754
+ const body = await this.jsonOf(response);
755
+ if (!response.ok) {
756
+ this.refusePoll({ status: response.status, body });
757
+ return false;
758
+ }
759
+ const delivered = this.deliverFrames(body);
760
+ for (const listener of this.pingListeners) listener();
761
+ if (delivered === 0) {
762
+ const elapsedMs = Date.now() - startedAt;
763
+ if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
764
+ }
765
+ return true;
766
+ }
767
+ refusePoll({
768
+ status,
769
+ body
770
+ }) {
771
+ const answered = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
772
+ if (answered) this.emitMessage(JSON.stringify(answered));
773
+ if (answered?.type === "refused") {
774
+ return;
775
+ }
776
+ this.fail(`the poll was answered with HTTP ${status}`, 1006);
777
+ }
778
+ /** Emits every frame a poll answered and returns how many there were. */
779
+ deliverFrames(body) {
780
+ const frames = Array.isArray(body?.frames) ? body.frames : [];
781
+ for (const frame of frames) {
782
+ const entry = frame;
783
+ if (entry.type === "cancel" && typeof entry.callId === "string")
784
+ this.inFlight.delete(entry.callId);
785
+ this.emitMessage(JSON.stringify(frame));
786
+ }
787
+ return frames.length;
894
788
  }
895
789
  async post(data) {
896
790
  if (!this.token) return;
@@ -908,12 +802,7 @@ var HttpLongPollSocket = class {
908
802
  if (this.frames.signal.aborted) return;
909
803
  response = null;
910
804
  }
911
- if (response?.ok) return;
912
- if (response?.status === 410) {
913
- this.fail("the platform no longer knows this instance, registering again", SESSION_LOST_CLOSE_CODE);
914
- return;
915
- }
916
- if (response && response.status < 500) return;
805
+ if (this.postSettled(response)) return;
917
806
  const delay = POST_RETRY_DELAYS_MS[attempt];
918
807
  if (delay === void 0) {
919
808
  this.fail(`a frame could not be posted after ${attempt} retries`, 1006);
@@ -922,6 +811,18 @@ var HttpLongPollSocket = class {
922
811
  await wait(delay);
923
812
  }
924
813
  }
814
+ /** Whether a post's response ends its retries: delivered, refused, or the session lost. */
815
+ postSettled(response) {
816
+ if (response?.ok) return true;
817
+ if (response?.status === 410) {
818
+ this.fail(
819
+ "the platform no longer knows this instance, registering again",
820
+ SESSION_LOST_CLOSE_CODE
821
+ );
822
+ return true;
823
+ }
824
+ return response !== null && response.status < 500;
825
+ }
925
826
  async jsonOf(response) {
926
827
  try {
927
828
  const parsed = await response.json();
@@ -975,19 +876,257 @@ function openTransportSocket({
975
876
  return transport === "http" ? new HttpLongPollSocket({ url: httpUrl, headers }) : socketFactory({ url: websocketUrl, headers });
976
877
  }
977
878
 
879
+ // src/agent/schema.ts
880
+ var AgentParameterError = class extends Error {
881
+ constructor(message) {
882
+ super(message);
883
+ this.code = "agent_parameter_invalid";
884
+ this.name = "AgentParameterError";
885
+ }
886
+ };
887
+ 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 })`;
888
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
889
+ var isStandardJsonSchema = (value) => {
890
+ if (!isRecord2(value)) return false;
891
+ const standard = value["~standard"];
892
+ return isRecord2(standard) && isRecord2(standard.jsonSchema);
893
+ };
894
+ var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
895
+ var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
896
+ var definitionType = (definition) => {
897
+ if (definition.type) return definition.type;
898
+ if (definition.options) return "string";
899
+ const value = definition.default;
900
+ if (typeof value === "number") return "number";
901
+ if (typeof value === "boolean") return "boolean";
902
+ return "string";
903
+ };
904
+ var definitionMapToSchema = (definitions) => {
905
+ const properties = {};
906
+ const required = [];
907
+ for (const [name, definition] of Object.entries(definitions)) {
908
+ const property = { type: definitionType(definition) };
909
+ if (definition.options) property.enum = [...definition.options];
910
+ if (definition.default !== void 0) property.default = definition.default;
911
+ else required.push(name);
912
+ if (definition.description !== void 0) property.description = definition.description;
913
+ properties[name] = property;
914
+ }
915
+ const schema = { type: "object", properties };
916
+ if (required.length > 0) schema.required = required;
917
+ return schema;
918
+ };
919
+ var readStandardJsonSchema = (input) => {
920
+ const converter = input["~standard"].jsonSchema;
921
+ const options = { target: "draft-2020-12" };
922
+ const schema = converter.input?.(options) ?? converter.output?.(options);
923
+ if (schema === void 0) {
924
+ throw new AgentParameterError(
925
+ `the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`
926
+ );
927
+ }
928
+ if (!isRecord2(schema)) {
929
+ throw new AgentParameterError(
930
+ `the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`
931
+ );
932
+ }
933
+ return schema;
934
+ };
935
+ function toParameterSchema(input) {
936
+ if (input === void 0) return { type: "object", properties: {} };
937
+ if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
938
+ if (isJsonSchemaObject(input)) return input;
939
+ if (isDefinitionMap(input)) return definitionMapToSchema(input);
940
+ throw new AgentParameterError(ACCEPTED_FORMS);
941
+ }
942
+ var specType = (property) => {
943
+ const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
944
+ if (type === "number" || type === "integer") return "number";
945
+ if (type === "boolean") return "boolean";
946
+ return "string";
947
+ };
948
+ var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
949
+ function parameterSpecsFromSchema(schema) {
950
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
951
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
952
+ const specs = [];
953
+ for (const [name, raw] of Object.entries(properties)) {
954
+ const property = isRecord2(raw) ? raw : {};
955
+ const spec = { name, type: specType(property) };
956
+ const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
957
+ if (options && options.length > 0) spec.options = options;
958
+ const fallback = scalar(property.default);
959
+ if (fallback !== void 0) spec.default = fallback;
960
+ if (typeof property.description === "string") spec.description = property.description;
961
+ spec.required = fallback === void 0 && required.has(name);
962
+ specs.push(spec);
963
+ }
964
+ return specs;
965
+ }
966
+ var coerce = ({
967
+ spec,
968
+ value
969
+ }) => {
970
+ if (spec.type === "number") {
971
+ return coerceNumber(spec.name, value);
972
+ }
973
+ if (spec.type === "boolean") {
974
+ if (typeof value === "boolean") return value;
975
+ if (value === "true") return true;
976
+ if (value === "false") return false;
977
+ throw new AgentParameterError(
978
+ `parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`
979
+ );
980
+ }
981
+ const asString = typeof value === "string" ? value : String(value);
982
+ if (spec.options && !spec.options.includes(asString)) {
983
+ throw new AgentParameterError(
984
+ `parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
985
+ );
986
+ }
987
+ return asString;
988
+ };
989
+ function resolveParameterValues({
990
+ specs,
991
+ supplied
992
+ }) {
993
+ const values = { ...supplied };
994
+ for (const spec of specs) {
995
+ const value = values[spec.name];
996
+ if (value === void 0) {
997
+ if (spec.default !== void 0) {
998
+ values[spec.name] = spec.default;
999
+ continue;
1000
+ }
1001
+ if (!spec.required) continue;
1002
+ throw new AgentParameterError(
1003
+ `parameter "${spec.name}" is required and the run did not supply it`
1004
+ );
1005
+ }
1006
+ values[spec.name] = coerce({ spec, value });
1007
+ }
1008
+ return values;
1009
+ }
1010
+ var issuePath = (issue) => (issue.path ?? []).map(
1011
+ (segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
1012
+ ).join(".");
1013
+ async function validateParameterValues({
1014
+ schema,
1015
+ values
1016
+ }) {
1017
+ const standard = schema["~standard"];
1018
+ const result = await standard.validate?.(values);
1019
+ if (result === void 0) return values;
1020
+ if (result.issues) {
1021
+ const detail = result.issues.map((issue) => {
1022
+ const path2 = issuePath(issue);
1023
+ return path2 ? `${path2}: ${issue.message}` : issue.message;
1024
+ }).join("; ");
1025
+ throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
1026
+ }
1027
+ const parsed = isRecord2(result.value) ? result.value : {};
1028
+ return { ...values, ...parsed };
1029
+ }
1030
+ function createParameterReader({
1031
+ input,
1032
+ specs
1033
+ }) {
1034
+ const schema = isStandardJsonSchema(input) ? input : void 0;
1035
+ return async (supplied) => {
1036
+ const values = resolveParameterValues({ specs, supplied });
1037
+ return schema ? validateParameterValues({ schema, values }) : values;
1038
+ };
1039
+ }
1040
+ function coerceNumber(name, value) {
1041
+ const asNumber = typeof value === "number" ? value : Number(value);
1042
+ if (typeof value === "boolean") {
1043
+ throw new AgentParameterError(
1044
+ `parameter "${name}" must be a number, got ${JSON.stringify(value)}`
1045
+ );
1046
+ }
1047
+ if (!Number.isFinite(asNumber)) {
1048
+ throw new AgentParameterError(
1049
+ `parameter "${name}" must be a number, got ${JSON.stringify(value)}`
1050
+ );
1051
+ }
1052
+ const stringValue2 = String(value).trim();
1053
+ if (stringValue2 === "") {
1054
+ throw new AgentParameterError(
1055
+ `parameter "${name}" must be a number, got ${JSON.stringify(value)}`
1056
+ );
1057
+ }
1058
+ return asNumber;
1059
+ }
1060
+
1061
+ // src/agent/state.ts
1062
+ var STATE_MAX_BYTES = 64 * 1024;
1063
+ var AgentStateError = class extends Error {
1064
+ constructor(code, message) {
1065
+ super(message);
1066
+ this.code = code;
1067
+ this.name = "AgentStateError";
1068
+ }
1069
+ };
1070
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1071
+ function checkState(value) {
1072
+ if (value === void 0 || value === null) return void 0;
1073
+ if (!isPlainObject(value)) {
1074
+ throw new AgentStateError(
1075
+ "agent_call_failed",
1076
+ `the state must be a JSON object, got ${Array.isArray(value) ? "an array" : typeof value}`
1077
+ );
1078
+ }
1079
+ let encoded;
1080
+ try {
1081
+ encoded = JSON.stringify(value);
1082
+ } catch (error) {
1083
+ throw new AgentStateError(
1084
+ "agent_call_failed",
1085
+ `the state is not JSON: ${error instanceof Error ? error.message : String(error)}`
1086
+ );
1087
+ }
1088
+ const sizeBytes = Buffer.byteLength(encoded, "utf8");
1089
+ if (sizeBytes > STATE_MAX_BYTES) {
1090
+ throw new AgentStateError(
1091
+ "agent_payload_too_large",
1092
+ `The state is ${sizeBytes} bytes, above the limit of ${STATE_MAX_BYTES} bytes.`
1093
+ );
1094
+ }
1095
+ return value;
1096
+ }
1097
+
978
1098
  // src/agent/client.ts
1099
+ var kindRefusal = (runtime, kind) => {
1100
+ if (kind === "turn") return null;
1101
+ if (kind !== "setup" && kind !== "teardown") {
1102
+ return { code: "agent_call_failed", message: `unknown call kind "${kind}"` };
1103
+ }
1104
+ if (runtime.hooks[kind]) return null;
1105
+ return {
1106
+ code: "hook_not_declared",
1107
+ message: `agent "${runtime.name}" declares no ${kind} hook`
1108
+ };
1109
+ };
1110
+ var errorCodeOf = (error) => error instanceof AgentParameterError || error instanceof AgentStateError ? error.code : "agent_call_failed";
1111
+ var answerCall = async ({
1112
+ runtime,
1113
+ frame,
1114
+ call
1115
+ }) => {
1116
+ const hook = frame.kind === "setup" || frame.kind === "teardown" ? runtime.hooks[frame.kind] : void 0;
1117
+ if (!hook) {
1118
+ const result = await runtime.run(call);
1119
+ return { ...result, state: checkState(result.state) };
1120
+ }
1121
+ const state = await hook({ run: frame.run, state: frame.state, params: call.params });
1122
+ return { state: checkState(frame.kind === "setup" ? state ?? {} : state) };
1123
+ };
979
1124
  var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
980
1125
  var NOT_CONNECTED = "not connected to LangWatch";
981
1126
  function refusalAdvice(frame) {
982
1127
  switch (frame.code) {
983
1128
  case "project_required": {
984
- const projects = Array.isArray(frame.meta?.projects) ? frame.meta.projects : [];
985
- const listed = projects.map((project) => {
986
- const entry = project;
987
- const id = typeof entry.id === "string" ? entry.id : "";
988
- const name = typeof entry.name === "string" ? entry.name : "";
989
- return name && id ? `${name} (${id})` : id || name;
990
- }).filter((line) => line !== "");
1129
+ const listed = projectNames(frame.meta?.projects);
991
1130
  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"}.`;
992
1131
  }
993
1132
  case "api_key_invalid":
@@ -1007,8 +1146,30 @@ function refusalAdvice(frame) {
1007
1146
  return `${frame.message} (${frame.code})`;
1008
1147
  }
1009
1148
  }
1149
+ function projectNames(value) {
1150
+ if (!Array.isArray(value)) return [];
1151
+ return value.map(projectName).filter((name) => name !== "");
1152
+ }
1153
+ function projectName(value) {
1154
+ if (!isRecord3(value)) return "";
1155
+ const id = stringValue(value.id);
1156
+ const name = stringValue(value.name);
1157
+ if (name && id) return `${name} (${id})`;
1158
+ return id || name;
1159
+ }
1160
+ function isRecord3(value) {
1161
+ return typeof value === "object" && value !== null;
1162
+ }
1163
+ function stringValue(value) {
1164
+ return typeof value === "string" ? value : "";
1165
+ }
1010
1166
  var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
1011
1167
  var CLOSE_GRACE_MS = 500;
1168
+ var readCallTraceContext = (frame) => {
1169
+ const parent = frame.traceparent ? propagation.extract(context.active(), { traceparent: frame.traceparent }) : context.active();
1170
+ const traceId = trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
1171
+ return { parent, traceId };
1172
+ };
1012
1173
  var AgentClient = class {
1013
1174
  constructor(config) {
1014
1175
  this.agents = [];
@@ -1049,7 +1210,7 @@ var AgentClient = class {
1049
1210
  get isRegistered() {
1050
1211
  return this.registered;
1051
1212
  }
1052
- /** True once the client gave up: refused, or no socket implementation. No timer is left behind. */
1213
+ /** True once the client gave up: refused, or no socket implementation available. */
1053
1214
  get isStopped() {
1054
1215
  return this.stopped;
1055
1216
  }
@@ -1066,7 +1227,9 @@ var AgentClient = class {
1066
1227
  addAgent(runtime) {
1067
1228
  this.agents.push(runtime);
1068
1229
  if (this.gaveUp) {
1069
- this.logger.debug(`agent "${runtime.name}" ${NOT_CONNECTED}: the connection gave up earlier in this process`);
1230
+ this.logger.debug(
1231
+ `agent "${runtime.name}" ${NOT_CONNECTED}: the connection gave up earlier in this process`
1232
+ );
1070
1233
  return;
1071
1234
  }
1072
1235
  this.stopped = false;
@@ -1076,6 +1239,10 @@ var AgentClient = class {
1076
1239
  }
1077
1240
  this.scheduleConnect(0);
1078
1241
  }
1242
+ /** Registers the agents again after one changed: a hook declared once the socket opened. */
1243
+ refreshAgents() {
1244
+ if (this.socket) this.restartSocket();
1245
+ }
1079
1246
  /** Closes the socket and connects again at once, keeping the reconnect loop. */
1080
1247
  restartSocket() {
1081
1248
  const socket = this.socket;
@@ -1235,7 +1402,10 @@ var AgentClient = class {
1235
1402
  * connection is lost, and silence while the retries run: the same notice
1236
1403
  * repeats only after the notice interval, and a reconnect resets it.
1237
1404
  */
1238
- noteDisconnected({ wasRegistered, code }) {
1405
+ noteDisconnected({
1406
+ wasRegistered,
1407
+ code
1408
+ }) {
1239
1409
  const now = Date.now();
1240
1410
  if (wasRegistered) {
1241
1411
  this.logger.warn(
@@ -1328,7 +1498,8 @@ var AgentClient = class {
1328
1498
  this.logger.info(`connected to LangWatch over HTTP long polling at ${this.httpUrl}`);
1329
1499
  }
1330
1500
  this.heartbeatIntervalMs = frame.heartbeatIntervalMs;
1331
- if (frame.instanceId && frame.instanceId !== this.instance.id) this.instance.id = frame.instanceId;
1501
+ if (frame.instanceId && frame.instanceId !== this.instance.id)
1502
+ this.instance.id = frame.instanceId;
1332
1503
  this.byId.clear();
1333
1504
  for (const entry of frame.agents) {
1334
1505
  const runtime = this.agents.find(
@@ -1355,21 +1526,9 @@ var AgentClient = class {
1355
1526
  });
1356
1527
  return;
1357
1528
  }
1358
- const busy = [...this.inFlight.values()].filter((call2) => call2.runtime === runtime).length;
1359
- if (busy >= runtime.concurrency) {
1360
- this.sendError({
1361
- callId: frame.callId,
1362
- code: "agent_busy",
1363
- message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
1364
- });
1365
- return;
1366
- }
1367
- if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
1368
- this.sendError({
1369
- callId: frame.callId,
1370
- code: "agent_call_timeout",
1371
- message: "the call deadline passed before it started"
1372
- });
1529
+ const refusal = this.callRefusal({ frame, runtime });
1530
+ if (refusal) {
1531
+ this.sendError({ callId: frame.callId, ...refusal });
1373
1532
  return;
1374
1533
  }
1375
1534
  const entry = { runtime, cancelled: false, timer: null };
@@ -1392,18 +1551,18 @@ var AgentClient = class {
1392
1551
  }
1393
1552
  this.send({ type: "ack", protocol: PROTOCOL_VERSION, callId: frame.callId });
1394
1553
  this.armCallDeadline({ frame, entry, runtime });
1395
- const parent = frame.traceparent ? propagation.extract(context.active(), { traceparent: frame.traceparent }) : context.active();
1396
- const traceId = trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
1554
+ const { parent, traceId } = readCallTraceContext(frame);
1397
1555
  const call = {
1398
1556
  messages: frame.messages,
1399
1557
  newMessages: frame.newMessages,
1400
1558
  threadId: frame.threadId,
1401
1559
  session: frame.session,
1560
+ state: frame.state,
1402
1561
  params,
1403
1562
  traceId
1404
1563
  };
1405
1564
  try {
1406
- const result = await context.with(parent, () => runtime.run(call));
1565
+ const result = await context.with(parent, () => answerCall({ runtime, frame, call }));
1407
1566
  if (entry.cancelled) return;
1408
1567
  this.releaseCall({ callId: frame.callId, entry });
1409
1568
  await this.flushSpans();
@@ -1411,12 +1570,13 @@ var AgentClient = class {
1411
1570
  type: "result",
1412
1571
  protocol: PROTOCOL_VERSION,
1413
1572
  callId: frame.callId,
1414
- output: result.output,
1415
- ...result.session === void 0 ? {} : { session: result.session }
1573
+ ...result.output === void 0 ? {} : { output: result.output },
1574
+ ...result.session === void 0 ? {} : { session: result.session },
1575
+ ...result.state === void 0 ? {} : { state: result.state }
1416
1576
  });
1417
1577
  } catch (error) {
1418
1578
  if (entry.cancelled) return;
1419
- const code = error instanceof AgentParameterError ? error.code : "agent_call_failed";
1579
+ const code = errorCodeOf(error);
1420
1580
  const message = describeError(error);
1421
1581
  this.logger.warn(`agent "${runtime.name}" call ${frame.callId} failed: ${message}`);
1422
1582
  this.releaseCall({ callId: frame.callId, entry });
@@ -1426,15 +1586,32 @@ var AgentClient = class {
1426
1586
  this.releaseCall({ callId: frame.callId, entry });
1427
1587
  }
1428
1588
  }
1589
+ /** Why the runtime cannot take this call now, or null when it can. */
1590
+ callRefusal({
1591
+ frame,
1592
+ runtime
1593
+ }) {
1594
+ const kind = kindRefusal(runtime, frame.kind);
1595
+ if (kind) return kind;
1596
+ const busy = [...this.inFlight.values()].filter((call) => call.runtime === runtime).length;
1597
+ if (busy >= runtime.concurrency) {
1598
+ return {
1599
+ code: "agent_busy",
1600
+ message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
1601
+ };
1602
+ }
1603
+ if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
1604
+ return {
1605
+ code: "agent_call_timeout",
1606
+ message: "the call deadline passed before it started"
1607
+ };
1608
+ }
1609
+ return null;
1610
+ }
1429
1611
  /**
1430
- * Exports the spans of the call now instead of at the exporter's next
1431
- * schedule. The judge reads the agent's spans right after the last turn,
1432
- * and a batch exporter would otherwise hold them for seconds, which is what
1433
- * made the judge report the spans missing.
1434
- *
1435
- * The call awaits this before it sends its result or its error: the frame is
1436
- * what tells the platform the turn is over, so a frame that goes out first
1437
- * lets the judge read the call while its spans are still in the exporter.
1612
+ * Exports the spans of the call now, since a batch exporter would
1613
+ * otherwise hold them for seconds after the judge reads them. Awaited
1614
+ * before the result or error frame goes out.
1438
1615
  */
1439
1616
  async flushSpans() {
1440
1617
  const provider = trace.getTracerProvider();
@@ -1466,26 +1643,33 @@ var AgentClient = class {
1466
1643
  runtime
1467
1644
  }) {
1468
1645
  const fromDeadline = frame.deadlineAt === null ? Infinity : frame.deadlineAt - Date.now();
1469
- const limit = Math.min(fromDeadline, runtime.timeoutMs);
1646
+ const limit = frame.kind !== "turn" && frame.deadlineAt !== null ? fromDeadline : Math.min(fromDeadline, runtime.timeoutMs);
1470
1647
  if (!Number.isFinite(limit)) return;
1471
- const timer = setTimeout(() => {
1472
- entry.timer = null;
1473
- if (entry.cancelled) return;
1474
- entry.cancelled = true;
1475
- this.releaseCall({ callId: frame.callId, entry });
1476
- this.logger.warn(
1477
- `agent "${runtime.name}" call ${frame.callId} passed its ${limit} ms limit`
1478
- );
1479
- this.sendError({
1480
- callId: frame.callId,
1481
- code: "agent_call_timeout",
1482
- message: `the call passed the ${limit} ms limit of agent "${runtime.name}"`
1483
- });
1484
- }, Math.max(0, limit));
1648
+ const timer = setTimeout(
1649
+ () => {
1650
+ entry.timer = null;
1651
+ if (entry.cancelled) return;
1652
+ entry.cancelled = true;
1653
+ this.releaseCall({ callId: frame.callId, entry });
1654
+ this.logger.warn(
1655
+ `agent "${runtime.name}" call ${frame.callId} passed its ${limit} ms limit`
1656
+ );
1657
+ this.sendError({
1658
+ callId: frame.callId,
1659
+ code: "agent_call_timeout",
1660
+ message: `the call passed the ${limit} ms limit of agent "${runtime.name}"`
1661
+ });
1662
+ },
1663
+ Math.max(0, limit)
1664
+ );
1485
1665
  timer.unref();
1486
1666
  entry.timer = timer;
1487
1667
  }
1488
- sendError({ callId, code, message }) {
1668
+ sendError({
1669
+ callId,
1670
+ code,
1671
+ message
1672
+ }) {
1489
1673
  this.send({ type: "result", protocol: PROTOCOL_VERSION, callId, error: { code, message } });
1490
1674
  }
1491
1675
  guard(action) {
@@ -1528,7 +1712,11 @@ var installShutdownHooks = () => {
1528
1712
  }
1529
1713
  process.on("beforeExit", onBeforeExit);
1530
1714
  };
1531
- function warnOnce2({ logger, key, message }) {
1715
+ function warnOnce2({
1716
+ logger,
1717
+ key,
1718
+ message
1719
+ }) {
1532
1720
  if (noticesGiven.has(key)) {
1533
1721
  logger.debug(message);
1534
1722
  return;
@@ -1571,22 +1759,33 @@ var DEFAULT_CONCURRENCY = 10;
1571
1759
  var isMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.role === "string";
1572
1760
  function normalizeReply(reply) {
1573
1761
  if (typeof reply === "string") return { output: reply };
1574
- if (Array.isArray(reply) && reply.every(isMessage)) return { output: reply };
1762
+ if (Array.isArray(reply)) {
1763
+ if (reply.every(isMessage)) return { output: reply };
1764
+ }
1575
1765
  if (isMessage(reply)) return { output: reply };
1576
1766
  if (typeof reply === "object" && reply !== null && "output" in reply) {
1577
- const { output, session } = reply;
1578
- const normalized = normalizeReply(output);
1579
- return session === void 0 ? normalized : { output: normalized.output, session };
1767
+ const { output, session, state } = reply;
1768
+ return {
1769
+ output: normalizeReply(output).output,
1770
+ ...session === void 0 ? {} : { session },
1771
+ ...state === void 0 ? {} : { state }
1772
+ };
1580
1773
  }
1581
1774
  throw new Error(
1582
- "the agent handler must return a string, a message, a list of messages, or { output, session }"
1775
+ "the agent handler must return a string, a message, a list of messages, or { output, session, state }"
1583
1776
  );
1584
1777
  }
1585
- var clampTimeout = ({ timeoutMs, logger, name }) => {
1778
+ var clampTimeout = ({
1779
+ timeoutMs,
1780
+ logger,
1781
+ name
1782
+ }) => {
1586
1783
  if (timeoutMs === void 0) return DEFAULT_TIMEOUT_MS;
1587
1784
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return DEFAULT_TIMEOUT_MS;
1588
1785
  if (timeoutMs > MAX_TIMEOUT_MS) {
1589
- logger.warn(`agent "${name}": timeoutMs ${timeoutMs} is above the ${MAX_TIMEOUT_MS} cap, using the cap`);
1786
+ logger.warn(
1787
+ `agent "${name}": timeoutMs ${timeoutMs} is above the ${MAX_TIMEOUT_MS} cap, using the cap`
1788
+ );
1590
1789
  return MAX_TIMEOUT_MS;
1591
1790
  }
1592
1791
  return Math.floor(timeoutMs);
@@ -1617,10 +1816,12 @@ function connectAgent(options, handler) {
1617
1816
  newMessages: call.newMessages ?? call.messages,
1618
1817
  threadId: call.threadId ?? `local_${Date.now().toString(36)}`,
1619
1818
  session: call.session ?? null,
1819
+ state: call.state ?? null,
1620
1820
  params,
1621
1821
  traceId: call.traceId ?? ""
1622
1822
  });
1623
1823
  };
1824
+ const hooks = {};
1624
1825
  const runtime = {
1625
1826
  name,
1626
1827
  environment,
@@ -1630,14 +1831,29 @@ function connectAgent(options, handler) {
1630
1831
  parameters,
1631
1832
  concurrency,
1632
1833
  timeoutMs,
1633
- ...options.sticky ? { sticky: true } : {}
1834
+ ...options.sticky ? { sticky: true } : {},
1835
+ hooks: { setup: false, teardown: false }
1634
1836
  },
1635
1837
  readParams: readParams2,
1636
1838
  concurrency,
1637
1839
  timeoutMs,
1638
- run: runHandler
1840
+ run: runHandler,
1841
+ hooks
1639
1842
  };
1640
1843
  let detach;
1844
+ let refresh;
1845
+ const scenario = {
1846
+ setup: (hook) => {
1847
+ hooks.setup = async ({ run, params }) => hook(run, params);
1848
+ runtime.register.hooks = { setup: true, teardown: Boolean(hooks.teardown) };
1849
+ refresh?.();
1850
+ },
1851
+ teardown: (hook) => {
1852
+ hooks.teardown = async ({ run, state, params }) => hook(run, state, params);
1853
+ runtime.register.hooks = { setup: Boolean(hooks.setup), teardown: true };
1854
+ refresh?.();
1855
+ }
1856
+ };
1641
1857
  if (!resolveEnabled({ explicit: options.enabled })) {
1642
1858
  logger.debug(`agent "${name}" not connected to LangWatch: the connection is disabled`);
1643
1859
  } else {
@@ -1659,15 +1875,18 @@ function connectAgent(options, handler) {
1659
1875
  });
1660
1876
  client.addAgent(runtime);
1661
1877
  detach = () => client.removeAgent(runtime);
1878
+ refresh = () => client.refreshAgents();
1662
1879
  }
1663
1880
  }
1664
1881
  const connected = Object.assign(invoke, {
1665
1882
  environment,
1666
1883
  parameters,
1884
+ scenario,
1667
1885
  disconnect: async () => {
1668
1886
  if (!detach) return;
1669
1887
  const release = detach;
1670
1888
  detach = void 0;
1889
+ refresh = void 0;
1671
1890
  await release();
1672
1891
  }
1673
1892
  });
@@ -1677,9 +1896,11 @@ function connectAgent(options, handler) {
1677
1896
  export {
1678
1897
  AGENT_TRANSPORTS,
1679
1898
  AgentParameterError,
1899
+ AgentStateError,
1680
1900
  DEFAULT_TIMEOUT_MS,
1681
1901
  MAX_TIMEOUT_MS,
1682
1902
  PROTOCOL_VERSION,
1903
+ STATE_MAX_BYTES,
1683
1904
  connectAgent,
1684
1905
  normalizeReply,
1685
1906
  parameterSpecsFromSchema,