@lmnr-ai/lmnr 0.8.44 → 0.8.45

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 (33) hide show
  1. package/dist/cli.cjs +23 -25
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.d.cts +1 -1
  4. package/dist/cli.d.mts +1 -1
  5. package/dist/cli.mjs +7 -8
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/{evaluations-RbBZb5yl.d.cts → evaluations-BHEE6fmA.d.cts} +17934 -8631
  8. package/dist/{evaluations-CVo0ms_r.d.mts → evaluations-BbIIQMLr.d.mts} +17934 -8631
  9. package/dist/{file-utils-1olUHMKC.mjs → file-utils-BtzEDUy6.mjs} +2 -2
  10. package/dist/file-utils-BtzEDUy6.mjs.map +1 -0
  11. package/dist/{file-utils-CMO5MuV9.cjs → file-utils-pZG20hHb.cjs} +6 -6
  12. package/dist/file-utils-pZG20hHb.cjs.map +1 -0
  13. package/dist/index.cjs +574 -435
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +1533 -437
  16. package/dist/index.d.mts +1533 -437
  17. package/dist/index.mjs +287 -148
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs +159 -62
  20. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs.map +1 -1
  21. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.cts +3388 -250
  22. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.mts +3388 -250
  23. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs +159 -62
  24. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs.map +1 -1
  25. package/dist/{dist-BtB31bk1.cjs → utils-CatgN6Pb.cjs} +558 -558
  26. package/dist/utils-CatgN6Pb.cjs.map +1 -0
  27. package/dist/{dist-Cjres0a5.mjs → utils-UmlDPMxe.mjs} +552 -552
  28. package/dist/utils-UmlDPMxe.mjs.map +1 -0
  29. package/package.json +30 -30
  30. package/dist/dist-BtB31bk1.cjs.map +0 -1
  31. package/dist/dist-Cjres0a5.mjs.map +0 -1
  32. package/dist/file-utils-1olUHMKC.mjs.map +0 -1
  33. package/dist/file-utils-CMO5MuV9.cjs.map +0 -1
@@ -1,10 +1,10 @@
1
- import { TraceFlags } from "@opentelemetry/api";
2
1
  import { config } from "dotenv";
3
2
  import * as path from "path";
4
3
  import pino from "pino";
5
4
  import { PinoPretty } from "pino-pretty";
6
- import { fileURLToPath } from "url";
7
5
  import { v4 } from "uuid";
6
+ import { TraceFlags } from "@opentelemetry/api";
7
+ import { fileURLToPath } from "url";
8
8
  //#region ../types/dist/index.mjs
9
9
  /** Directory the debug-session file lives in, relative to the working dir. */
10
10
  const DEBUG_SESSION_DIR = ".lmnr";
@@ -25,42 +25,128 @@ let TracingLevel = /* @__PURE__ */ function(TracingLevel) {
25
25
  }({});
26
26
  const errorMessage = (error) => error instanceof Error ? error.message : String(error);
27
27
  //#endregion
28
- //#region package.json
29
- var version$1 = "0.8.44";
30
- //#endregion
31
- //#region src/opentelemetry-lib/tracing/attributes.ts
32
- const SPAN_INPUT = "lmnr.span.input";
33
- const SPAN_OUTPUT = "lmnr.span.output";
34
- const SPAN_TYPE = "lmnr.span.type";
35
- const SPAN_PATH = "lmnr.span.path";
36
- const SPAN_IDS_PATH = "lmnr.span.ids_path";
37
- const PARENT_SPAN_PATH = "lmnr.span.parent_path";
38
- const PARENT_SPAN_IDS_PATH = "lmnr.span.parent_ids_path";
39
- const SPAN_INSTRUMENTATION_SOURCE = "lmnr.span.instrumentation_source";
40
- const SPAN_SDK_VERSION = "lmnr.span.sdk_version";
41
- const SPAN_LANGUAGE_VERSION = "lmnr.span.language_version";
42
- const SPAN_INSTRUMENTATION_SCOPE_NAME = "lmnr.span.instrumentation_scope.name";
43
- const SPAN_INSTRUMENTATION_SCOPE_VERSION = "lmnr.span.instrumentation_scope.version";
44
- const TRACE_HAS_BROWSER_SESSION = "lmnr.internal.has_browser_session";
45
- const HUMAN_EVALUATOR_OPTIONS = "lmnr.span.human_evaluator_options";
46
- const ASSOCIATION_PROPERTIES = "lmnr.association.properties";
47
- const SESSION_ID = "lmnr.association.properties.session_id";
48
- const USER_ID = "lmnr.association.properties.user_id";
49
- const TRACE_TYPE = "lmnr.association.properties.trace_type";
50
- const ASSOCIATION_PROPERTIES_OVERRIDES = { "span_type": SPAN_TYPE };
51
- const LaminarAttributes = {
52
- INPUT_TOKEN_COUNT: "gen_ai.usage.input_tokens",
53
- OUTPUT_TOKEN_COUNT: "gen_ai.usage.output_tokens",
54
- TOTAL_TOKEN_COUNT: "llm.usage.total_tokens",
55
- PROVIDER: "gen_ai.system",
56
- REQUEST_MODEL: "gen_ai.request.model",
57
- RESPONSE_MODEL: "gen_ai.response.model",
58
- INPUT_COST: "gen_ai.usage.input_cost",
59
- OUTPUT_COST: "gen_ai.usage.output_cost",
60
- TOTAL_COST: "gen_ai.usage.cost"
28
+ //#region ../client/dist/index.mjs
29
+ var version$1 = "0.8.45";
30
+ function getLangVersion() {
31
+ if (typeof process !== "undefined" && process.versions && process.versions.node) return `node-${process.versions.node}`;
32
+ if (typeof navigator !== "undefined" && navigator.userAgent) return `browser-${navigator.userAgent}`;
33
+ return null;
34
+ }
35
+ var BaseResource = class {
36
+ constructor(baseHttpUrl, auth) {
37
+ this.baseHttpUrl = baseHttpUrl;
38
+ this.auth = auth;
39
+ this.credential = auth.type === "apiKey" ? auth.key : auth.token;
40
+ }
41
+ /** API path prefix: `/v1/cli` for CLI user-token auth, `/v1` otherwise. */
42
+ get apiPrefix() {
43
+ return this.auth.type === "userToken" ? "/v1/cli" : "/v1";
44
+ }
45
+ headers() {
46
+ return {
47
+ Authorization: `Bearer ${this.credential}`,
48
+ "Content-Type": "application/json",
49
+ Accept: "application/json",
50
+ ...this.auth.type === "userToken" ? { "x-lmnr-project-id": this.auth.projectId } : {}
51
+ };
52
+ }
53
+ async handleError(response) {
54
+ const errorMsg = await response.text();
55
+ throw new Error(`${response.status} ${errorMsg}`);
56
+ }
57
+ };
58
+ var BrowserEventsResource = class extends BaseResource {
59
+ constructor(baseHttpUrl, auth) {
60
+ super(baseHttpUrl, auth);
61
+ }
62
+ async send({ sessionId, traceId, events }) {
63
+ const payload = {
64
+ sessionId,
65
+ traceId,
66
+ events,
67
+ source: getLangVersion() ?? "javascript",
68
+ sdkVersion: version$1
69
+ };
70
+ const jsonString = JSON.stringify(payload);
71
+ const compressedStream = new Blob([jsonString], { type: "application/json" }).stream().pipeThrough(new CompressionStream("gzip"));
72
+ const compressedData = await new Response(compressedStream).arrayBuffer();
73
+ const response = await fetch(this.baseHttpUrl + "/v1/browser-sessions/events", {
74
+ method: "POST",
75
+ headers: {
76
+ ...this.headers(),
77
+ "Content-Encoding": "gzip"
78
+ },
79
+ body: compressedData
80
+ });
81
+ if (!response.ok) await this.handleError(response);
82
+ }
83
+ };
84
+ /**
85
+ * Locale-aware, case-insensitive comparator for the project listing. Ordering
86
+ * lives here — the single choke point every CLI surface (the `project list`
87
+ * table + its `--json`, and the interactive picker used by `setup` / `plugin
88
+ * add`) reads from — so the order can't drift between surfaces.
89
+ */
90
+ const projectCollator = new Intl.Collator(void 0, { sensitivity: "base" });
91
+ /** Sort projects by workspace name, then project name (stable, human-scannable). */
92
+ const sortProjects = (projects) => [...projects].sort((a, b) => projectCollator.compare(a.workspaceName ?? "", b.workspaceName ?? "") || projectCollator.compare(a.name ?? "", b.name ?? ""));
93
+ /**
94
+ * User-scoped CLI endpoints that don't target a specific project. Authed by the
95
+ * BetterAuth user JWT (the `credential`); deliberately does NOT send an
96
+ * `x-lmnr-project-id` header (these routes are project discovery, pre-selection).
97
+ *
98
+ * Discovery exception: this resource always hits `/v1/cli/projects` with the
99
+ * bare bearer and overrides `BaseResource.headers()`/`apiPrefix`, so it works
100
+ * even when constructed with a `userToken` auth that has no real project id yet.
101
+ */
102
+ var CliResource = class extends BaseResource {
103
+ constructor(baseHttpUrl, auth) {
104
+ super(baseHttpUrl, auth);
105
+ }
106
+ /** Workspaces + projects the authenticated user can access. */
107
+ async listProjects() {
108
+ const response = await fetch(`${this.baseHttpUrl}/v1/cli/projects`, {
109
+ method: "GET",
110
+ headers: {
111
+ Authorization: `Bearer ${this.credential}`,
112
+ Accept: "application/json"
113
+ }
114
+ });
115
+ if (!response.ok) await this.handleError(response);
116
+ const body = await response.json();
117
+ return Array.isArray(body?.projects) ? sortProjects(body.projects) : [];
118
+ }
119
+ /**
120
+ * Resolve which project a project API key belongs to. Authed by the user JWT
121
+ * (the bearer); the project key travels in the body, NOT in `Authorization`.
122
+ * The server verifies the key and that the authenticated user is a member of
123
+ * the resolved project. Returns a tri-state probe so callers can distinguish a
124
+ * revoked key (401 → `invalid`) from a server/access problem (`unverifiable`).
125
+ */
126
+ async resolveProjectByApiKey(apiKey) {
127
+ let response;
128
+ try {
129
+ response = await fetch(`${this.baseHttpUrl}/v1/cli/project`, {
130
+ method: "POST",
131
+ headers: {
132
+ Authorization: `Bearer ${this.credential}`,
133
+ "Content-Type": "application/json",
134
+ Accept: "application/json"
135
+ },
136
+ body: JSON.stringify({ apiKey })
137
+ });
138
+ } catch {
139
+ return { status: "unverifiable" };
140
+ }
141
+ if (response.status === 401) return { status: "invalid" };
142
+ if (!response.ok) return { status: "unverifiable" };
143
+ const body = await response.json().catch(() => null);
144
+ return body?.projectId ? {
145
+ status: "ok",
146
+ projectId: body.projectId
147
+ } : { status: "unverifiable" };
148
+ }
61
149
  };
62
- //#endregion
63
- //#region src/utils.ts
64
150
  function initializeLogger$1(options) {
65
151
  const colorize = options?.colorize ?? true;
66
152
  const level = options?.level ?? process.env.LMNR_LOG_LEVEL?.toLowerCase()?.trim() ?? "info";
@@ -69,29 +155,18 @@ function initializeLogger$1(options) {
69
155
  minimumLevel: level
70
156
  }));
