@lmnr-ai/client 0.8.25 → 0.8.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +125 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -31
- package/dist/index.d.mts +83 -31
- package/dist/index.mjs +125 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Datapoint, Dataset, EvaluationDatapoint, GetDatapointsResponse, InitEvaluationResponse, PushDatapointsResponse,
|
|
1
|
+
import { Datapoint, Dataset, EvaluationDatapoint, GetDatapointsResponse, InitEvaluationResponse, PushDatapointsResponse, StringUUID } from "@lmnr-ai/types";
|
|
2
2
|
|
|
3
3
|
//#region src/resources/index.d.ts
|
|
4
4
|
declare class BaseResource {
|
|
@@ -260,46 +260,37 @@ declare class EvaluatorsResource extends BaseResource {
|
|
|
260
260
|
declare class RolloutSessionsResource extends BaseResource {
|
|
261
261
|
constructor(baseHttpUrl: string, projectApiKey: string);
|
|
262
262
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
263
|
+
* Idempotently register (upsert) a debug session on the backend, keyed on the
|
|
264
|
+
* SDK-supplied session id. The backend stores the row so the session is
|
|
265
|
+
* visible in the UI; a null/omitted name never clobbers a name set elsewhere.
|
|
266
|
+
*
|
|
267
|
+
* Returns the backend-resolved `projectId` (derived from the API key) so the
|
|
268
|
+
* caller can build the debugger URL; null if the body can't be parsed.
|
|
265
269
|
*/
|
|
266
|
-
|
|
270
|
+
register({
|
|
267
271
|
sessionId,
|
|
268
|
-
name
|
|
269
|
-
params,
|
|
270
|
-
signal
|
|
272
|
+
name
|
|
271
273
|
}: {
|
|
272
274
|
sessionId: string;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
setStatus({
|
|
275
|
+
name?: string;
|
|
276
|
+
}): Promise<string | null>;
|
|
277
|
+
/**
|
|
278
|
+
* Rename an existing debug session. Update-only: the backend returns 404 (and
|
|
279
|
+
* this throws) when the session id is unknown for the project, so a mistyped
|
|
280
|
+
* id surfaces as an error rather than silently creating a session. Creation
|
|
281
|
+
* stays the SDK's job via {@link register}.
|
|
282
|
+
*/
|
|
283
|
+
setName({
|
|
283
284
|
sessionId,
|
|
284
|
-
|
|
285
|
+
name
|
|
285
286
|
}: {
|
|
286
287
|
sessionId: string;
|
|
287
|
-
|
|
288
|
+
name: string;
|
|
288
289
|
}): Promise<void>;
|
|
289
|
-
|
|
290
|
-
sessionId
|
|
291
|
-
span
|
|
290
|
+
delete({
|
|
291
|
+
sessionId
|
|
292
292
|
}: {
|
|
293
293
|
sessionId: string;
|
|
294
|
-
span: {
|
|
295
|
-
name: string;
|
|
296
|
-
startTime: string;
|
|
297
|
-
spanId: string;
|
|
298
|
-
traceId: string;
|
|
299
|
-
parentSpanId: string | undefined;
|
|
300
|
-
attributes: Record<string, any>;
|
|
301
|
-
spanType: SpanType;
|
|
302
|
-
};
|
|
303
294
|
}): Promise<void>;
|
|
304
295
|
}
|
|
305
296
|
//#endregion
|
|
@@ -348,6 +339,65 @@ declare class TagsResource extends BaseResource {
|
|
|
348
339
|
tag(trace_id: string, tags: string[] | string): Promise<any>;
|
|
349
340
|
}
|
|
350
341
|
//#endregion
|
|
342
|
+
//#region src/resources/traces.d.ts
|
|
343
|
+
declare class TracesResource extends BaseResource {
|
|
344
|
+
/** Resource for post-factum operations on existing traces. */
|
|
345
|
+
constructor(baseHttpUrl: string, projectApiKey: string);
|
|
346
|
+
/**
|
|
347
|
+
* Push a metadata patch to an existing trace.
|
|
348
|
+
*
|
|
349
|
+
* The patch is shallow-merged server-side into the trace's existing metadata
|
|
350
|
+
* (`existing || patch`, last-write-wins per top-level key). Useful for
|
|
351
|
+
* attaching post-factum signals — quality scores, human edits, triage labels —
|
|
352
|
+
* to a trace that has already finished. The patch does NOT extend `endTime`
|
|
353
|
+
* or change tokens / cost / top span / tags / span names. `numSpans` is
|
|
354
|
+
* incremented by 1 (paid by the virtual span that carried the patch through
|
|
355
|
+
* the ingestion queue) so the new ClickHouse row beats the prior version on
|
|
356
|
+
* `ReplacingMergeTree(numSpans)`. No row is added to the `spans` table.
|
|
357
|
+
*
|
|
358
|
+
* Compared to `Laminar.setTraceMetadata` (which sets metadata on the
|
|
359
|
+
* currently in-flight trace via OpenTelemetry attributes), this method
|
|
360
|
+
* operates on a finished trace by trace id, so it must be called after the
|
|
361
|
+
* trace has been flushed.
|
|
362
|
+
*
|
|
363
|
+
* A 404 response (the trace was not found in the project — typically because
|
|
364
|
+
* it has not been flushed yet) is logged as a warning and the call returns
|
|
365
|
+
* without throwing, since the 404 may be expected when pushing too soon
|
|
366
|
+
* after the trace run. Pass `failOnNotFound: true` to throw instead (e.g.
|
|
367
|
+
* CLI callers that must report the failure). Any other non-OK status throws.
|
|
368
|
+
*
|
|
369
|
+
* @param traceId - The trace id to push metadata to. Accepts a UUID string
|
|
370
|
+
* or a 32-char OTel hex trace id.
|
|
371
|
+
* @param metadata - The metadata patch. Top-level keys are merged into the
|
|
372
|
+
* trace's existing metadata. Must be non-empty (the server rejects empty
|
|
373
|
+
* patches with 400).
|
|
374
|
+
* @param options - `failOnNotFound`: throw on 404 instead of warn-and-return.
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* import { Laminar, observe, LaminarClient } from "@lmnr-ai/lmnr";
|
|
378
|
+
* Laminar.initialize();
|
|
379
|
+
* const client = new LaminarClient();
|
|
380
|
+
*
|
|
381
|
+
* let traceId: string | null = null;
|
|
382
|
+
* await observe({ name: "generate" }, async () => {
|
|
383
|
+
* traceId = await Laminar.getTraceId();
|
|
384
|
+
* });
|
|
385
|
+
* await Laminar.flush();
|
|
386
|
+
*
|
|
387
|
+
* if (traceId) {
|
|
388
|
+
* await client.traces.pushMetadata(traceId, {
|
|
389
|
+
* score: 0.85,
|
|
390
|
+
* reviewer: "alice",
|
|
391
|
+
* needsReview: false,
|
|
392
|
+
* });
|
|
393
|
+
* }
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
pushMetadata(traceId: string, metadata: Record<string, unknown>, options?: {
|
|
397
|
+
failOnNotFound?: boolean;
|
|
398
|
+
}): Promise<void>;
|
|
399
|
+
}
|
|
400
|
+
//#endregion
|
|
351
401
|
//#region src/index.d.ts
|
|
352
402
|
declare class LaminarClient {
|
|
353
403
|
private baseUrl;
|
|
@@ -359,6 +409,7 @@ declare class LaminarClient {
|
|
|
359
409
|
private _rolloutSessions;
|
|
360
410
|
private _sql;
|
|
361
411
|
private _tags;
|
|
412
|
+
private _traces;
|
|
362
413
|
constructor({
|
|
363
414
|
baseUrl,
|
|
364
415
|
projectApiKey,
|
|
@@ -375,6 +426,7 @@ declare class LaminarClient {
|
|
|
375
426
|
get rolloutSessions(): RolloutSessionsResource;
|
|
376
427
|
get sql(): SqlResource;
|
|
377
428
|
get tags(): TagsResource;
|
|
429
|
+
get traces(): TracesResource;
|
|
378
430
|
}
|
|
379
431
|
//#endregion
|
|
380
432
|
export { LaminarClient };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Datapoint, Dataset, EvaluationDatapoint, GetDatapointsResponse, InitEvaluationResponse, PushDatapointsResponse,
|
|
1
|
+
import { Datapoint, Dataset, EvaluationDatapoint, GetDatapointsResponse, InitEvaluationResponse, PushDatapointsResponse, StringUUID } from "@lmnr-ai/types";
|
|
2
2
|
|
|
3
3
|
//#region src/resources/index.d.ts
|
|
4
4
|
declare class BaseResource {
|
|
@@ -260,46 +260,37 @@ declare class EvaluatorsResource extends BaseResource {
|
|
|
260
260
|
declare class RolloutSessionsResource extends BaseResource {
|
|
261
261
|
constructor(baseHttpUrl: string, projectApiKey: string);
|
|
262
262
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
263
|
+
* Idempotently register (upsert) a debug session on the backend, keyed on the
|
|
264
|
+
* SDK-supplied session id. The backend stores the row so the session is
|
|
265
|
+
* visible in the UI; a null/omitted name never clobbers a name set elsewhere.
|
|
266
|
+
*
|
|
267
|
+
* Returns the backend-resolved `projectId` (derived from the API key) so the
|
|
268
|
+
* caller can build the debugger URL; null if the body can't be parsed.
|
|
265
269
|
*/
|
|
266
|
-
|
|
270
|
+
register({
|
|
267
271
|
sessionId,
|
|
268
|
-
name
|
|
269
|
-
params,
|
|
270
|
-
signal
|
|
272
|
+
name
|
|
271
273
|
}: {
|
|
272
274
|
sessionId: string;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
setStatus({
|
|
275
|
+
name?: string;
|
|
276
|
+
}): Promise<string | null>;
|
|
277
|
+
/**
|
|
278
|
+
* Rename an existing debug session. Update-only: the backend returns 404 (and
|
|
279
|
+
* this throws) when the session id is unknown for the project, so a mistyped
|
|
280
|
+
* id surfaces as an error rather than silently creating a session. Creation
|
|
281
|
+
* stays the SDK's job via {@link register}.
|
|
282
|
+
*/
|
|
283
|
+
setName({
|
|
283
284
|
sessionId,
|
|
284
|
-
|
|
285
|
+
name
|
|
285
286
|
}: {
|
|
286
287
|
sessionId: string;
|
|
287
|
-
|
|
288
|
+
name: string;
|
|
288
289
|
}): Promise<void>;
|
|
289
|
-
|
|
290
|
-
sessionId
|
|
291
|
-
span
|
|
290
|
+
delete({
|
|
291
|
+
sessionId
|
|
292
292
|
}: {
|
|
293
293
|
sessionId: string;
|
|
294
|
-
span: {
|
|
295
|
-
name: string;
|
|
296
|
-
startTime: string;
|
|
297
|
-
spanId: string;
|
|
298
|
-
traceId: string;
|
|
299
|
-
parentSpanId: string | undefined;
|
|
300
|
-
attributes: Record<string, any>;
|
|
301
|
-
spanType: SpanType;
|
|
302
|
-
};
|
|
303
294
|
}): Promise<void>;
|
|
304
295
|
}
|
|
305
296
|
//#endregion
|
|
@@ -348,6 +339,65 @@ declare class TagsResource extends BaseResource {
|
|
|
348
339
|
tag(trace_id: string, tags: string[] | string): Promise<any>;
|
|
349
340
|
}
|
|
350
341
|
//#endregion
|
|
342
|
+
//#region src/resources/traces.d.ts
|
|
343
|
+
declare class TracesResource extends BaseResource {
|
|
344
|
+
/** Resource for post-factum operations on existing traces. */
|
|
345
|
+
constructor(baseHttpUrl: string, projectApiKey: string);
|
|
346
|
+
/**
|
|
347
|
+
* Push a metadata patch to an existing trace.
|
|
348
|
+
*
|
|
349
|
+
* The patch is shallow-merged server-side into the trace's existing metadata
|
|
350
|
+
* (`existing || patch`, last-write-wins per top-level key). Useful for
|
|
351
|
+
* attaching post-factum signals — quality scores, human edits, triage labels —
|
|
352
|
+
* to a trace that has already finished. The patch does NOT extend `endTime`
|
|
353
|
+
* or change tokens / cost / top span / tags / span names. `numSpans` is
|
|
354
|
+
* incremented by 1 (paid by the virtual span that carried the patch through
|
|
355
|
+
* the ingestion queue) so the new ClickHouse row beats the prior version on
|
|
356
|
+
* `ReplacingMergeTree(numSpans)`. No row is added to the `spans` table.
|
|
357
|
+
*
|
|
358
|
+
* Compared to `Laminar.setTraceMetadata` (which sets metadata on the
|
|
359
|
+
* currently in-flight trace via OpenTelemetry attributes), this method
|
|
360
|
+
* operates on a finished trace by trace id, so it must be called after the
|
|
361
|
+
* trace has been flushed.
|
|
362
|
+
*
|
|
363
|
+
* A 404 response (the trace was not found in the project — typically because
|
|
364
|
+
* it has not been flushed yet) is logged as a warning and the call returns
|
|
365
|
+
* without throwing, since the 404 may be expected when pushing too soon
|
|
366
|
+
* after the trace run. Pass `failOnNotFound: true` to throw instead (e.g.
|
|
367
|
+
* CLI callers that must report the failure). Any other non-OK status throws.
|
|
368
|
+
*
|
|
369
|
+
* @param traceId - The trace id to push metadata to. Accepts a UUID string
|
|
370
|
+
* or a 32-char OTel hex trace id.
|
|
371
|
+
* @param metadata - The metadata patch. Top-level keys are merged into the
|
|
372
|
+
* trace's existing metadata. Must be non-empty (the server rejects empty
|
|
373
|
+
* patches with 400).
|
|
374
|
+
* @param options - `failOnNotFound`: throw on 404 instead of warn-and-return.
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* import { Laminar, observe, LaminarClient } from "@lmnr-ai/lmnr";
|
|
378
|
+
* Laminar.initialize();
|
|
379
|
+
* const client = new LaminarClient();
|
|
380
|
+
*
|
|
381
|
+
* let traceId: string | null = null;
|
|
382
|
+
* await observe({ name: "generate" }, async () => {
|
|
383
|
+
* traceId = await Laminar.getTraceId();
|
|
384
|
+
* });
|
|
385
|
+
* await Laminar.flush();
|
|
386
|
+
*
|
|
387
|
+
* if (traceId) {
|
|
388
|
+
* await client.traces.pushMetadata(traceId, {
|
|
389
|
+
* score: 0.85,
|
|
390
|
+
* reviewer: "alice",
|
|
391
|
+
* needsReview: false,
|
|
392
|
+
* });
|
|
393
|
+
* }
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
pushMetadata(traceId: string, metadata: Record<string, unknown>, options?: {
|
|
397
|
+
failOnNotFound?: boolean;
|
|
398
|
+
}): Promise<void>;
|
|
399
|
+
}
|
|
400
|
+
//#endregion
|
|
351
401
|
//#region src/index.d.ts
|
|
352
402
|
declare class LaminarClient {
|
|
353
403
|
private baseUrl;
|
|
@@ -359,6 +409,7 @@ declare class LaminarClient {
|
|
|
359
409
|
private _rolloutSessions;
|
|
360
410
|
private _sql;
|
|
361
411
|
private _tags;
|
|
412
|
+
private _traces;
|
|
362
413
|
constructor({
|
|
363
414
|
baseUrl,
|
|
364
415
|
projectApiKey,
|
|
@@ -375,6 +426,7 @@ declare class LaminarClient {
|
|
|
375
426
|
get rolloutSessions(): RolloutSessionsResource;
|
|
376
427
|
get sql(): SqlResource;
|
|
377
428
|
get tags(): TagsResource;
|
|
429
|
+
get traces(): TracesResource;
|
|
378
430
|
}
|
|
379
431
|
//#endregion
|
|
380
432
|
export { LaminarClient };
|
package/dist/index.mjs
CHANGED
|
@@ -3,8 +3,9 @@ import * as path from "path";
|
|
|
3
3
|
import pino from "pino";
|
|
4
4
|
import { PinoPretty } from "pino-pretty";
|
|
5
5
|
import { v4 } from "uuid";
|
|
6
|
+
import { errorMessage } from "@lmnr-ai/types";
|
|
6
7
|
//#region package.json
|
|
7
|
-
var version = "0.8.
|
|
8
|
+
var version = "0.8.27";
|
|
8
9
|
//#endregion
|
|
9
10
|
//#region src/version.ts
|
|
10
11
|
function getLangVersion() {
|
|
@@ -69,7 +70,7 @@ function initializeLogger(options) {
|
|
|
69
70
|
minimumLevel: level
|
|
70
71
|
}));
|
|
71
72
|
}
|
|
72
|
-
const logger$
|
|
73
|
+
const logger$4 = initializeLogger();
|
|
73
74
|
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);
|
|
74
75
|
const newUUID = () => {
|
|
75
76
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
@@ -78,9 +79,9 @@ const newUUID = () => {
|
|
|
78
79
|
const otelSpanIdToUUID = (spanId) => {
|
|
79
80
|
let id = spanId.toLowerCase();
|
|
80
81
|
if (id.startsWith("0x")) id = id.slice(2);
|
|
81
|
-
if (id.length !== 16) logger$
|
|
82
|
+
if (id.length !== 16) logger$4.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);
|
|
82
83
|
if (!/^[0-9a-f]+$/.test(id)) {
|
|
83
|
-
logger$
|
|
84
|
+
logger$4.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);
|
|
84
85
|
return newUUID();
|
|
85
86
|
}
|
|
86
87
|
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");
|
|
@@ -88,9 +89,9 @@ const otelSpanIdToUUID = (spanId) => {
|
|
|
88
89
|
const otelTraceIdToUUID = (traceId) => {
|
|
89
90
|
let id = traceId.toLowerCase();
|
|
90
91
|
if (id.startsWith("0x")) id = id.slice(2);
|
|
91
|
-
if (id.length !== 32) logger$
|
|
92
|
+
if (id.length !== 32) logger$4.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);
|
|
92
93
|
if (!/^[0-9a-f]+$/.test(id)) {
|
|
93
|
-
logger$
|
|
94
|
+
logger$4.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);
|
|
94
95
|
return newUUID();
|
|
95
96
|
}
|
|
96
97
|
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");
|
|
@@ -120,7 +121,7 @@ const loadEnv = (options) => {
|
|
|
120
121
|
};
|
|
121
122
|
//#endregion
|
|
122
123
|
//#region src/resources/datasets.ts
|
|
123
|
-
const logger$
|
|
124
|
+
const logger$3 = initializeLogger();
|
|
124
125
|
const DEFAULT_DATASET_PULL_LIMIT = 100;
|
|
125
126
|
const DEFAULT_DATASET_PUSH_BATCH_SIZE = 100;
|
|
126
127
|
var DatasetsResource = class extends BaseResource {
|
|
@@ -175,7 +176,7 @@ var DatasetsResource = class extends BaseResource {
|
|
|
175
176
|
let response;
|
|
176
177
|
for (let i = 0; i < points.length; i += batchSize) {
|
|
177
178
|
const batchNum = Math.floor(i / batchSize) + 1;
|
|
178
|
-
logger$
|
|
179
|
+
logger$3.debug(`Pushing batch ${batchNum} of ${totalBatches}`);
|
|
179
180
|
const batch = points.slice(i, i + batchSize);
|
|
180
181
|
const fetchResponse = await fetch(this.baseHttpUrl + "/v1/datasets/datapoints", {
|
|
181
182
|
method: "POST",
|
|
@@ -225,7 +226,7 @@ var DatasetsResource = class extends BaseResource {
|
|
|
225
226
|
};
|
|
226
227
|
//#endregion
|
|
227
228
|
//#region src/resources/evals.ts
|
|
228
|
-
const logger = initializeLogger();
|
|
229
|
+
const logger$2 = initializeLogger();
|
|
229
230
|
const INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH = 16e6;
|
|
230
231
|
var EvalsResource = class extends BaseResource {
|
|
231
232
|
constructor(baseHttpUrl, projectApiKey) {
|
|
@@ -361,7 +362,7 @@ var EvalsResource = class extends BaseResource {
|
|
|
361
362
|
* @returns {Promise<GetDatapointsResponse>} Response from the datapoint retrieval
|
|
362
363
|
*/
|
|
363
364
|
async getDatapoints({ datasetName, offset, limit }) {
|
|
364
|
-
logger.warn("evals.getDatapoints() is deprecated. Use client.datasets.pull() instead.");
|
|
365
|
+
logger$2.warn("evals.getDatapoints() is deprecated. Use client.datasets.pull() instead.");
|
|
365
366
|
const params = new URLSearchParams({
|
|
366
367
|
name: datasetName,
|
|
367
368
|
offset: offset.toString(),
|
|
@@ -378,7 +379,7 @@ var EvalsResource = class extends BaseResource {
|
|
|
378
379
|
let length = initialLength;
|
|
379
380
|
let lastResponse = null;
|
|
380
381
|
for (let i = 0; i < maxRetries; i++) {
|
|
381
|
-
logger.debug(`Retrying save datapoints... ${i + 1} of ${maxRetries}, length: ${length}`);
|
|
382
|
+
logger$2.debug(`Retrying save datapoints... ${i + 1} of ${maxRetries}, length: ${length}`);
|
|
382
383
|
const response = await fetch(this.baseHttpUrl + `/v1/evals/${evalId}/datapoints`, {
|
|
383
384
|
method: "POST",
|
|
384
385
|
headers: this.headers(),
|
|
@@ -464,60 +465,51 @@ var EvaluatorsResource = class extends BaseResource {
|
|
|
464
465
|
};
|
|
465
466
|
//#endregion
|
|
466
467
|
//#region src/resources/rollout-sessions.ts
|
|
468
|
+
const logger$1 = initializeLogger();
|
|
467
469
|
var RolloutSessionsResource = class extends BaseResource {
|
|
468
470
|
constructor(baseHttpUrl, projectApiKey) {
|
|
469
471
|
super(baseHttpUrl, projectApiKey);
|
|
470
472
|
}
|
|
471
473
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
+
* Idempotently register (upsert) a debug session on the backend, keyed on the
|
|
475
|
+
* SDK-supplied session id. The backend stores the row so the session is
|
|
476
|
+
* visible in the UI; a null/omitted name never clobbers a name set elsewhere.
|
|
477
|
+
*
|
|
478
|
+
* Returns the backend-resolved `projectId` (derived from the API key) so the
|
|
479
|
+
* caller can build the debugger URL; null if the body can't be parsed.
|
|
474
480
|
*/
|
|
475
|
-
async
|
|
481
|
+
async register({ sessionId, name }) {
|
|
476
482
|
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}`, {
|
|
477
483
|
method: "POST",
|
|
478
|
-
headers:
|
|
479
|
-
|
|
480
|
-
"Accept": "text/event-stream"
|
|
481
|
-
},
|
|
482
|
-
body: JSON.stringify({
|
|
483
|
-
name,
|
|
484
|
-
params
|
|
485
|
-
}),
|
|
486
|
-
signal
|
|
487
|
-
});
|
|
488
|
-
if (!response.ok) throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);
|
|
489
|
-
if (!response.body) throw new Error("No response body");
|
|
490
|
-
return response;
|
|
491
|
-
}
|
|
492
|
-
async delete({ sessionId }) {
|
|
493
|
-
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}`, {
|
|
494
|
-
method: "DELETE",
|
|
495
|
-
headers: this.headers()
|
|
484
|
+
headers: this.headers(),
|
|
485
|
+
body: JSON.stringify({ name })
|
|
496
486
|
});
|
|
497
487
|
if (!response.ok) await this.handleError(response);
|
|
488
|
+
try {
|
|
489
|
+
return (await response.json()).projectId ?? null;
|
|
490
|
+
} catch (e) {
|
|
491
|
+
logger$1.warn(`Failed to parse rollout register response: ${errorMessage(e)}`);
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
498
494
|
}
|
|
499
|
-
|
|
500
|
-
|
|
495
|
+
/**
|
|
496
|
+
* Rename an existing debug session. Update-only: the backend returns 404 (and
|
|
497
|
+
* this throws) when the session id is unknown for the project, so a mistyped
|
|
498
|
+
* id surfaces as an error rather than silently creating a session. Creation
|
|
499
|
+
* stays the SDK's job via {@link register}.
|
|
500
|
+
*/
|
|
501
|
+
async setName({ sessionId, name }) {
|
|
502
|
+
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}/name`, {
|
|
501
503
|
method: "PATCH",
|
|
502
504
|
headers: this.headers(),
|
|
503
|
-
body: JSON.stringify({
|
|
505
|
+
body: JSON.stringify({ name })
|
|
504
506
|
});
|
|
505
507
|
if (!response.ok) await this.handleError(response);
|
|
506
508
|
}
|
|
507
|
-
async
|
|
508
|
-
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}
|
|
509
|
-
method: "
|
|
510
|
-
headers: this.headers()
|
|
511
|
-
body: JSON.stringify({
|
|
512
|
-
type: "spanStart",
|
|
513
|
-
spanId: otelSpanIdToUUID(span.spanId),
|
|
514
|
-
traceId: otelTraceIdToUUID(span.traceId),
|
|
515
|
-
parentSpanId: span.parentSpanId ? otelSpanIdToUUID(span.parentSpanId) : void 0,
|
|
516
|
-
attributes: span.attributes,
|
|
517
|
-
startTime: span.startTime,
|
|
518
|
-
name: span.name,
|
|
519
|
-
spanType: span.spanType
|
|
520
|
-
})
|
|
509
|
+
async delete({ sessionId }) {
|
|
510
|
+
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}`, {
|
|
511
|
+
method: "DELETE",
|
|
512
|
+
headers: this.headers()
|
|
521
513
|
});
|
|
522
514
|
if (!response.ok) await this.handleError(response);
|
|
523
515
|
}
|
|
@@ -599,6 +591,86 @@ var TagsResource = class extends BaseResource {
|
|
|
599
591
|
}
|
|
600
592
|
};
|
|
601
593
|
//#endregion
|
|
594
|
+
//#region src/resources/traces.ts
|
|
595
|
+
/** Resource for post-factum operations on existing traces. */
|
|
596
|
+
const logger = initializeLogger();
|
|
597
|
+
var TracesResource = class extends BaseResource {
|
|
598
|
+
/** Resource for post-factum operations on existing traces. */
|
|
599
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
600
|
+
super(baseHttpUrl, projectApiKey);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Push a metadata patch to an existing trace.
|
|
604
|
+
*
|
|
605
|
+
* The patch is shallow-merged server-side into the trace's existing metadata
|
|
606
|
+
* (`existing || patch`, last-write-wins per top-level key). Useful for
|
|
607
|
+
* attaching post-factum signals — quality scores, human edits, triage labels —
|
|
608
|
+
* to a trace that has already finished. The patch does NOT extend `endTime`
|
|
609
|
+
* or change tokens / cost / top span / tags / span names. `numSpans` is
|
|
610
|
+
* incremented by 1 (paid by the virtual span that carried the patch through
|
|
611
|
+
* the ingestion queue) so the new ClickHouse row beats the prior version on
|
|
612
|
+
* `ReplacingMergeTree(numSpans)`. No row is added to the `spans` table.
|
|
613
|
+
*
|
|
614
|
+
* Compared to `Laminar.setTraceMetadata` (which sets metadata on the
|
|
615
|
+
* currently in-flight trace via OpenTelemetry attributes), this method
|
|
616
|
+
* operates on a finished trace by trace id, so it must be called after the
|
|
617
|
+
* trace has been flushed.
|
|
618
|
+
*
|
|
619
|
+
* A 404 response (the trace was not found in the project — typically because
|
|
620
|
+
* it has not been flushed yet) is logged as a warning and the call returns
|
|
621
|
+
* without throwing, since the 404 may be expected when pushing too soon
|
|
622
|
+
* after the trace run. Pass `failOnNotFound: true` to throw instead (e.g.
|
|
623
|
+
* CLI callers that must report the failure). Any other non-OK status throws.
|
|
624
|
+
*
|
|
625
|
+
* @param traceId - The trace id to push metadata to. Accepts a UUID string
|
|
626
|
+
* or a 32-char OTel hex trace id.
|
|
627
|
+
* @param metadata - The metadata patch. Top-level keys are merged into the
|
|
628
|
+
* trace's existing metadata. Must be non-empty (the server rejects empty
|
|
629
|
+
* patches with 400).
|
|
630
|
+
* @param options - `failOnNotFound`: throw on 404 instead of warn-and-return.
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* import { Laminar, observe, LaminarClient } from "@lmnr-ai/lmnr";
|
|
634
|
+
* Laminar.initialize();
|
|
635
|
+
* const client = new LaminarClient();
|
|
636
|
+
*
|
|
637
|
+
* let traceId: string | null = null;
|
|
638
|
+
* await observe({ name: "generate" }, async () => {
|
|
639
|
+
* traceId = await Laminar.getTraceId();
|
|
640
|
+
* });
|
|
641
|
+
* await Laminar.flush();
|
|
642
|
+
*
|
|
643
|
+
* if (traceId) {
|
|
644
|
+
* await client.traces.pushMetadata(traceId, {
|
|
645
|
+
* score: 0.85,
|
|
646
|
+
* reviewer: "alice",
|
|
647
|
+
* needsReview: false,
|
|
648
|
+
* });
|
|
649
|
+
* }
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
async pushMetadata(traceId, metadata, options) {
|
|
653
|
+
if (!metadata || Object.keys(metadata).length === 0) throw new Error("metadata must be a non-empty object");
|
|
654
|
+
const formattedTraceId = isStringUUID(traceId) ? traceId : otelTraceIdToUUID(traceId);
|
|
655
|
+
const url = this.baseHttpUrl + "/v1/traces/metadata";
|
|
656
|
+
const response = await fetch(url, {
|
|
657
|
+
method: "POST",
|
|
658
|
+
headers: this.headers(),
|
|
659
|
+
body: JSON.stringify({
|
|
660
|
+
traceId: formattedTraceId,
|
|
661
|
+
metadata
|
|
662
|
+
})
|
|
663
|
+
});
|
|
664
|
+
if (response.status === 404) {
|
|
665
|
+
const message = `Trace ${formattedTraceId} not found. The trace may not have been flushed yet — call await Laminar.flush() and retry.`;
|
|
666
|
+
if (options?.failOnNotFound) throw new Error(message);
|
|
667
|
+
logger.warn(message);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (!response.ok) await this.handleError(response);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
//#endregion
|
|
602
674
|
//#region src/index.ts
|
|
603
675
|
var LaminarClient = class {
|
|
604
676
|
constructor({ baseUrl, projectApiKey, port } = {}) {
|
|
@@ -614,6 +686,7 @@ var LaminarClient = class {
|
|
|
614
686
|
this._rolloutSessions = new RolloutSessionsResource(this.baseUrl, this.projectApiKey);
|
|
615
687
|
this._sql = new SqlResource(this.baseUrl, this.projectApiKey);
|
|
616
688
|
this._tags = new TagsResource(this.baseUrl, this.projectApiKey);
|
|
689
|
+
this._traces = new TracesResource(this.baseUrl, this.projectApiKey);
|
|
617
690
|
}
|
|
618
691
|
get browserEvents() {
|
|
619
692
|
return this._browserEvents;
|
|
@@ -636,6 +709,9 @@ var LaminarClient = class {
|
|
|
636
709
|
get tags() {
|
|
637
710
|
return this._tags;
|
|
638
711
|
}
|
|
712
|
+
get traces() {
|
|
713
|
+
return this._traces;
|
|
714
|
+
}
|
|
639
715
|
};
|
|
640
716
|
//#endregion
|
|
641
717
|
export { LaminarClient };
|