@lmnr-ai/client 0.8.26 → 0.8.28

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Datapoint, Dataset, EvaluationDatapoint, GetDatapointsResponse, InitEvaluationResponse, PushDatapointsResponse, RolloutParam, SpanType, StringUUID } from "@lmnr-ai/types";
1
+ import { CachedSpan, 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 {
@@ -257,49 +257,83 @@ declare class EvaluatorsResource extends BaseResource {
257
257
  }
258
258
  //#endregion
259
259
  //#region src/resources/rollout-sessions.d.ts
260
+ /**
261
+ * Result of a debug-replay cache lookup (debug-replay v2).
262
+ *
263
+ * - `hit` — the server served a cached response for this input hash; replay it
264
+ * and mark the span CACHED.
265
+ * - `miss` — no cache entry; the caller latches process-wide live mode and runs
266
+ * every subsequent call live.
267
+ * - `live` — run THIS call live without latching (server COLD warmup-timeout
268
+ * degrade, or any non-OK / transport error here).
269
+ */
270
+ type CacheOutcome = {
271
+ kind: "hit";
272
+ cached: CachedSpan;
273
+ } | {
274
+ kind: "miss";
275
+ } | {
276
+ kind: "live";
277
+ };
260
278
  declare class RolloutSessionsResource extends BaseResource {
261
279
  constructor(baseHttpUrl: string, projectApiKey: string);
262
280
  /**
263
- * Connects to the SSE stream for rollout debugging sessions
264
- * Returns the Response object for streaming SSE events
281
+ * Idempotently register (upsert) a debug session on the backend, keyed on the
282
+ * SDK-supplied session id. The backend stores the row so the session is
283
+ * visible in the UI; a null/omitted name never clobbers a name set elsewhere.
284
+ *
285
+ * Returns the backend-resolved `projectId` (derived from the API key) so the
286
+ * caller can build the debugger URL; null if the body can't be parsed.
265
287
  */
266
- connect({
288
+ register({
267
289
  sessionId,
268
- name,
269
- params,
270
- signal
290
+ name
271
291
  }: {
272
292
  sessionId: string;
273
- params: RolloutParam[];
274
- name: string;
275
- signal?: AbortSignal;
276
- }): Promise<Response>;
277
- delete({
278
- sessionId
293
+ name?: string;
294
+ }): Promise<string | null>;
295
+ /**
296
+ * Rename an existing debug session. Update-only: the backend returns 404 (and
297
+ * this throws) when the session id is unknown for the project, so a mistyped
298
+ * id surfaces as an error rather than silently creating a session. Creation
299
+ * stays the SDK's job via {@link register}.
300
+ */
301
+ setName({
302
+ sessionId,
303
+ name
279
304
  }: {
280
305
  sessionId: string;
306
+ name: string;
281
307
  }): Promise<void>;
282
- setStatus({
308
+ /**
309
+ * Look up the debug-replay cache for a single LLM call (debug-replay v2).
310
+ *
311
+ * The server is keyed by `inputHash` (hex blake3 of the canonicalized,
312
+ * system-stripped input messages). It returns one of three outcomes:
313
+ * - `{ outcome: "hit", response }` — a cached response to replay.
314
+ * - `{ outcome: "miss" }` — no entry; caller latches live mode.
315
+ * - `{ outcome: "live" }` — run this call live (COLD degrade).
316
+ *
317
+ * Error posture: a non-OK response or a transport error degrades to
318
+ * `{ kind: "live" }` for THIS call only — it never throws and never latches
319
+ * the process-wide live flag (only a real MISS does that). This keeps a flaky
320
+ * cache backend from turning a replay into a crash.
321
+ */
322
+ cache({
283
323
  sessionId,
284
- status
324
+ replayTraceId,
325
+ cacheUntil,
326
+ inputHash
285
327
  }: {
286
328
  sessionId: string;
287
- status: 'PENDING' | 'RUNNING' | 'FINISHED' | 'STOPPED';
288
- }): Promise<void>;
289
- sendSpanUpdate({
290
- sessionId,
291
- span
329
+ replayTraceId: string;
330
+ cacheUntil: string;
331
+ inputHash: string;
332
+ }): Promise<CacheOutcome>;
333
+ delete({
334
+ sessionId
292
335
  }: {
293
336
  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
337
  }): Promise<void>;
304
338
  }
305
339
  //#endregion
@@ -371,13 +405,16 @@ declare class TracesResource extends BaseResource {
371
405
  *
372
406
  * A 404 response (the trace was not found in the project — typically because
373
407
  * it has not been flushed yet) is logged as a warning and the call returns
374
- * without throwing; any other non-OK status throws.
408
+ * without throwing, since the 404 may be expected when pushing too soon
409
+ * after the trace run. Pass `failOnNotFound: true` to throw instead (e.g.
410
+ * CLI callers that must report the failure). Any other non-OK status throws.
375
411
  *
376
412
  * @param traceId - The trace id to push metadata to. Accepts a UUID string
377
413
  * or a 32-char OTel hex trace id.
378
414
  * @param metadata - The metadata patch. Top-level keys are merged into the
379
415
  * trace's existing metadata. Must be non-empty (the server rejects empty
380
416
  * patches with 400).
417
+ * @param options - `failOnNotFound`: throw on 404 instead of warn-and-return.
381
418
  * @example
382
419
  * ```typescript
383
420
  * import { Laminar, observe, LaminarClient } from "@lmnr-ai/lmnr";
@@ -399,7 +436,9 @@ declare class TracesResource extends BaseResource {
399
436
  * }
400
437
  * ```
401
438
  */
402
- pushMetadata(traceId: string, metadata: Record<string, unknown>): Promise<void>;
439
+ pushMetadata(traceId: string, metadata: Record<string, unknown>, options?: {
440
+ failOnNotFound?: boolean;
441
+ }): Promise<void>;
403
442
  }
404
443
  //#endregion
405
444
  //#region src/index.d.ts
@@ -433,5 +472,5 @@ declare class LaminarClient {
433
472
  get traces(): TracesResource;
434
473
  }
435
474
  //#endregion
436
- export { LaminarClient };
475
+ export { type CacheOutcome, LaminarClient, RolloutSessionsResource };
437
476
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Datapoint, Dataset, EvaluationDatapoint, GetDatapointsResponse, InitEvaluationResponse, PushDatapointsResponse, RolloutParam, SpanType, StringUUID } from "@lmnr-ai/types";
1
+ import { CachedSpan, 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 {
@@ -257,49 +257,83 @@ declare class EvaluatorsResource extends BaseResource {
257
257
  }
258
258
  //#endregion
259
259
  //#region src/resources/rollout-sessions.d.ts
260
+ /**
261
+ * Result of a debug-replay cache lookup (debug-replay v2).
262
+ *
263
+ * - `hit` — the server served a cached response for this input hash; replay it
264
+ * and mark the span CACHED.
265
+ * - `miss` — no cache entry; the caller latches process-wide live mode and runs
266
+ * every subsequent call live.
267
+ * - `live` — run THIS call live without latching (server COLD warmup-timeout
268
+ * degrade, or any non-OK / transport error here).
269
+ */
270
+ type CacheOutcome = {
271
+ kind: "hit";
272
+ cached: CachedSpan;
273
+ } | {
274
+ kind: "miss";
275
+ } | {
276
+ kind: "live";
277
+ };
260
278
  declare class RolloutSessionsResource extends BaseResource {
261
279
  constructor(baseHttpUrl: string, projectApiKey: string);
262
280
  /**
263
- * Connects to the SSE stream for rollout debugging sessions
264
- * Returns the Response object for streaming SSE events
281
+ * Idempotently register (upsert) a debug session on the backend, keyed on the
282
+ * SDK-supplied session id. The backend stores the row so the session is
283
+ * visible in the UI; a null/omitted name never clobbers a name set elsewhere.
284
+ *
285
+ * Returns the backend-resolved `projectId` (derived from the API key) so the
286
+ * caller can build the debugger URL; null if the body can't be parsed.
265
287
  */
266
- connect({
288
+ register({
267
289
  sessionId,
268
- name,
269
- params,
270
- signal
290
+ name
271
291
  }: {
272
292
  sessionId: string;
273
- params: RolloutParam[];
274
- name: string;
275
- signal?: AbortSignal;
276
- }): Promise<Response>;
277
- delete({
278
- sessionId
293
+ name?: string;
294
+ }): Promise<string | null>;
295
+ /**
296
+ * Rename an existing debug session. Update-only: the backend returns 404 (and
297
+ * this throws) when the session id is unknown for the project, so a mistyped
298
+ * id surfaces as an error rather than silently creating a session. Creation
299
+ * stays the SDK's job via {@link register}.
300
+ */
301
+ setName({
302
+ sessionId,
303
+ name
279
304
  }: {
280
305
  sessionId: string;
306
+ name: string;
281
307
  }): Promise<void>;
282
- setStatus({
308
+ /**
309
+ * Look up the debug-replay cache for a single LLM call (debug-replay v2).
310
+ *
311
+ * The server is keyed by `inputHash` (hex blake3 of the canonicalized,
312
+ * system-stripped input messages). It returns one of three outcomes:
313
+ * - `{ outcome: "hit", response }` — a cached response to replay.
314
+ * - `{ outcome: "miss" }` — no entry; caller latches live mode.
315
+ * - `{ outcome: "live" }` — run this call live (COLD degrade).
316
+ *
317
+ * Error posture: a non-OK response or a transport error degrades to
318
+ * `{ kind: "live" }` for THIS call only — it never throws and never latches
319
+ * the process-wide live flag (only a real MISS does that). This keeps a flaky
320
+ * cache backend from turning a replay into a crash.
321
+ */
322
+ cache({
283
323
  sessionId,
284
- status
324
+ replayTraceId,
325
+ cacheUntil,
326
+ inputHash
285
327
  }: {
286
328
  sessionId: string;
287
- status: 'PENDING' | 'RUNNING' | 'FINISHED' | 'STOPPED';
288
- }): Promise<void>;
289
- sendSpanUpdate({
290
- sessionId,
291
- span
329
+ replayTraceId: string;
330
+ cacheUntil: string;
331
+ inputHash: string;
332
+ }): Promise<CacheOutcome>;
333
+ delete({
334
+ sessionId
292
335
  }: {
293
336
  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
337
  }): Promise<void>;
304
338
  }
305
339
  //#endregion
@@ -371,13 +405,16 @@ declare class TracesResource extends BaseResource {
371
405
  *
372
406
  * A 404 response (the trace was not found in the project — typically because
373
407
  * it has not been flushed yet) is logged as a warning and the call returns
374
- * without throwing; any other non-OK status throws.
408
+ * without throwing, since the 404 may be expected when pushing too soon
409
+ * after the trace run. Pass `failOnNotFound: true` to throw instead (e.g.
410
+ * CLI callers that must report the failure). Any other non-OK status throws.
375
411
  *
376
412
  * @param traceId - The trace id to push metadata to. Accepts a UUID string
377
413
  * or a 32-char OTel hex trace id.
378
414
  * @param metadata - The metadata patch. Top-level keys are merged into the
379
415
  * trace's existing metadata. Must be non-empty (the server rejects empty
380
416
  * patches with 400).
417
+ * @param options - `failOnNotFound`: throw on 404 instead of warn-and-return.
381
418
  * @example
382
419
  * ```typescript
383
420
  * import { Laminar, observe, LaminarClient } from "@lmnr-ai/lmnr";
@@ -399,7 +436,9 @@ declare class TracesResource extends BaseResource {
399
436
  * }
400
437
  * ```
401
438
  */
402
- pushMetadata(traceId: string, metadata: Record<string, unknown>): Promise<void>;
439
+ pushMetadata(traceId: string, metadata: Record<string, unknown>, options?: {
440
+ failOnNotFound?: boolean;
441
+ }): Promise<void>;
403
442
  }
404
443
  //#endregion
405
444
  //#region src/index.d.ts
@@ -433,5 +472,5 @@ declare class LaminarClient {
433
472
  get traces(): TracesResource;
434
473
  }
435
474
  //#endregion
436
- export { LaminarClient };
475
+ export { type CacheOutcome, LaminarClient, RolloutSessionsResource };
437
476
  //# sourceMappingURL=index.d.mts.map
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.26";
8
+ var version = "0.8.28";
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$3 = initializeLogger();
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$3.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);
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$3.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);
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$3.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);
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$3.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);
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$2 = initializeLogger();
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$2.debug(`Pushing batch ${batchNum} of ${totalBatches}`);
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$1 = 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$1.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$1.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,129 @@ var EvaluatorsResource = class extends BaseResource {
464
465
  };
465
466
  //#endregion
466
467
  //#region src/resources/rollout-sessions.ts
468
+ const logger$1 = initializeLogger();
469
+ /**
470
+ * Map the opaque HIT `response` payload onto a {@link CachedSpan} the provider
471
+ * wrappers can replay. The server-side shape of `response` is not yet frozen
472
+ * (app-server plan 01 leaves it as a `serde_json::Value`), so this stays
473
+ * deliberately tolerant: the whole payload is serialized into `output` (the only
474
+ * field the AI SDK wrapper's `parseCachedSpan` actually reads, via
475
+ * `JSON.parse`), and a `finishReason` is surfaced into `attributes` when the
476
+ * payload carries one. `name`/`input` are irrelevant to replay and left empty.
477
+ */
478
+ const toCachedSpan = (response) => {
479
+ const output = typeof response === "string" ? response : JSON.stringify(response ?? null);
480
+ const attributes = {};
481
+ if (response !== null && typeof response === "object" && typeof response.finishReason === "string") attributes["ai.response.finishReason"] = response.finishReason;
482
+ return {
483
+ name: "",
484
+ input: "",
485
+ output,
486
+ attributes
487
+ };
488
+ };
467
489
  var RolloutSessionsResource = class extends BaseResource {
468
490
  constructor(baseHttpUrl, projectApiKey) {
469
491
  super(baseHttpUrl, projectApiKey);
470
492
  }
471
493
  /**
472
- * Connects to the SSE stream for rollout debugging sessions
473
- * Returns the Response object for streaming SSE events
494
+ * Idempotently register (upsert) a debug session on the backend, keyed on the
495
+ * SDK-supplied session id. The backend stores the row so the session is
496
+ * visible in the UI; a null/omitted name never clobbers a name set elsewhere.
497
+ *
498
+ * Returns the backend-resolved `projectId` (derived from the API key) so the
499
+ * caller can build the debugger URL; null if the body can't be parsed.
474
500
  */
475
- async connect({ sessionId, name, params, signal }) {
501
+ async register({ sessionId, name }) {
476
502
  const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}`, {
477
503
  method: "POST",
478
- headers: {
479
- ...this.headers(),
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()
504
+ headers: this.headers(),
505
+ body: JSON.stringify({ name })
496
506
  });
497
507
  if (!response.ok) await this.handleError(response);
508
+ try {
509
+ return (await response.json()).projectId ?? null;
510
+ } catch (e) {
511
+ logger$1.warn(`Failed to parse rollout register response: ${errorMessage(e)}`);
512
+ return null;
513
+ }
498
514
  }
499
- async setStatus({ sessionId, status }) {
500
- const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}/status`, {
515
+ /**
516
+ * Rename an existing debug session. Update-only: the backend returns 404 (and
517
+ * this throws) when the session id is unknown for the project, so a mistyped
518
+ * id surfaces as an error rather than silently creating a session. Creation
519
+ * stays the SDK's job via {@link register}.
520
+ */
521
+ async setName({ sessionId, name }) {
522
+ const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}/name`, {
501
523
  method: "PATCH",
502
524
  headers: this.headers(),
503
- body: JSON.stringify({ status })
525
+ body: JSON.stringify({ name })
504
526
  });
505
527
  if (!response.ok) await this.handleError(response);
506
528
  }
507
- async sendSpanUpdate({ sessionId, span }) {
508
- const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}/update`, {
509
- method: "PATCH",
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
- })
529
+ /**
530
+ * Look up the debug-replay cache for a single LLM call (debug-replay v2).
531
+ *
532
+ * The server is keyed by `inputHash` (hex blake3 of the canonicalized,
533
+ * system-stripped input messages). It returns one of three outcomes:
534
+ * - `{ outcome: "hit", response }` — a cached response to replay.
535
+ * - `{ outcome: "miss" }` — no entry; caller latches live mode.
536
+ * - `{ outcome: "live" }` — run this call live (COLD degrade).
537
+ *
538
+ * Error posture: a non-OK response or a transport error degrades to
539
+ * `{ kind: "live" }` for THIS call only — it never throws and never latches
540
+ * the process-wide live flag (only a real MISS does that). This keeps a flaky
541
+ * cache backend from turning a replay into a crash.
542
+ */
543
+ async cache({ sessionId, replayTraceId, cacheUntil, inputHash }) {
544
+ let response;
545
+ try {
546
+ response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}/cache`, {
547
+ method: "POST",
548
+ headers: this.headers(),
549
+ body: JSON.stringify({
550
+ replayTraceId,
551
+ cacheUntil,
552
+ inputHash
553
+ })
554
+ });
555
+ } catch (e) {
556
+ logger$1.warn(`Debug cache lookup failed, running live: ${errorMessage(e)}`);
557
+ return { kind: "live" };
558
+ }
559
+ if (!response.ok) {
560
+ logger$1.warn(`Debug cache lookup returned ${response.status}, running live`);
561
+ return { kind: "live" };
562
+ }
563
+ let body;
564
+ try {
565
+ body = await response.json();
566
+ } catch (e) {
567
+ logger$1.warn(`Failed to parse debug cache response, running live: ${errorMessage(e)}`);
568
+ return { kind: "live" };
569
+ }
570
+ switch (body.outcome) {
571
+ case "hit":
572
+ if (body.response === null || body.response === void 0) {
573
+ logger$1.warn("Debug cache HIT had no response payload, running live");
574
+ return { kind: "live" };
575
+ }
576
+ return {
577
+ kind: "hit",
578
+ cached: toCachedSpan(body.response)
579
+ };
580
+ case "miss": return { kind: "miss" };
581
+ case "live": return { kind: "live" };
582
+ default:
583
+ logger$1.warn(`Unknown debug cache outcome "${body.outcome}", running live`);
584
+ return { kind: "live" };
585
+ }
586
+ }
587
+ async delete({ sessionId }) {
588
+ const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}`, {
589
+ method: "DELETE",
590
+ headers: this.headers()
521
591
  });
522
592
  if (!response.ok) await this.handleError(response);
523
593
  }
@@ -626,13 +696,16 @@ var TracesResource = class extends BaseResource {
626
696
  *
627
697
  * A 404 response (the trace was not found in the project — typically because
628
698
  * it has not been flushed yet) is logged as a warning and the call returns
629
- * without throwing; any other non-OK status throws.
699
+ * without throwing, since the 404 may be expected when pushing too soon
700
+ * after the trace run. Pass `failOnNotFound: true` to throw instead (e.g.
701
+ * CLI callers that must report the failure). Any other non-OK status throws.
630
702
  *
631
703
  * @param traceId - The trace id to push metadata to. Accepts a UUID string
632
704
  * or a 32-char OTel hex trace id.
633
705
  * @param metadata - The metadata patch. Top-level keys are merged into the
634
706
  * trace's existing metadata. Must be non-empty (the server rejects empty
635
707
  * patches with 400).
708
+ * @param options - `failOnNotFound`: throw on 404 instead of warn-and-return.
636
709
  * @example
637
710
  * ```typescript
638
711
  * import { Laminar, observe, LaminarClient } from "@lmnr-ai/lmnr";
@@ -654,7 +727,7 @@ var TracesResource = class extends BaseResource {
654
727
  * }
655
728
  * ```
656
729
  */
657
- async pushMetadata(traceId, metadata) {
730
+ async pushMetadata(traceId, metadata, options) {
658
731
  if (!metadata || Object.keys(metadata).length === 0) throw new Error("metadata must be a non-empty object");
659
732
  const formattedTraceId = isStringUUID(traceId) ? traceId : otelTraceIdToUUID(traceId);
660
733
  const url = this.baseHttpUrl + "/v1/traces/metadata";
@@ -667,7 +740,9 @@ var TracesResource = class extends BaseResource {
667
740
  })
668
741
  });
669
742
  if (response.status === 404) {
670
- logger.warn(`Trace ${formattedTraceId} not found. The trace may not have been flushed yet — call await Laminar.flush() and retry.`);
743
+ const message = `Trace ${formattedTraceId} not found. The trace may not have been flushed yet — call await Laminar.flush() and retry.`;
744
+ if (options?.failOnNotFound) throw new Error(message);
745
+ logger.warn(message);
671
746
  return;
672
747
  }
673
748
  if (!response.ok) await this.handleError(response);
@@ -717,6 +792,6 @@ var LaminarClient = class {
717
792
  }
718
793
  };
719
794
  //#endregion
720
- export { LaminarClient };
795
+ export { LaminarClient, RolloutSessionsResource };
721
796
 
722
797
  //# sourceMappingURL=index.mjs.map