71
157
  }
72
- const logger$5 = initializeLogger$1();
158
+ const logger$4 = initializeLogger$1();
73
159
  const isStringUUID$1 = (id) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id);
74
- const NIL_UUID = "00000000-0000-0000-0000-000000000000";
75
160
  const newUUID$1 = () => {
76
161
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
77
162
  else return v4();
78
163
  };
79
- const normalizeOtelTraceId = (traceId) => {
80
- let id = traceId.toLowerCase();
81
- if (id.startsWith("0x")) id = id.slice(2);
82
- return id.padStart(32, "0").slice(-32);
83
- };
84
- const normalizeOtelSpanId = (spanId) => {
85
- let id = spanId.toLowerCase();
86
- if (id.startsWith("0x")) id = id.slice(2);
87
- return id.padStart(16, "0").slice(-16);
88
- };
89
164
  const otelSpanIdToUUID$1 = (spanId) => {
90
165
  let id = spanId.toLowerCase();
91
166
  if (id.startsWith("0x")) id = id.slice(2);
92
- if (id.length !== 16) logger$5.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);
167
+ if (id.length !== 16) logger$4.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);
93
168
  if (!/^[0-9a-f]+$/.test(id)) {
94
- logger$5.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);
169
+ logger$4.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);
95
170
  return newUUID$1();
96
171
  }
97
172
  return id.padStart(32, "0").replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
@@ -99,481 +174,70 @@ const otelSpanIdToUUID$1 = (spanId) => {
99
174
  const otelTraceIdToUUID$1 = (traceId) => {
100
175
  let id = traceId.toLowerCase();
101
176
  if (id.startsWith("0x")) id = id.slice(2);
102
- if (id.length !== 32) logger$5.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);
177
+ if (id.length !== 32) logger$4.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);
103
178
  if (!/^[0-9a-f]+$/.test(id)) {
104
- logger$5.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);
179
+ logger$4.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);
105
180
  return newUUID$1();
106
181
  }
107
182
  return id.replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
108
183
  };
