langwatch 1.12.1 → 1.14.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 (41) hide show
  1. package/dist/agent/index.d.mts +5 -1
  2. package/dist/agent/index.d.ts +5 -1
  3. package/dist/agent/index.js +239 -14
  4. package/dist/agent/index.js.map +1 -1
  5. package/dist/agent/index.mjs +239 -14
  6. package/dist/agent/index.mjs.map +1 -1
  7. package/dist/{chunk-H2DJGGYQ.js → chunk-7FIG44IG.js} +12 -12
  8. package/dist/{chunk-H2DJGGYQ.js.map → chunk-7FIG44IG.js.map} +1 -1
  9. package/dist/{chunk-53NOLJEX.mjs → chunk-MCM4C66E.mjs} +2 -2
  10. package/dist/{chunk-OSVJ65OO.js → chunk-MGASIVXD.js} +2 -2
  11. package/dist/{chunk-OSVJ65OO.js.map → chunk-MGASIVXD.js.map} +1 -1
  12. package/dist/{chunk-2HA4SJSD.mjs → chunk-NPEK4RSD.mjs} +5 -2
  13. package/dist/{chunk-2HA4SJSD.mjs.map → chunk-NPEK4RSD.mjs.map} +1 -1
  14. package/dist/{chunk-4ERRCKOH.js → chunk-O4J3ZPZY.js} +5 -2
  15. package/dist/chunk-O4J3ZPZY.js.map +1 -0
  16. package/dist/{chunk-IVRETMQH.mjs → chunk-QKIDVFHZ.mjs} +2 -2
  17. package/dist/{chunk-IVRETMQH.mjs.map → chunk-QKIDVFHZ.mjs.map} +1 -1
  18. package/dist/cli/bundle.js +348 -795
  19. package/dist/{implementation-DLMEZQgA.d.mts → implementation-BXX0qdV7.d.mts} +1 -1
  20. package/dist/{implementation-BNaydX6f.d.ts → implementation-Cv7sBdwj.d.ts} +1 -1
  21. package/dist/index.d.mts +75 -5
  22. package/dist/index.d.ts +75 -5
  23. package/dist/index.js +324 -99
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +242 -17
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/observability-sdk/index.d.mts +3 -3
  28. package/dist/observability-sdk/index.d.ts +3 -3
  29. package/dist/observability-sdk/index.js +3 -3
  30. package/dist/observability-sdk/index.mjs +2 -2
  31. package/dist/observability-sdk/instrumentation/langchain/index.d.mts +1 -1
  32. package/dist/observability-sdk/instrumentation/langchain/index.d.ts +1 -1
  33. package/dist/observability-sdk/instrumentation/langchain/index.js +3 -3
  34. package/dist/observability-sdk/instrumentation/langchain/index.mjs +1 -1
  35. package/dist/observability-sdk/setup/node/index.js +3 -3
  36. package/dist/observability-sdk/setup/node/index.mjs +2 -2
  37. package/dist/{types-CLDQMYpV.d.ts → types-BejNp4Dw.d.ts} +2525 -565
  38. package/dist/{types-DWNdXOWw.d.mts → types-DXNIJdZx.d.mts} +2525 -565
  39. package/package.json +1 -1
  40. package/dist/chunk-4ERRCKOH.js.map +0 -1
  41. /package/dist/{chunk-53NOLJEX.mjs.map → chunk-MCM4C66E.mjs.map} +0 -0
