retrace-sdk 0.16.6 → 0.16.7

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.
@@ -16,7 +16,7 @@ function shortHash(input) {
16
16
  h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
17
17
  h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
18
18
  const n = 4294967296 * (2097151 & h2) + (h1 >>> 0);
19
- return n.toString(16).padStart(14, "0");
19
+ return n.toString(16).padStart(16, "0");
20
20
  };
21
21
  return (pass(0) + pass(0x9e3779b9)).slice(0, 32);
22
22
  }
@@ -71,8 +71,19 @@ export declare class WSTransport implements Transport {
71
71
  export declare class HTTPTransport implements Transport {
72
72
  private traceData;
73
73
  private spans;
74
+ private backlog;
75
+ private readonly backlogCap;
76
+ private dropped;
74
77
  send(eventType: string, data: Record<string, unknown>): void;
75
78
  flush(): Promise<void>;
79
+ /** POST one complete trace payload. Returns "ok" | "reject" (non-retryable 4xx) | "retry"
80
+ * (429 / 5xx / network after retries exhausted — caller should retain the payload). */
81
+ private upload;
82
+ /** Retain a failed trace for a later retry; bounded — drop + count the oldest on overflow. */
83
+ private enqueueBacklog;
84
+ /** Retry previously-failed traces oldest-first; stop at the first still-failing one so a server
85
+ * that is still down isn't hammered. */
86
+ private drainBacklog;
76
87
  private buildSpans;
77
88
  close(): void;
78
89
  /** HTTP is already the one-shot channel — just drain. */
package/dist/transport.js CHANGED
@@ -349,6 +349,12 @@ export class WSTransport {
349
349
  export class HTTPTransport {
350
350
  traceData = null;
351
351
  spans = [];
352
+ // Bounded offline backlog (parity with WSTransport's queue + the Python HTTPTransport): a trace
353
+ // that fails to upload after exhausting transient retries is RETAINED here and retried on a later
354
+ // flush instead of being silently dropped. Bounded so a long outage can't grow memory unbounded.
355
+ backlog = [];
356
+ backlogCap = 1000;
357
+ dropped = 0;
352
358
  send(eventType, data) {
353
359
  if (eventType === "trace_started") {
354
360
  this.traceData = data;
@@ -363,15 +369,26 @@ export class HTTPTransport {
363
369
  }
364
370
  }
365
371
  async flush() {
372
+ // Opportunistically retry anything stranded by a previous outage first.
373
+ if (this.backlog.length)
374
+ await this.drainBacklog();
366
375
  if (!this.traceData)
367
376
  return;
368
- const cfg = getConfig();
369
- const url = `${cfg.baseUrl}/api/v1/traces`;
370
377
  const body = { ...this.traceData, spans: this.buildSpans() };
371
378
  const payload = JSON.stringify(body);
372
379
  // Clear first so a concurrent flush (e.g. trace_ended then shutdown drain) can't double-send.
373
380
  this.traceData = null;
374
381
  this.spans = [];
382
+ // On a transient failure, RETAIN the trace in the bounded backlog instead of dropping it — the
383
+ // previous code cleared the buffer unconditionally, losing the trace under sustained 429/5xx.
384
+ if ((await this.upload(payload)) === "retry")
385
+ this.enqueueBacklog(payload);
386
+ }
387
+ /** POST one complete trace payload. Returns "ok" | "reject" (non-retryable 4xx) | "retry"
388
+ * (429 / 5xx / network after retries exhausted — caller should retain the payload). */
389
+ async upload(payload) {
390
+ const cfg = getConfig();
391
+ const url = `${cfg.baseUrl}/api/v1/traces`;
375
392
  // Retry up to 3 times with exponential backoff; awaited so shutdown can drain it.
376
393
  for (let n = 1; n <= 3; n++) {
377
394
  try {
@@ -381,13 +398,13 @@ export class HTTPTransport {
381
398
  body: payload,
382
399
  });
383
400
  if (res.ok)
384
- return;
401
+ return "ok";
385
402
  const txt = await res.text().catch(() => "");
386
403
  // 4xx (except 429) is a client/payload error that won't succeed on retry — surface
387
404
  // it loudly and stop, rather than silently dropping the trace.
388
405
  if (res.status < 500 && res.status !== 429) {
389
406
  console.error(`[retrace] trace upload rejected (HTTP ${res.status}): ${txt.slice(0, 300)}`);
390
- return;
407
+ return "reject";
391
408
  }
392
409
  // 5xx / 429 — transient; retry.
393
410
  if (n === 3)
@@ -400,6 +417,26 @@ export class HTTPTransport {
400
417
  if (n < 3)
401
418
  await new Promise((r) => setTimeout(r, 1000 * n));
402
419
  }
420
+ return "retry";
421
+ }
422
+ /** Retain a failed trace for a later retry; bounded — drop + count the oldest on overflow. */
423
+ enqueueBacklog(payload) {
424
+ this.backlog.push(payload);
425
+ while (this.backlog.length > this.backlogCap) {
426
+ this.backlog.shift();
427
+ this.dropped++;
428
+ console.error(`[retrace] offline buffer full (cap ${this.backlogCap}) — dropped oldest trace (total dropped: ${this.dropped})`);
429
+ }
430
+ }
431
+ /** Retry previously-failed traces oldest-first; stop at the first still-failing one so a server
432
+ * that is still down isn't hammered. */
433
+ async drainBacklog() {
434
+ while (this.backlog.length) {
435
+ const r = await this.upload(this.backlog[0]);
436
+ if (r === "retry")
437
+ break; // still failing — keep the rest for next time
438
+ this.backlog.shift(); // "ok" (delivered) or "reject" (unrecoverable) → remove
439
+ }
403
440
  }
404
441
  buildSpans() {
405
442
  const merged = new Map();
@@ -423,7 +460,7 @@ export class HTTPTransport {
423
460
  await this.flush();
424
461
  }
425
462
  hasPendingData() {
426
- return this.traceData !== null || this.spans.length > 0;
463
+ return this.traceData !== null || this.spans.length > 0 || this.backlog.length > 0;
427
464
  }
428
465
  }
429
466
  export function createTransport(mode = "auto") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retrace-sdk",
3
- "version": "0.16.6",
3
+ "version": "0.16.7",
4
4
  "description": "The execution replay engine for AI agents. Record, replay, fork, and share agent executions.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",