109
- const uuidToOtelTraceId = (uuid) => uuid.replace(/-/g, "");
110
- const uuidToOtelSpanId = (uuid) => uuid.replace(/-/g, "").slice(16);
111
- /**
112
- * This is a simple implementation of a semaphore to replicate
113
- * the behavior of the `asyncio.Semaphore` in Python.
114
- */
115
- var Semaphore = class {
116
- constructor(value = 1) {
117
- this._waiters = [];
118
- if (value < 0) throw new Error("Semaphore value must be >= 0");
119
- this._value = value;
120
- this._waiters = [];
184
+ const slicePayload = (value, length) => {
185
+ if (value === null || value === void 0) return value;
186
+ const str = JSON.stringify(value);
187
+ if (str.length <= length) return value;
188
+ return str.slice(0, length) + "...";
189
+ };
190
+ const loadEnv$1 = (options) => {
191
+ const nodeEnv = process.env.NODE_ENV || "development";
192
+ const envDir = process.cwd();
193
+ const envFiles = [
194
+ ".env",
195
+ ".env.local",
196
+ `.env.${nodeEnv}`,
197
+ `.env.${nodeEnv}.local`
198
+ ];
199
+ const logLevel = process.env.LMNR_LOG_LEVEL ?? "info";
200
+ const verbose = ["debug", "trace"].includes(logLevel.trim().toLowerCase());
201
+ const quiet = options?.quiet ?? !verbose;
202
+ config({
203
+ path: options?.paths ?? envFiles.map((envFile) => path.resolve(envDir, envFile)),
204
+ quiet
205
+ });
206
+ };
207
+ const logger$3 = initializeLogger$1();
208
+ const DEFAULT_DATASET_PULL_LIMIT = 100;
209
+ const DEFAULT_DATASET_PUSH_BATCH_SIZE = 100;
210
+ var DatasetsResource = class extends BaseResource {
211
+ constructor(baseHttpUrl, auth) {
212
+ super(baseHttpUrl, auth);
121
213
  }
122
- async acquire() {
123
- if (this._value > 0) {
124
- this._value--;
125
- return;
126
- }
127
- return new Promise((resolve) => {
128
- this._waiters.push(resolve);
214
+ /**
215
+ * List all datasets.
216
+ *
217
+ * @returns {Promise<Dataset[]>} Array of datasets
218
+ */
219
+ async listDatasets() {
220
+ const response = await fetch(this.baseHttpUrl + this.apiPrefix + "/datasets", {
221
+ method: "GET",
222
+ headers: this.headers()
129
223
  });
224
+ if (!response.ok) await this.handleError(response);
225
+ return response.json();
130
226
  }
131
- release() {
132
- if (this._waiters.length > 0) this._waiters.shift()?.();
133
- else this._value++;
134
- }
135
- async using(fn) {
136
- try {
137
- await this.acquire();
138
- return await fn();
139
- } finally {
140
- this.release();
141
- }
142
- }
143
- };
144
- const tryToOtelSpanContext = (spanContext) => {
145
- if (typeof spanContext === "string") try {
146
- return recordToOtelSpanContext(JSON.parse(spanContext));
147
- } catch (e) {
148
- throw new Error(`Failed to parse span context ${spanContext}. The string must be a json representation of a LaminarSpanContext.Error: ${errorMessage(e)}`);
149
- }
150
- else if (isRecord(spanContext)) return recordToOtelSpanContext(spanContext);
151
- else if (typeof spanContext.traceId === "string" && typeof spanContext.spanId === "string" && spanContext.traceId.length === 32 && spanContext.spanId.length === 16) {
152
- logger$5.warn("The span context is already an OpenTelemetry SpanContext. Returning it as is. Please use `LaminarSpanContext` objects instead.");
153
- return spanContext;
154
- } else throw new Error(`Invalid span context ${JSON.stringify(spanContext)}. Must be a LaminarSpanContext or its json representation.`);
155
- };
156
- const recordToOtelSpanContext = (record) => {
157
- if (typeof record.spanId === "string" && typeof record.traceId === "string" || typeof record.span_id === "string" && typeof record.trace_id === "string") return {
158
- spanId: uuidToOtelSpanId(record?.spanId ?? record?.["span_id"]),
159
- traceId: uuidToOtelTraceId(record?.traceId ?? record?.["trace_id"]),
160
- isRemote: record?.isRemote ?? record?.["is_remote"] ?? false,
161
- traceFlags: record?.traceFlags ?? TraceFlags.SAMPLED
162
- };
163
- else throw new Error(`Invalid span context ${JSON.stringify(record)}. Must be a json representation of a LaminarSpanContext.`);
164
- };
165
- const isRecord = (value) => typeof value === "object" && !Array.isArray(value) && value !== null;
166
- /**
167
- * Deserialize a LaminarSpanContext from a string or record.
168
- * Handles both camelCase and snake_case keys for cross-language compatibility.
169
- *
170
- * @param data - The data to deserialize (string or record)
171
- * @returns The deserialized LaminarSpanContext
172
- * @throws Error if the data is invalid
173
- */
174
- const deserializeLaminarSpanContext = (data) => {
175
- if (typeof data === "string") try {
176
- return deserializeLaminarSpanContext(JSON.parse(data));
177
- } catch (e) {
178
- throw new Error(`Failed to parse LaminarSpanContext: ${errorMessage(e)}`);
179
- }
180
- if (!isRecord(data)) throw new Error("Invalid LaminarSpanContext: must be a string or object");
181
- const traceId = data.traceId ?? data.trace_id;
182
- const spanId = data.spanId ?? data.span_id;
183
- const isRemote = data.isRemote ?? data.is_remote ?? false;
184
- const spanPath = data.spanPath ?? data.span_path;
185
- const spanIdsPath = data.spanIdsPath ?? data.span_ids_path;
186
- const userId = data.userId ?? data.user_id;
187
- const sessionId = data.sessionId ?? data.session_id;
188
- const metadata = data.metadata;
189
- const traceType = data.traceType ?? data.trace_type;
190
- const tracingLevel = data.tracingLevel ?? data.tracing_level;
191
- const debug = data.debug;
192
- if (typeof traceId !== "string" || typeof spanId !== "string") throw new Error("Invalid LaminarSpanContext: traceId and spanId must be strings");
193
- if (!isStringUUID$1(traceId) || !isStringUUID$1(spanId)) throw new Error("Invalid LaminarSpanContext: traceId and spanId must be valid UUIDs");
194
- return {
195
- traceId,
196
- spanId,
197
- isRemote: Boolean(isRemote),
198
- spanPath: Array.isArray(spanPath) ? spanPath : void 0,
199
- spanIdsPath: Array.isArray(spanIdsPath) ? spanIdsPath : void 0,
200
- userId,
201
- sessionId,
202
- metadata,
203
- traceType,
204
- tracingLevel,
205
- debug: isRecord(debug) ? deserializeDebugContext(debug) : void 0
206
- };
207
- };
208
- /**
209
- * Normalize a value to a canonical lowercase UUID string, or undefined.
210
- *
211
- * The debug block's `sessionId` / `replayTraceId` are always full ids; a value
212
- * that isn't UUID-shaped is dropped (treated as absent) rather than thrown, so
213
- * a partially-broken block never breaks span-context parsing.
214
- */
215
- const asString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
216
- /**
217
- * Parse a debug block, accepting camelCase and snake_case. All ids are kept
218
- * VERBATIM: the producer emits the run's exact session / replay-trace /
219
- * cache-until strings (un-normalized — `LMNR_DEBUG_SESSION_ID` may be an
220
- * arbitrary non-UUID value), so the consumer must round-trip them unchanged or
221
- * a downstream run never joins the run. Keep line-comparable with the Python
222
- * `DebugContext.deserialize`.
223
- */
224
- const deserializeDebugContext = (data) => ({
225
- enabled: data.enabled === true,
226
- sessionId: asString(data.sessionId ?? data.session_id),
227
- replayTraceId: asString(data.replayTraceId ?? data.replay_trace_id),
228
- cacheUntil: asString(data.cacheUntil ?? data.cache_until)
229
- });
230
- const getDirname = () => {
231
- if (typeof __dirname !== "undefined") return __dirname;
232
- if (typeof import.meta.url !== "undefined") return path.dirname(fileURLToPath(import.meta.url));
233
- return process.cwd();
234
- };
235
- const MAX_MANUAL_SPAN_PAYLOAD_SIZE = 1024 * 1024 * 10;
236
- const TRUNCATION_SUFFIX = "...[Laminar: truncated]";
237
- /**
238
- * Cut an oversized span payload down to the limit rather than dropping it.
239
- *
240
- * Keeping the leading bytes preserves the start of the value, which is the useful part when
241
- * debugging. Mirrors `_truncate_payload` in the Python SDK.
242
- *
243
- * Callers pass the result of `JSON.stringify`, whose TS signature claims `string` but which
244
- * actually returns `undefined` for a top-level `undefined`, function, or symbol. Guard at
245
- * runtime and hand such values straight back: `setAttribute` treats them as a no-op, which is
246
- * the behaviour these payloads had before truncation existed.
247
- */
248
- const truncateSpanPayload = (serialized, kind) => {
249
- if (typeof serialized !== "string") return serialized;
250
- if (serialized.length <= 10485760) return serialized;
251
- logger$5.warn(`Laminar: span ${kind} is ${serialized.length} bytes, which exceeds the ${MAX_MANUAL_SPAN_PAYLOAD_SIZE} byte limit. Truncating to the limit; the recorded value will not be valid JSON.`);
252
- const keep = MAX_MANUAL_SPAN_PAYLOAD_SIZE - 23;
253
- return serialized.slice(0, keep) + TRUNCATION_SUFFIX;
254
- };
255
- const isOtelAttributeValueType = (value) => {
256
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return true;
257
- if (Array.isArray(value)) {
258
- const allStrings = value.every((value) => value == null || typeof value === "string");
259
- const allNumbers = value.every((value) => value == null || typeof value === "number");
260
- const allBooleans = value.every((value) => value == null || typeof value === "boolean");
261
- return allStrings || allNumbers || allBooleans;
262
- }
263
- return false;
264
- };
265
- const metadataToAttributes = (metadata) => Object.fromEntries(Object.entries(metadata).map(([key, value]) => {
266
- if (isOtelAttributeValueType(value)) return [`${ASSOCIATION_PROPERTIES}.metadata.${key}`, value];
267
- else return [`${ASSOCIATION_PROPERTIES}.metadata.${key}`, JSON.stringify(value)];
268
- }));
269
- /**
270
- * Get OTEL environment variable with priority order.
271
- * Checks in order:
272
- * 1. OTEL_EXPORTER_OTLP_TRACES_{varName}
273
- * 2. OTEL_EXPORTER_OTLP_{varName}
274
- * 3. OTEL_{varName}
275
- *
276
- * @param varName - The variable name (e.g., 'ENDPOINT', 'HEADERS', 'PROTOCOL')
277
- * @returns The environment variable value or undefined if not found
278
- */
279
- const getOtelEnvVar = (varName) => {
280
- const candidates = [
281
- `OTEL_EXPORTER_OTLP_TRACES_${varName}`,
282
- `OTEL_EXPORTER_OTLP_${varName}`,
283
- `OTEL_${varName}`
284
- ];
285
- for (const candidate of candidates) {
286
- const value = process?.env?.[candidate];
287
- if (value) return value;
288
- }
289
- };
290
- /**
291
- * Check if OTEL configuration is available.
292
- * @returns true if OTEL endpoint is configured
293
- */
294
- const hasOtelConfig = () => !!getOtelEnvVar("ENDPOINT");
295
- /**
296
- * Parse OTEL headers string into a record object.
297
- * Format: key1=value1,key2=value2
298
- * Values are URL-decoded.
299
- *
300
- * @param headersStr - Headers string in OTEL format
301
- * @returns Parsed headers object
302
- */
303
- const parseOtelHeaders = (headersStr) => {
304
- if (!headersStr) return {};
305
- const headers = {};
306
- for (const pair of headersStr.split(",")) {
307
- const equalIndex = pair.indexOf("=");
308
- if (equalIndex !== -1) {
309
- const key = pair.substring(0, equalIndex).trim();
310
- const value = pair.substring(equalIndex + 1).trim();
311
- headers[key] = decodeURIComponent(value);
312
- }
313
- }
314
- return headers;
315
- };
316
- /**
317
- * Validate that either Laminar API key or OTEL configuration is present.
318
- * Throws an error if neither is configured.
319
- *
320
- * @param apiKey - The Laminar API key (if provided)
321
- * @throws Error if neither API key nor OTEL configuration is present
322
- */
323
- const validateTracingConfig = (apiKey) => {
324
- if (!apiKey && !hasOtelConfig()) throw new Error("Please initialize the Laminar object with your project API key or set the LMNR_PROJECT_API_KEY environment variable, or configure OTEL environment variables (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, etc.)");
325
- };
326
- const loadEnv$1 = (options) => {
327
- const nodeEnv = process.env.NODE_ENV || "development";
328
- const envDir = process.cwd();
329
- const envFiles = [
330
- ".env",
331
- ".env.local",
332
- `.env.${nodeEnv}`,
333
- `.env.${nodeEnv}.local`
334
- ];
335
- const logLevel = process.env.LMNR_LOG_LEVEL ?? "info";
336
- const verbose = ["debug", "trace"].includes(logLevel.trim().toLowerCase());
337
- const quiet = options?.quiet ?? !verbose;
338
- config({
339
- path: options?.paths ?? envFiles.map((envFile) => path.resolve(envDir, envFile)),
340
- quiet
341
- });
342
- };
343
- /**
344
- * Converts an API base URL to the frontend/web URL.
345
- * - Converts https://api.lmnr.ai to https://www.laminar.sh
346
- * - Removes trailing slashes
347
- * - For localhost/127.0.0.1, ensures a port is specified (defaults to 5667)
348
- *
349
- * @param baseUrl - The API base URL (defaults to "https://api.lmnr.ai")
350
- * @returns The frontend URL
351
- */
352
- const getFrontendUrl = (baseUrl, frontendPort) => {
353
- let url = baseUrl ?? "https://api.lmnr.ai";
354
- if (url === "https://api.lmnr.ai") url = "https://www.laminar.sh";
355
- url = url.replace(/\/$/, "");
356
- if (/localhost|127\.0\.0\.1/.test(url)) {
357
- const port = frontendPort ?? url.match(/:\d{1,5}$/g)?.[0]?.slice(1) ?? 5667;
358
- if (/:(\d{1,5})$/.test(url)) url = url.replace(/:\d{1,5}$/g, `:${port}`);
359
- else url = `${url}:${port}`;
360
- }
361
- return url;
362
- };
363
- //#endregion
364
- //#region ../client/dist/index.mjs
365
- var version = "0.8.44";
366
- function getLangVersion() {
367
- if (typeof process !== "undefined" && process.versions && process.versions.node) return `node-${process.versions.node}`;
368
- if (typeof navigator !== "undefined" && navigator.userAgent) return `browser-${navigator.userAgent}`;
369
- return null;
370
- }
371
- var BaseResource = class {
372
- constructor(baseHttpUrl, auth) {
373
- this.baseHttpUrl = baseHttpUrl;
374
- this.auth = auth;
375
- this.credential = auth.type === "apiKey" ? auth.key : auth.token;
376
- }
377
- /** API path prefix: `/v1/cli` for CLI user-token auth, `/v1` otherwise. */
378
- get apiPrefix() {
379
- return this.auth.type === "userToken" ? "/v1/cli" : "/v1";
380
- }
381
- headers() {
382
- return {
383
- Authorization: `Bearer ${this.credential}`,
384
- "Content-Type": "application/json",
385
- Accept: "application/json",
386
- ...this.auth.type === "userToken" ? { "x-lmnr-project-id": this.auth.projectId } : {}
387
- };
388
- }
389
- async handleError(response) {
390
- const errorMsg = await response.text();
391
- throw new Error(`${response.status} ${errorMsg}`);
392
- }
393
- };
394
- var BrowserEventsResource = class extends BaseResource {
395
- constructor(baseHttpUrl, auth) {
396
- super(baseHttpUrl, auth);
397
- }
398
- async send({ sessionId, traceId, events }) {
399
- const payload = {
400
- sessionId,
401
- traceId,
402
- events,
403
- source: getLangVersion() ?? "javascript",
404
- sdkVersion: version
405
- };
406
- const jsonString = JSON.stringify(payload);
407
- const compressedStream = new Blob([jsonString], { type: "application/json" }).stream().pipeThrough(new CompressionStream("gzip"));
408
- const compressedData = await new Response(compressedStream).arrayBuffer();
409
- const response = await fetch(this.baseHttpUrl + "/v1/browser-sessions/events", {
410
- method: "POST",
411
- headers: {
412
- ...this.headers(),
413
- "Content-Encoding": "gzip"
414
- },
415
- body: compressedData
416
- });
417
- if (!response.ok) await this.handleError(response);
418
- }
419
- };
420
- /**
421
- * Locale-aware, case-insensitive comparator for the project listing. Ordering
422
- * lives here — the single choke point every CLI surface (the `project list`
423
- * table + its `--json`, and the interactive picker used by `setup` / `plugin
424
- * add`) reads from — so the order can't drift between surfaces.
425
- */
426
- const projectCollator = new Intl.Collator(void 0, { sensitivity: "base" });
427
- /** Sort projects by workspace name, then project name (stable, human-scannable). */
428
- const sortProjects = (projects) => [...projects].sort((a, b) => projectCollator.compare(a.workspaceName ?? "", b.workspaceName ?? "") || projectCollator.compare(a.name ?? "", b.name ?? ""));
429
- /**
430
- * User-scoped CLI endpoints that don't target a specific project. Authed by the
431
- * BetterAuth user JWT (the `credential`); deliberately does NOT send an
432
- * `x-lmnr-project-id` header (these routes are project discovery, pre-selection).
433
- *
434
- * Discovery exception: this resource always hits `/v1/cli/projects` with the
435
- * bare bearer and overrides `BaseResource.headers()`/`apiPrefix`, so it works
436
- * even when constructed with a `userToken` auth that has no real project id yet.
437
- */
438
- var CliResource = class extends BaseResource {
439
- constructor(baseHttpUrl, auth) {
440
- super(baseHttpUrl, auth);
441
- }
442
- /** Workspaces + projects the authenticated user can access. */
443
- async listProjects() {
444
- const response = await fetch(`${this.baseHttpUrl}/v1/cli/projects`, {
445
- method: "GET",
446
- headers: {
447
- Authorization: `Bearer ${this.credential}`,
448
- Accept: "application/json"
449
- }
450
- });
451
- if (!response.ok) await this.handleError(response);
452
- const body = await response.json();
453
- return Array.isArray(body?.projects) ? sortProjects(body.projects) : [];
454
- }
455
- /**
456
- * Resolve which project a project API key belongs to. Authed by the user JWT
457
- * (the bearer); the project key travels in the body, NOT in `Authorization`.
458
- * The server verifies the key and that the authenticated user is a member of
459
- * the resolved project. Returns a tri-state probe so callers can distinguish a
460
- * revoked key (401 → `invalid`) from a server/access problem (`unverifiable`).
461
- */
462
- async resolveProjectByApiKey(apiKey) {
463
- let response;
464
- try {
465
- response = await fetch(`${this.baseHttpUrl}/v1/cli/project`, {
466
- method: "POST",
467
- headers: {
468
- Authorization: `Bearer ${this.credential}`,
469
- "Content-Type": "application/json",
470
- Accept: "application/json"
471
- },
472
- body: JSON.stringify({ apiKey })
473
- });
474
- } catch {
475
- return { status: "unverifiable" };
476
- }
477
- if (response.status === 401) return { status: "invalid" };
478
- if (!response.ok) return { status: "unverifiable" };
479
- const body = await response.json().catch(() => null);
480
- return body?.projectId ? {
481
- status: "ok",
482
- projectId: body.projectId
483
- } : { status: "unverifiable" };
484
- }
485
- };
486
- function initializeLogger(options) {
487
- const colorize = options?.colorize ?? true;
488
- const level = options?.level ?? process.env.LMNR_LOG_LEVEL?.toLowerCase()?.trim() ?? "info";
489
- return pino({ level }, PinoPretty({
490
- colorize,
491
- minimumLevel: level
492
- }));
493
- }
494
- const logger$4 = initializeLogger();
495
- const isStringUUID = (id) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id);
496
- const newUUID = () => {
497
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
498
- else return v4();
499
- };
500
- const otelSpanIdToUUID = (spanId) => {
501
- let id = spanId.toLowerCase();
502
- if (id.startsWith("0x")) id = id.slice(2);
503
- if (id.length !== 16) logger$4.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);
504
- if (!/^[0-9a-f]+$/.test(id)) {
505
- logger$4.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);
506
- return newUUID();
507
- }
508
- return id.padStart(32, "0").replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
509
- };
510
- const otelTraceIdToUUID = (traceId) => {
511
- let id = traceId.toLowerCase();
512
- if (id.startsWith("0x")) id = id.slice(2);
513
- if (id.length !== 32) logger$4.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);
514
- if (!/^[0-9a-f]+$/.test(id)) {
515
- logger$4.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);
516
- return newUUID();
517
- }
518
- return id.replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
519
- };
520
- const slicePayload = (value, length) => {
521
- if (value === null || value === void 0) return value;
522
- const str = JSON.stringify(value);
523
- if (str.length <= length) return value;
524
- return str.slice(0, length) + "...";
525
- };
526
- const loadEnv = (options) => {
527
- const nodeEnv = process.env.NODE_ENV || "development";
528
- const envDir = process.cwd();
529
- const envFiles = [
530
- ".env",
531
- ".env.local",
532
- `.env.${nodeEnv}`,
533
- `.env.${nodeEnv}.local`
534
- ];
535
- const logLevel = process.env.LMNR_LOG_LEVEL ?? "info";
536
- const verbose = ["debug", "trace"].includes(logLevel.trim().toLowerCase());
537
- const quiet = options?.quiet ?? !verbose;
538
- config({
539
- path: options?.paths ?? envFiles.map((envFile) => path.resolve(envDir, envFile)),
540
- quiet
541
- });
542
- };
543
- const logger$3 = initializeLogger();
544
- const DEFAULT_DATASET_PULL_LIMIT = 100;
545
- const DEFAULT_DATASET_PUSH_BATCH_SIZE = 100;
546
- var DatasetsResource = class extends BaseResource {
547
- constructor(baseHttpUrl, auth) {
548
- super(baseHttpUrl, auth);
549
- }
550
- /**
551
- * List all datasets.
552
- *
553
- * @returns {Promise<Dataset[]>} Array of datasets
554
- */
555
- async listDatasets() {
556
- const response = await fetch(this.baseHttpUrl + this.apiPrefix + "/datasets", {
557
- method: "GET",
558
- headers: this.headers()
559
- });
560
- if (!response.ok) await this.handleError(response);
561
- return response.json();
562
- }
563
- /**
564
- * Get a dataset by name.
565
- *
566
- * @param {string} name - Name of the dataset
567
- * @returns {Promise<Dataset[]>} Array of datasets with matching name
568
- */
569
- async getDatasetByName(name) {
570
- const params = new URLSearchParams({ name });
571
- const response = await fetch(this.baseHttpUrl + `${this.apiPrefix}/datasets?${params.toString()}`, {
572
- method: "GET",
573
- headers: this.headers()
574
- });
575
- if (!response.ok) await this.handleError(response);
576
- return response.json();
227
+ /**
228
+ * Get a dataset by name.
229
+ *
230
+ * @param {string} name - Name of the dataset
231
+ * @returns {Promise<Dataset[]>} Array of datasets with matching name
232
+ */
233
+ async getDatasetByName(name) {
234
+ const params = new URLSearchParams({ name });
235
+ const response = await fetch(this.baseHttpUrl + `${this.apiPrefix}/datasets?${params.toString()}`, {
236
+ method: "GET",
237
+ headers: this.headers()
238
+ });
239
+ if (!response.ok) await this.handleError(response);
240
+ return response.json();
577
241
  }
578
242
  /**
579
243
  * Push datapoints to a dataset.
@@ -643,7 +307,7 @@ var DatasetsResource = class extends BaseResource {
643
307
  return response.json();
644
308
  }
645
309
  };
646
- const logger$2 = initializeLogger();
310
+ const logger$2 = initializeLogger$1();
647
311
  const INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH = 16e6;
648
312
  var EvalsResource = class extends BaseResource {
649
313
  constructor(baseHttpUrl, auth) {
@@ -723,14 +387,14 @@ var EvalsResource = class extends BaseResource {
723
387
  * @returns {Promise<StringUUID>} The datapoint ID
724
388
  */
725
389
  async createDatapoint({ evalId, data, target, metadata, index, traceId }) {
726
- const datapointId = newUUID();
390
+ const datapointId = newUUID$1();
727
391
  const partialDatapoint = {
728
392
  id: datapointId,
729
393
  data,
730
394
  target,
731
395
  index: index ?? 0,
732
- traceId: traceId ?? newUUID(),
733
- executorSpanId: newUUID(),
396
+ traceId: traceId ?? newUUID$1(),
397
+ executorSpanId: newUUID$1(),
734
398
  metadata
735
399
  };
736
400
  await this.saveDatapoints({
@@ -882,14 +546,14 @@ var EvaluatorsResource = class extends BaseResource {
882
546
  metadata,
883
547
  score,
884
548
  source: "Code",
885
- traceId: isStringUUID(options.traceId) ? options.traceId : otelTraceIdToUUID(options.traceId)
549
+ traceId: isStringUUID$1(options.traceId) ? options.traceId : otelTraceIdToUUID$1(options.traceId)
886
550
  };
887
551
  else if ("spanId" in options && options.spanId) payload = {
888
552
  name,
889
553
  metadata,
890
554
  score,
891
555
  source: "Code",
892
- spanId: isStringUUID(options.spanId) ? options.spanId : otelSpanIdToUUID(options.spanId)
556
+ spanId: isStringUUID$1(options.spanId) ? options.spanId : otelSpanIdToUUID$1(options.spanId)
893
557
  };
894
558
  else throw new Error("Either 'traceId' or 'spanId' must be provided.");
895
559
  const response = await fetch(this.baseHttpUrl + "/v1/evaluators/score", {
@@ -900,7 +564,7 @@ var EvaluatorsResource = class extends BaseResource {
900
564
  if (!response.ok) await this.handleError(response);
901
565
  }
902
566
  };
903
- const logger$1 = initializeLogger();
567
+ const logger$1 = initializeLogger$1();
904
568
  /**
905
569
  * Map the opaque HIT `response` payload onto a {@link CachedSpan} the provider
906
570
  * wrappers can replay. The server-side shape of `response` is not yet frozen
@@ -1230,11 +894,11 @@ var TagsResource = class extends BaseResource {
1230
894
  */
1231
895
  async tag(trace_id, tags) {
1232
896
  const traceTags = Array.isArray(tags) ? tags : [tags];
1233
- const formattedTraceId = isStringUUID(trace_id) ? trace_id : otelTraceIdToUUID(trace_id);
897
+ const formattedTraceId = isStringUUID$1(trace_id) ? trace_id : otelTraceIdToUUID$1(trace_id);
1234
898
  const url = this.baseHttpUrl + "/v1/tag";
1235
899
  const payload = {
1236
- "traceId": formattedTraceId,
1237
- "names": traceTags
900
+ traceId: formattedTraceId,
901
+ names: traceTags
1238
902
  };
1239
903
  const response = await fetch(url, {
1240
904
  method: "POST",
@@ -1246,7 +910,7 @@ var TagsResource = class extends BaseResource {
1246
910
  }
1247
911
  };
1248
912
  /** Resource for post-factum operations on existing traces. */
1249
- const logger = initializeLogger();
913
+ const logger$5 = initializeLogger$1();
1250
914
  var TracesResource = class extends BaseResource {
1251
915
  /** Resource for post-factum operations on existing traces. */
1252
916
  constructor(baseHttpUrl, auth) {
@@ -1304,7 +968,7 @@ var TracesResource = class extends BaseResource {
1304
968
  */
1305
969
  async pushMetadata(traceId, metadata, options) {
1306
970
  if (!metadata || Object.keys(metadata).length === 0) throw new Error("metadata must be a non-empty object");
1307
- const formattedTraceId = isStringUUID(traceId) ? traceId : otelTraceIdToUUID(traceId);
971
+ const formattedTraceId = isStringUUID$1(traceId) ? traceId : otelTraceIdToUUID$1(traceId);
1308
972
  const url = this.baseHttpUrl + this.apiPrefix + "/traces/metadata";
1309
973
  const response = await fetch(url, {
1310
974
  method: "POST",
@@ -1317,7 +981,7 @@ var TracesResource = class extends BaseResource {
1317
981
  if (response.status === 404) {
1318
982
  const message = `Trace ${formattedTraceId} not found. The trace may not have been flushed yet — call await Laminar.flush() and retry.`;
1319
983
  if (options?.failOnNotFound) throw new Error(message);
1320
- logger.warn(message);
984
+ logger$5.warn(message);
1321
985
  return;
1322
986
  }
1323
987
  if (!response.ok) await this.handleError(response);
@@ -1325,7 +989,7 @@ var TracesResource = class extends BaseResource {
1325
989
  };
1326
990
  var LaminarClient = class LaminarClient {
1327
991
  constructor({ baseUrl, port, auth, projectApiKey, cliUserProjectId } = {}) {
1328
- loadEnv();
992
+ loadEnv$1();
1329
993
  this.auth = LaminarClient.normalizeAuth(auth, projectApiKey, cliUserProjectId);
1330
994
  const resolvedBaseUrl = baseUrl ?? process.env.LMNR_BASE_URL;
1331
995
  this.configuredUrl = resolvedBaseUrl;
@@ -1395,35 +1059,371 @@ var LaminarClient = class LaminarClient {
1395
1059
  get browserEvents() {
1396
1060
  return this._browserEvents;
1397
1061
  }
1398
- get cli() {
1399
- return this._cli;
1062
+ get cli() {
1063
+ return this._cli;
1064
+ }
1065
+ get datasets() {
1066
+ return this._datasets;
1067
+ }
1068
+ get evals() {
1069
+ return this._evals;
1070
+ }
1071
+ get evaluators() {
1072
+ return this._evaluators;
1073
+ }
1074
+ get signals() {
1075
+ return this._signals;
1076
+ }
1077
+ get rolloutSessions() {
1078
+ return this._rolloutSessions;
1079
+ }
1080
+ get sql() {
1081
+ return this._sql;
1082
+ }
1083
+ get tags() {
1084
+ return this._tags;
1085
+ }
1086
+ get traces() {
1087
+ return this._traces;
1088
+ }
1089
+ };
1090
+ //#endregion
1091
+ //#region package.json
1092
+ var version = "0.8.45";
1093
+ //#endregion
1094
+ //#region src/opentelemetry-lib/tracing/attributes.ts
1095
+ const SPAN_INPUT = "lmnr.span.input";
1096
+ const SPAN_OUTPUT = "lmnr.span.output";
1097
+ const SPAN_TYPE = "lmnr.span.type";
1098
+ const SPAN_PATH = "lmnr.span.path";
1099
+ const SPAN_IDS_PATH = "lmnr.span.ids_path";
1100
+ const PARENT_SPAN_PATH = "lmnr.span.parent_path";
1101
+ const PARENT_SPAN_IDS_PATH = "lmnr.span.parent_ids_path";
1102
+ const SPAN_INSTRUMENTATION_SOURCE = "lmnr.span.instrumentation_source";
1103
+ const SPAN_SDK_VERSION = "lmnr.span.sdk_version";
1104
+ const SPAN_LANGUAGE_VERSION = "lmnr.span.language_version";
1105
+ const SPAN_INSTRUMENTATION_SCOPE_NAME = "lmnr.span.instrumentation_scope.name";
1106
+ const SPAN_INSTRUMENTATION_SCOPE_VERSION = "lmnr.span.instrumentation_scope.version";
1107
+ const TRACE_HAS_BROWSER_SESSION = "lmnr.internal.has_browser_session";
1108
+ const HUMAN_EVALUATOR_OPTIONS = "lmnr.span.human_evaluator_options";
1109
+ const ASSOCIATION_PROPERTIES = "lmnr.association.properties";
1110
+ const SESSION_ID = "lmnr.association.properties.session_id";
1111
+ const USER_ID = "lmnr.association.properties.user_id";
1112
+ const TRACE_TYPE = "lmnr.association.properties.trace_type";
1113
+ const ASSOCIATION_PROPERTIES_OVERRIDES = { span_type: SPAN_TYPE };
1114
+ const LaminarAttributes = {
1115
+ INPUT_TOKEN_COUNT: "gen_ai.usage.input_tokens",
1116
+ OUTPUT_TOKEN_COUNT: "gen_ai.usage.output_tokens",
1117
+ TOTAL_TOKEN_COUNT: "llm.usage.total_tokens",
1118
+ PROVIDER: "gen_ai.system",
1119
+ REQUEST_MODEL: "gen_ai.request.model",
1120
+ RESPONSE_MODEL: "gen_ai.response.model",
1121
+ INPUT_COST: "gen_ai.usage.input_cost",
1122
+ OUTPUT_COST: "gen_ai.usage.output_cost",
1123
+ TOTAL_COST: "gen_ai.usage.cost"
1124
+ };
1125
+ //#endregion
1126
+ //#region src/utils.ts
1127
+ function initializeLogger(options) {
1128
+ const colorize = options?.colorize ?? true;
1129
+ const level = options?.level ?? process.env.LMNR_LOG_LEVEL?.toLowerCase()?.trim() ?? "info";
1130
+ return pino({ level }, PinoPretty({
1131
+ colorize,
1132
+ minimumLevel: level
1133
+ }));
1134
+ }
1135
+ const logger = initializeLogger();
1136
+ const isStringUUID = (id) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id);
1137
+ const NIL_UUID = "00000000-0000-0000-0000-000000000000";
1138
+ const newUUID = () => {
1139
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
1140
+ else return v4();
1141
+ };
1142
+ const normalizeOtelTraceId = (traceId) => {
1143
+ let id = traceId.toLowerCase();
1144
+ if (id.startsWith("0x")) id = id.slice(2);
1145
+ return id.padStart(32, "0").slice(-32);
1146
+ };
1147
+ const normalizeOtelSpanId = (spanId) => {
1148
+ let id = spanId.toLowerCase();
1149
+ if (id.startsWith("0x")) id = id.slice(2);
1150
+ return id.padStart(16, "0").slice(-16);
1151
+ };
1152
+ const otelSpanIdToUUID = (spanId) => {
1153
+ let id = spanId.toLowerCase();
1154
+ if (id.startsWith("0x")) id = id.slice(2);
1155
+ if (id.length !== 16) logger.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);
1156
+ if (!/^[0-9a-f]+$/.test(id)) {
1157
+ logger.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);
1158
+ return newUUID();
1159
+ }
1160
+ return id.padStart(32, "0").replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
1161
+ };
1162
+ const otelTraceIdToUUID = (traceId) => {
1163
+ let id = traceId.toLowerCase();
1164
+ if (id.startsWith("0x")) id = id.slice(2);
1165
+ if (id.length !== 32) logger.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);
1166
+ if (!/^[0-9a-f]+$/.test(id)) {
1167
+ logger.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);
1168
+ return newUUID();
1169
+ }
1170
+ return id.replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
1171
+ };
1172
+ const uuidToOtelTraceId = (uuid) => uuid.replace(/-/g, "");
1173
+ const uuidToOtelSpanId = (uuid) => uuid.replace(/-/g, "").slice(16);
1174
+ /**
1175
+ * This is a simple implementation of a semaphore to replicate
1176
+ * the behavior of the `asyncio.Semaphore` in Python.
1177
+ */
1178
+ var Semaphore = class {
1179
+ constructor(value = 1) {
1180
+ this._waiters = [];
1181
+ if (value < 0) throw new Error("Semaphore value must be >= 0");
1182
+ this._value = value;
1183
+ this._waiters = [];
1184
+ }
1185
+ async acquire() {
1186
+ if (this._value > 0) {
1187
+ this._value--;
1188
+ return;
1189
+ }
1190
+ return new Promise((resolve) => {
1191
+ this._waiters.push(resolve);
1192
+ });
1400
1193
  }
1401
- get datasets() {
1402
- return this._datasets;
1194
+ release() {
1195
+ if (this._waiters.length > 0) this._waiters.shift()?.();
1196
+ else this._value++;
1403
1197
  }
1404
- get evals() {
1405
- return this._evals;
1198
+ async using(fn) {
1199
+ try {
1200
+ await this.acquire();
1201
+ return await fn();
1202
+ } finally {
1203
+ this.release();
1204
+ }
1406
1205
  }
1407
- get evaluators() {
1408
- return this._evaluators;
1206
+ };
1207
+ const tryToOtelSpanContext = (spanContext) => {
1208
+ if (typeof spanContext === "string") try {
1209
+ return recordToOtelSpanContext(JSON.parse(spanContext));
1210
+ } catch (e) {
1211
+ throw new Error(`Failed to parse span context ${spanContext}. The string must be a json representation of a LaminarSpanContext.Error: ${errorMessage(e)}`);
1409
1212
  }
1410
- get signals() {
1411
- return this._signals;
1213
+ else if (isRecord(spanContext)) return recordToOtelSpanContext(spanContext);
1214
+ else if (typeof spanContext.traceId === "string" && typeof spanContext.spanId === "string" && spanContext.traceId.length === 32 && spanContext.spanId.length === 16) {
1215
+ logger.warn("The span context is already an OpenTelemetry SpanContext. Returning it as is. Please use `LaminarSpanContext` objects instead.");
1216
+ return spanContext;
1217
+ } else throw new Error(`Invalid span context ${JSON.stringify(spanContext)}. Must be a LaminarSpanContext or its json representation.`);
1218
+ };
1219
+ const recordToOtelSpanContext = (record) => {
1220
+ if (typeof record.spanId === "string" && typeof record.traceId === "string" || typeof record.span_id === "string" && typeof record.trace_id === "string") return {
1221
+ spanId: uuidToOtelSpanId(record?.spanId ?? record?.["span_id"]),
1222
+ traceId: uuidToOtelTraceId(record?.traceId ?? record?.["trace_id"]),
1223
+ isRemote: record?.isRemote ?? record?.["is_remote"] ?? false,
1224
+ traceFlags: record?.traceFlags ?? TraceFlags.SAMPLED
1225
+ };
1226
+ else throw new Error(`Invalid span context ${JSON.stringify(record)}. Must be a json representation of a LaminarSpanContext.`);
1227
+ };
1228
+ const isRecord = (value) => typeof value === "object" && !Array.isArray(value) && value !== null;
1229
+ /**
1230
+ * Deserialize a LaminarSpanContext from a string or record.
1231
+ * Handles both camelCase and snake_case keys for cross-language compatibility.
1232
+ *
1233
+ * @param data - The data to deserialize (string or record)
1234
+ * @returns The deserialized LaminarSpanContext
1235
+ * @throws Error if the data is invalid
1236
+ */
1237
+ const deserializeLaminarSpanContext = (data) => {
1238
+ if (typeof data === "string") try {
1239
+ return deserializeLaminarSpanContext(JSON.parse(data));
1240
+ } catch (e) {
1241
+ throw new Error(`Failed to parse LaminarSpanContext: ${errorMessage(e)}`);
1412
1242
  }
1413
- get rolloutSessions() {
1414
- return this._rolloutSessions;
1243
+ if (!isRecord(data)) throw new Error("Invalid LaminarSpanContext: must be a string or object");
1244
+ const traceId = data.traceId ?? data.trace_id;
1245
+ const spanId = data.spanId ?? data.span_id;
1246
+ const isRemote = data.isRemote ?? data.is_remote ?? false;
1247
+ const spanPath = data.spanPath ?? data.span_path;
1248
+ const spanIdsPath = data.spanIdsPath ?? data.span_ids_path;
1249
+ const userId = data.userId ?? data.user_id;
1250
+ const sessionId = data.sessionId ?? data.session_id;
1251
+ const metadata = data.metadata;
1252
+ const traceType = data.traceType ?? data.trace_type;
1253
+ const tracingLevel = data.tracingLevel ?? data.tracing_level;
1254
+ const debug = data.debug;
1255
+ if (typeof traceId !== "string" || typeof spanId !== "string") throw new Error("Invalid LaminarSpanContext: traceId and spanId must be strings");
1256
+ if (!isStringUUID(traceId) || !isStringUUID(spanId)) throw new Error("Invalid LaminarSpanContext: traceId and spanId must be valid UUIDs");
1257
+ return {
1258
+ traceId,
1259
+ spanId,
1260
+ isRemote: Boolean(isRemote),
1261
+ spanPath: Array.isArray(spanPath) ? spanPath : void 0,
1262
+ spanIdsPath: Array.isArray(spanIdsPath) ? spanIdsPath : void 0,
1263
+ userId,
1264
+ sessionId,
1265
+ metadata,
1266
+ traceType,
1267
+ tracingLevel,
1268
+ debug: isRecord(debug) ? deserializeDebugContext(debug) : void 0
1269
+ };
1270
+ };
1271
+ /**
1272
+ * Normalize a value to a canonical lowercase UUID string, or undefined.
1273
+ *
1274
+ * The debug block's `sessionId` / `replayTraceId` are always full ids; a value
1275
+ * that isn't UUID-shaped is dropped (treated as absent) rather than thrown, so
1276
+ * a partially-broken block never breaks span-context parsing.
1277
+ */
1278
+ const asString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
1279
+ /**
1280
+ * Parse a debug block, accepting camelCase and snake_case. All ids are kept
1281
+ * VERBATIM: the producer emits the run's exact session / replay-trace /
1282
+ * cache-until strings (un-normalized — `LMNR_DEBUG_SESSION_ID` may be an
1283
+ * arbitrary non-UUID value), so the consumer must round-trip them unchanged or
1284
+ * a downstream run never joins the run. Keep line-comparable with the Python
1285
+ * `DebugContext.deserialize`.
1286
+ */
1287
+ const deserializeDebugContext = (data) => ({
1288
+ enabled: data.enabled === true,
1289
+ sessionId: asString(data.sessionId ?? data.session_id),
1290
+ replayTraceId: asString(data.replayTraceId ?? data.replay_trace_id),
1291
+ cacheUntil: asString(data.cacheUntil ?? data.cache_until)
1292
+ });
1293
+ const getDirname = () => {
1294
+ if (typeof __dirname !== "undefined") return __dirname;
1295
+ if (typeof import.meta.url !== "undefined") return path.dirname(fileURLToPath(import.meta.url));
1296
+ return process.cwd();
1297
+ };
1298
+ const MAX_MANUAL_SPAN_PAYLOAD_SIZE = 1024 * 1024 * 10;
1299
+ const TRUNCATION_SUFFIX = "...[Laminar: truncated]";
1300
+ /**
1301
+ * Cut an oversized span payload down to the limit rather than dropping it.
1302
+ *
1303
+ * Keeping the leading bytes preserves the start of the value, which is the useful part when
1304
+ * debugging. Mirrors `_truncate_payload` in the Python SDK.
1305
+ *
1306
+ * Callers pass the result of `JSON.stringify`, whose TS signature claims `string` but which
1307
+ * actually returns `undefined` for a top-level `undefined`, function, or symbol. Guard at
1308
+ * runtime and hand such values straight back: `setAttribute` treats them as a no-op, which is
1309
+ * the behaviour these payloads had before truncation existed.
1310
+ */
1311
+ const truncateSpanPayload = (serialized, kind) => {
1312
+ if (typeof serialized !== "string") return serialized;
1313
+ if (serialized.length <= 10485760) return serialized;
1314
+ logger.warn(`Laminar: span ${kind} is ${serialized.length} bytes, which exceeds the ${MAX_MANUAL_SPAN_PAYLOAD_SIZE} byte limit. Truncating to the limit; the recorded value will not be valid JSON.`);
1315
+ const keep = MAX_MANUAL_SPAN_PAYLOAD_SIZE - 23;
1316
+ return serialized.slice(0, keep) + TRUNCATION_SUFFIX;
1317
+ };
1318
+ const isOtelAttributeValueType = (value) => {
1319
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return true;
1320
+ if (Array.isArray(value)) {
1321
+ const allStrings = value.every((value) => value == null || typeof value === "string");
1322
+ const allNumbers = value.every((value) => value == null || typeof value === "number");
1323
+ const allBooleans = value.every((value) => value == null || typeof value === "boolean");
1324
+ return allStrings || allNumbers || allBooleans;
1415
1325
  }
1416
- get sql() {
1417
- return this._sql;
1326
+ return false;
1327
+ };
1328
+ const metadataToAttributes = (metadata) => Object.fromEntries(Object.entries(metadata).map(([key, value]) => {
1329
+ if (isOtelAttributeValueType(value)) return [`${ASSOCIATION_PROPERTIES}.metadata.${key}`, value];
1330
+ else return [`${ASSOCIATION_PROPERTIES}.metadata.${key}`, JSON.stringify(value)];
1331
+ }));
1332
+ /**
1333
+ * Get OTEL environment variable with priority order.
1334
+ * Checks in order:
1335
+ * 1. OTEL_EXPORTER_OTLP_TRACES_{varName}
1336
+ * 2. OTEL_EXPORTER_OTLP_{varName}
1337
+ * 3. OTEL_{varName}
1338
+ *
1339
+ * @param varName - The variable name (e.g., 'ENDPOINT', 'HEADERS', 'PROTOCOL')
1340
+ * @returns The environment variable value or undefined if not found
1341
+ */
1342
+ const getOtelEnvVar = (varName) => {
1343
+ const candidates = [
1344
+ `OTEL_EXPORTER_OTLP_TRACES_${varName}`,
1345
+ `OTEL_EXPORTER_OTLP_${varName}`,
1346
+ `OTEL_${varName}`
1347
+ ];
1348
+ for (const candidate of candidates) {
1349
+ const value = process?.env?.[candidate];
1350
+ if (value) return value;
1418
1351
  }
1419
- get tags() {
1420
- return this._tags;
1352
+ };
1353
+ /**
1354
+ * Check if OTEL configuration is available.
1355
+ * @returns true if OTEL endpoint is configured
1356
+ */
1357
+ const hasOtelConfig = () => !!getOtelEnvVar("ENDPOINT");
1358
+ /**
1359
+ * Parse OTEL headers string into a record object.
1360
+ * Format: key1=value1,key2=value2
1361
+ * Values are URL-decoded.
1362
+ *
1363
+ * @param headersStr - Headers string in OTEL format
1364
+ * @returns Parsed headers object
1365
+ */
1366
+ const parseOtelHeaders = (headersStr) => {
1367
+ if (!headersStr) return {};
1368
+ const headers = {};
1369
+ for (const pair of headersStr.split(",")) {
1370
+ const equalIndex = pair.indexOf("=");
1371
+ if (equalIndex !== -1) {
1372
+ const key = pair.substring(0, equalIndex).trim();
1373
+ const value = pair.substring(equalIndex + 1).trim();
1374
+ headers[key] = decodeURIComponent(value);
1375
+ }
1421
1376
  }
1422
- get traces() {
1423
- return this._traces;
1377
+ return headers;
1378
+ };
1379
+ /**
1380
+ * Validate that either Laminar API key or OTEL configuration is present.
1381
+ * Throws an error if neither is configured.
1382
+ *
1383
+ * @param apiKey - The Laminar API key (if provided)
1384
+ * @throws Error if neither API key nor OTEL configuration is present
1385
+ */
1386
+ const validateTracingConfig = (apiKey) => {
1387
+ if (!apiKey && !hasOtelConfig()) throw new Error("Please initialize the Laminar object with your project API key or set the LMNR_PROJECT_API_KEY environment variable, or configure OTEL environment variables (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, etc.)");
1388
+ };
1389
+ const loadEnv = (options) => {
1390
+ const nodeEnv = process.env.NODE_ENV || "development";
1391
+ const envDir = process.cwd();
1392
+ const envFiles = [
1393
+ ".env",
1394
+ ".env.local",
1395
+ `.env.${nodeEnv}`,
1396
+ `.env.${nodeEnv}.local`
1397
+ ];
1398
+ const logLevel = process.env.LMNR_LOG_LEVEL ?? "info";
1399
+ const verbose = ["debug", "trace"].includes(logLevel.trim().toLowerCase());
1400
+ const quiet = options?.quiet ?? !verbose;
1401
+ config({
1402
+ path: options?.paths ?? envFiles.map((envFile) => path.resolve(envDir, envFile)),
1403
+ quiet
1404
+ });
1405
+ };
1406
+ /**
1407
+ * Converts an API base URL to the frontend/web URL.
1408
+ * - Converts https://api.lmnr.ai to https://www.laminar.sh
1409
+ * - Removes trailing slashes
1410
+ * - For localhost/127.0.0.1, ensures a port is specified (defaults to 5667)
1411
+ *
1412
+ * @param baseUrl - The API base URL (defaults to "https://api.lmnr.ai")
1413
+ * @returns The frontend URL
1414
+ */
1415
+ const getFrontendUrl = (baseUrl, frontendPort) => {
1416
+ let url = baseUrl ?? "https://api.lmnr.ai";
1417
+ if (url === "https://api.lmnr.ai") url = "https://www.laminar.sh";
1418
+ url = url.replace(/\/$/, "");
1419
+ if (/localhost|127\.0\.0\.1/.test(url)) {
1420
+ const port = frontendPort ?? url.match(/:\d{1,5}$/g)?.[0]?.slice(1) ?? 5667;
1421
+ if (/:(\d{1,5})$/.test(url)) url = url.replace(/:\d{1,5}$/g, `:${port}`);
1422
+ else url = `${url}:${port}`;
1424
1423
  }
1424
+ return url;
1425
1425
  };
1426
1426
  //#endregion
1427
- export { SPAN_INSTRUMENTATION_SCOPE_VERSION as A, version$1 as B, LaminarAttributes as C, SPAN_IDS_PATH as D, SESSION_ID as E, SPAN_SDK_VERSION as F, DEBUG_SESSION_FILE as H, SPAN_TYPE as I, TRACE_HAS_BROWSER_SESSION as L, SPAN_LANGUAGE_VERSION as M, SPAN_OUTPUT as N, SPAN_INPUT as O, SPAN_PATH as P, TRACE_TYPE as R, HUMAN_EVALUATOR_OPTIONS as S, PARENT_SPAN_PATH as T, TracingLevel as U, DEBUG_SESSION_DIR as V, errorMessage as W, truncateSpanPayload as _, getDirname as a, ASSOCIATION_PROPERTIES as b, initializeLogger$1 as c, newUUID$1 as d, normalizeOtelSpanId as f, parseOtelHeaders as g, otelTraceIdToUUID$1 as h, deserializeLaminarSpanContext as i, SPAN_INSTRUMENTATION_SOURCE as j, SPAN_INSTRUMENTATION_SCOPE_NAME as k, loadEnv$1 as l, otelSpanIdToUUID$1 as m, NIL_UUID as n, getFrontendUrl as o, normalizeOtelTraceId as p, Semaphore as r, getOtelEnvVar as s, LaminarClient as t, metadataToAttributes as u, tryToOtelSpanContext as v, PARENT_SPAN_IDS_PATH as w, ASSOCIATION_PROPERTIES_OVERRIDES as x, validateTracingConfig as y, USER_ID as z };
1427
+ export { SPAN_INSTRUMENTATION_SOURCE as A, LaminarClient as B, PARENT_SPAN_IDS_PATH as C, SPAN_INPUT as D, SPAN_IDS_PATH as E, SPAN_TYPE as F, DEBUG_SESSION_FILE as H, TRACE_HAS_BROWSER_SESSION as I, TRACE_TYPE as L, SPAN_OUTPUT as M, SPAN_PATH as N, SPAN_INSTRUMENTATION_SCOPE_NAME as O, SPAN_SDK_VERSION as P, USER_ID as R, LaminarAttributes as S, SESSION_ID as T, TracingLevel as U, DEBUG_SESSION_DIR as V, errorMessage as W, tryToOtelSpanContext as _, getFrontendUrl as a, ASSOCIATION_PROPERTIES_OVERRIDES as b, loadEnv as c, normalizeOtelSpanId as d, normalizeOtelTraceId as f, truncateSpanPayload as g, parseOtelHeaders as h, getDirname as i, SPAN_LANGUAGE_VERSION as j, SPAN_INSTRUMENTATION_SCOPE_VERSION as k, metadataToAttributes as l, otelTraceIdToUUID as m, Semaphore as n, getOtelEnvVar as o, otelSpanIdToUUID as p, deserializeLaminarSpanContext as r, initializeLogger as s, NIL_UUID as t, newUUID as u, validateTracingConfig as v, PARENT_SPAN_PATH as w, HUMAN_EVALUATOR_OPTIONS as x, ASSOCIATION_PROPERTIES as y, version as z };
1428
1428
 
1429
- //# sourceMappingURL=dist-Cjres0a5.mjs.map
1429
+ //# sourceMappingURL=utils-UmlDPMxe.mjs.map