@@ -221,7 +221,11 @@ interface ConnectAgentOptions<P extends ParameterInput = ParameterDefinitions> {
221
221
  instanceLabel?: string;
222
222
  /** Per call, default 120000, at most 300000. */
223
223
  timeoutMs?: number;
224
- /** Calls in flight per instance, default 1 in development and 4 elsewhere. */
224
+ /**
225
+ * Calls in flight per instance, default 10. A test suite sends several
226
+ * scenarios at once; the ceiling is there because the model providers rate
227
+ * limit the calls behind them.
228
+ */
225
229
  concurrency?: number;
226
230
  /** Keep every turn of a thread on the instance that answered the first one. */
227
231
  sticky?: boolean;
@@ -221,7 +221,11 @@ interface ConnectAgentOptions<P extends ParameterInput = ParameterDefinitions> {
221
221
  instanceLabel?: string;
222
222
  /** Per call, default 120000, at most 300000. */
223
223
  timeoutMs?: number;
224
- /** Calls in flight per instance, default 1 in development and 4 elsewhere. */
224
+ /**
225
+ * Calls in flight per instance, default 10. A test suite sends several
226
+ * scenarios at once; the ceiling is there because the model providers rate
227
+ * limit the calls behind them.
228
+ */
225
229
  concurrency?: number;
226
230
  /** Keep every turn of a thread on the instance that answered the first one. */
227
231
  sticky?: boolean;
@@ -87,7 +87,7 @@ var os = __toESM(require("os"));
87
87
  var import_node_crypto = require("crypto");
88
88
 
89
89
  // package.json
90
- var version = "1.12.1";
90
+ var version = "1.14.0";
91
91
 
92
92
  // src/internal/constants.ts
93
93
  var LANGWATCH_SDK_VERSION = version;
@@ -492,6 +492,213 @@ function createParameterReader({
492
492
 
493
493
  // src/agent/transport.ts
494
494
  var import_node_module = require("module");
495
+
496
+ // src/internal/http/langwatchFetch.ts
497
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
498
+ var FOLLOWING_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
499
+ var MAX_FOLLOW_HOPS = 5;
500
+ var CREDENTIAL_HEADERS = [
501
+ "authorization",
502
+ "cookie",
503
+ "proxy-authorization",
504
+ "x-api-key",
505
+ "x-auth-token",
506
+ "x-project-id"
507
+ ];
508
+ var LangWatchRedirectError = class extends Error {
509
+ constructor({
510
+ url,
511
+ location,
512
+ status
513
+ }) {
514
+ super(
515
+ `LangWatch refused to follow a redirect from ${url} to ${location ?? "an unknown location"} (HTTP ${status}). Set the endpoint to the final URL.`
516
+ );
517
+ this.name = "LangWatchRedirectError";
518
+ this.url = url;
519
+ this.location = location;
520
+ this.status = status;
521
+ }
522
+ };
523
+ var warnedOrigins = /* @__PURE__ */ new Set();
524
+ var isRedirect = (response) => response.type === "opaqueredirect" || REDIRECT_STATUSES.has(response.status);
525
+ var isStream = (body) => typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
526
+ var abortError = (signal) => {
527
+ const reason = signal.reason;
528
+ if (reason instanceof Error) return reason;
529
+ if (typeof DOMException !== "undefined") {
530
+ return new DOMException("This operation was aborted", "AbortError");
531
+ }
532
+ return Object.assign(new Error("This operation was aborted"), { name: "AbortError" });
533
+ };
534
+ var discard = (spare) => {
535
+ void spare?.body?.cancel().catch(() => void 0);
536
+ };
537
+ var replayBody = async ({
538
+ spare,
539
+ signal
540
+ }) => {
541
+ if (!signal) return spare.arrayBuffer();
542
+ if (signal.aborted) throw abortError(signal);
543
+ const aborted = new Promise((_, reject) => {
544
+ signal.addEventListener("abort", () => reject(abortError(signal)), { once: true });
545
+ });
546
+ void aborted.catch(() => void 0);
547
+ try {
548
+ return await Promise.race([spare.arrayBuffer(), aborted]);
549
+ } catch (error) {
550
+ discard(spare);
551
+ throw error;
552
+ }
553
+ };
554
+ var requestUrl = (input) => {
555
+ if (typeof input === "string") return input;
556
+ if (input instanceof URL) return input.href;
557
+ return input.url;
558
+ };
559
+ var parseHop = ({
560
+ url,
561
+ location
562
+ }) => {
563
+ try {
564
+ const from = new URL(url);
565
+ return { from, to: new URL(location, from) };
566
+ } catch {
567
+ return null;
568
+ }
569
+ };
570
+ var isSchemeUpgrade = ({ from, to }) => from.protocol === "http:" && to.protocol === "https:" && from.hostname === to.hostname && from.port === to.port;
571
+ var schemeUpgradeTarget = ({
572
+ url,
573
+ location
574
+ }) => {
575
+ const hop = parseHop({ url, location });
576
+ if (hop === null || !isSchemeUpgrade(hop)) return null;
577
+ const { from, to } = hop;
578
+ if (from.pathname !== to.pathname) return null;
579
+ if (from.search !== to.search) return null;
580
+ to.hash = "";
581
+ return to.href;
582
+ };
583
+ var followTarget = ({
584
+ url,
585
+ location
586
+ }) => {
587
+ const hop = parseHop({ url, location });
588
+ if (hop === null) return null;
589
+ const { from, to } = hop;
590
+ if (to.protocol !== "http:" && to.protocol !== "https:") return null;
591
+ if (from.protocol === "https:" && to.protocol === "http:") return null;
592
+ to.hash = "";
593
+ return to.href;
594
+ };
595
+ var keepsCredentials = ({ from, to }) => from.origin === to.origin || isSchemeUpgrade({ from, to });
596
+ var withoutCredentials = (headers) => {
597
+ const stripped = new Headers(headers);
598
+ for (const name of CREDENTIAL_HEADERS) stripped.delete(name);
599
+ return stripped;
600
+ };
601
+ var warnOnce = ({ url, logger }) => {
602
+ const { origin, host } = new URL(url);
603
+ if (warnedOrigins.has(origin)) return;
604
+ warnedOrigins.add(origin);
605
+ logger.warn(
606
+ `LangWatch endpoint ${origin} redirected to https. Set the endpoint to https://${host} to skip the extra round trip.`
607
+ );
608
+ };
609
+ var refusalOf = ({
610
+ url,
611
+ response
612
+ }) => new LangWatchRedirectError({
613
+ url,
614
+ location: response.headers.get("location"),
615
+ status: response.status
616
+ });
617
+ var effectiveRequest = ({
618
+ input,
619
+ init
620
+ }) => typeof Request !== "undefined" && input instanceof Request ? new Request(input, { ...init, redirect: "manual" }) : null;
621
+ var follow = async ({
622
+ send,
623
+ log,
624
+ effective,
625
+ init,
626
+ url,
627
+ method,
628
+ first
629
+ }) => {
630
+ let headers = new Headers(effective?.headers ?? init?.headers);
631
+ const signal = effective?.signal ?? init?.signal;
632
+ let current = url;
633
+ let response = first;
634
+ for (let hop = 0; hop < MAX_FOLLOW_HOPS; hop++) {
635
+ const location = response.headers.get("location");
636
+ const target = location === null ? null : followTarget({ url: current, location });
637
+ if (target === null) throw refusalOf({ url: current, response });
638
+ const from = new URL(current);
639
+ const to = new URL(target);
640
+ if (!keepsCredentials({ from, to })) headers = withoutCredentials(headers);
641
+ if (isSchemeUpgrade({ from, to })) warnOnce({ url: current, logger: log });
642
+ current = target;
643
+ response = effective ? await send(new Request(target, { method, headers, signal, redirect: "manual" })) : await send(target, { ...init, method, headers, body: void 0, redirect: "manual" });
644
+ if (!isRedirect(response)) return response;
645
+ }
646
+ throw refusalOf({ url: current, response });
647
+ };
648
+ var upgrade = async ({
649
+ send,
650
+ log,
651
+ effective,
652
+ init,
653
+ url,
654
+ first,
655
+ spare
656
+ }) => {
657
+ const location = first.headers.get("location");
658
+ const refused = refusalOf({ url, response: first });
659
+ const target = location === null ? null : schemeUpgradeTarget({ url, location });
660
+ if (location === null || first.status === 303 || target === null || isStream(init?.body)) {
661
+ discard(spare);
662
+ throw refused;
663
+ }
664
+ warnOnce({ url, logger: log });
665
+ const second = effective ? await send(
666
+ new Request(target, {
667
+ method: effective.method,
668
+ headers: effective.headers,
669
+ body: spare ? await replayBody({ spare, signal: effective.signal }) : null,
670
+ signal: effective.signal,
671
+ redirect: "manual"
672
+ })
673
+ ) : await send(target, { ...init, redirect: "manual" });
674
+ if (!isRedirect(second)) return second;
675
+ throw refusalOf({ url: target, response: second });
676
+ };
677
+ var createLangWatchFetch = ({
678
+ fetch: fetchImpl,
679
+ logger
680
+ } = {}) => {
681
+ const send = fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
682
+ const log = logger ?? new ConsoleLogger({ level: "warn", prefix: "LangWatch" });
683
+ return async (input, init) => {
684
+ const url = requestUrl(input);
685
+ const effective = effectiveRequest({ input, init });
686
+ const method = (effective?.method ?? init?.method ?? "GET").toUpperCase();
687
+ const spare = effective !== null && effective.body !== null ? effective.clone() : null;
688
+ const first = effective ? await send(effective) : await send(input, { ...init, redirect: "manual" });
689
+ if (!isRedirect(first)) {
690
+ discard(spare);
691
+ return first;
692
+ }
693
+ const hop = { send, log, effective, init, url, first };
694
+ if (!FOLLOWING_METHODS.has(method)) return upgrade({ ...hop, spare });
695
+ discard(spare);
696
+ return follow({ ...hop, method });
697
+ };
698
+ };
699
+ var langwatchFetch = createLangWatchFetch();
700
+
701
+ // src/agent/transport.ts
495
702
  var AGENT_TRANSPORTS = ["websocket", "http"];
496
703
  var INSTANCE_TOKEN_HEADER = "X-Agent-Instance-Token";
497
704
  var isSet3 = (value) => typeof value === "string" && value.trim() !== "";
@@ -576,7 +783,7 @@ var HttpLongPollSocket = class {
576
783
  this.settleRegistered = () => void 0;
577
784
  this.url = options.url;
578
785
  this.headers = options.headers;
579
- const fetchImpl = options.fetch ?? globalThis.fetch;
786
+ const fetchImpl = options.fetch ?? (typeof globalThis.fetch === "function" ? langwatchFetch : void 0);
580
787
  if (typeof fetchImpl !== "function") {
581
788
  throw new Error("the HTTP transport needs a global fetch; run on Node 20 or later");
582
789
  }
@@ -777,10 +984,9 @@ var HttpLongPollSocket = class {
777
984
  }
778
985
  };
779
986
 
780
- // src/agent/client.ts
987
+ // src/agent/reconnect.ts
781
988
  var RECONNECT_BASE_MS = 1e3;
782
989
  var RECONNECT_MAX_MS = 3e4;
783
- var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
784
990
  function reconnectDelayMs({
785
991
  attempt,
786
992
  baseMs = RECONNECT_BASE_MS,
@@ -791,6 +997,22 @@ function reconnectDelayMs({
791
997
  const jittered = exponential * (0.75 + random() * 0.5);
792
998
  return Math.round(Math.min(maxMs, Math.max(baseMs, jittered)));
793
999
  }
1000
+ function watchdogDelayMs(heartbeatIntervalMs) {
1001
+ return Math.max(15e3, heartbeatIntervalMs * 3);
1002
+ }
1003
+ var describeError = (error) => error instanceof Error ? error.message : String(error);
1004
+ function openTransportSocket({
1005
+ transport,
1006
+ websocketUrl,
1007
+ httpUrl,
1008
+ headers,
1009
+ socketFactory = defaultSocketFactory
1010
+ }) {
1011
+ return transport === "http" ? new HttpLongPollSocket({ url: httpUrl, headers }) : socketFactory({ url: websocketUrl, headers });
1012
+ }
1013
+
1014
+ // src/agent/client.ts
1015
+ var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
794
1016
  var NOT_CONNECTED = "not connected to LangWatch";
795
1017
  function refusalAdvice(frame) {
796
1018
  switch (frame.code) {
@@ -823,7 +1045,6 @@ function refusalAdvice(frame) {
823
1045
  }
824
1046
  var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
825
1047
  var CLOSE_GRACE_MS = 500;
826
- var describeError = (error) => error instanceof Error ? error.message : String(error);
827
1048
  var AgentClient = class {
828
1049
  constructor(config) {
829
1050
  this.agents = [];
@@ -984,7 +1205,13 @@ var AgentClient = class {
984
1205
  if (this.stopped || this.socket) return;
985
1206
  let socket;
986
1207
  try {
987
- socket = this.activeTransport === "http" ? new HttpLongPollSocket({ url: this.httpUrl, headers: this.headers }) : this.openSocket({ url: this.url, headers: this.headers });
1208
+ socket = openTransportSocket({
1209
+ transport: this.activeTransport,
1210
+ websocketUrl: this.url,
1211
+ httpUrl: this.httpUrl,
1212
+ headers: this.headers,
1213
+ socketFactory: this.openSocket
1214
+ });
988
1215
  } catch (error) {
989
1216
  if (error instanceof NoWebSocketError) {
990
1217
  this.giveUp(
@@ -1072,7 +1299,7 @@ var AgentClient = class {
1072
1299
  socket.terminate();
1073
1300
  } catch {
1074
1301
  }
1075
- }, Math.max(15e3, this.heartbeatIntervalMs * 3));
1302
+ }, watchdogDelayMs(this.heartbeatIntervalMs));
1076
1303
  }
1077
1304
  registerFrame() {
1078
1305
  return {
@@ -1335,7 +1562,7 @@ var installShutdownHooks = () => {
1335
1562
  }
1336
1563
  process.on("beforeExit", onBeforeExit);
1337
1564
  };
1338
- function warnOnce({ logger, key, message }) {
1565
+ function warnOnce2({ logger, key, message }) {
1339
1566
  if (noticesGiven.has(key)) {
1340
1567
  logger.debug(message);
1341
1568
  return;
@@ -1347,7 +1574,7 @@ function getSharedClient(config) {
1347
1574
  const key = `${config.endpoint ?? ""}|${config.projectId ?? ""}|${config.apiKey}`;
1348
1575
  if (shared) {
1349
1576
  if (sharedKey !== key) {
1350
- warnOnce({
1577
+ warnOnce2({
1351
1578
  logger: config.logger,
1352
1579
  key: "credentials-differ",
1353
1580
  message: "connectAgent: this process already has an agent connection with other credentials or endpoint; the first ones are used"
@@ -1364,6 +1591,7 @@ function getSharedClient(config) {
1364
1591
  // src/agent/define.ts
1365
1592
  var DEFAULT_TIMEOUT_MS = 12e4;
1366
1593
  var MAX_TIMEOUT_MS = 3e5;
1594
+ var DEFAULT_CONCURRENCY = 10;
1367
1595
  var isMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.role === "string";
1368
1596
  function normalizeReply(reply) {
1369
1597
  if (typeof reply === "string") return { output: reply };
@@ -1403,10 +1631,7 @@ function connectAgent(options, handler) {
1403
1631
  const parameters = toParameterSchema(options.parameters);
1404
1632
  const specs = parameterSpecsFromSchema(parameters);
1405
1633
  const timeoutMs = clampTimeout({ timeoutMs: options.timeoutMs, logger, name });
1406
- const concurrency = Math.max(
1407
- 1,
1408
- Math.floor(options.concurrency ?? (environment === DEFAULT_ENVIRONMENT ? 1 : 4))
1409
- );
1634
+ const concurrency = Math.max(1, Math.floor(options.concurrency ?? DEFAULT_CONCURRENCY));
1410
1635
  const runHandler = async (call) => normalizeReply(await handler(call));
1411
1636
  const readParams2 = createParameterReader({ input: options.parameters, specs });
1412
1637
  const invoke = async (call) => {
@@ -1442,7 +1667,7 @@ function connectAgent(options, handler) {
1442
1667
  } else {
1443
1668
  const apiKey = readApiKey(options.apiKey);
1444
1669
  if (!apiKey) {
1445
- warnOnce({
1670
+ warnOnce2({
1446
1671
  logger,
1447
1672
  key: "no-api-key",
1448
1673
  message: `agent "${name}" not connected to LangWatch: no API key. Set LANGWATCH_API_KEY to run simulations against it.`