bitfab 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -54,6 +54,120 @@ var init_errors = __esm({
54
54
  }
55
55
  });
56
56
 
57
+ // src/serialize.ts
58
+ function describeValue(value) {
59
+ try {
60
+ const ctorName = value?.constructor?.name;
61
+ if (ctorName && ctorName !== "Object") {
62
+ return ctorName;
63
+ }
64
+ } catch {
65
+ }
66
+ return typeof value;
67
+ }
68
+ function unserializableStub(value, reason) {
69
+ let summary;
70
+ try {
71
+ summary = `<unserializable: ${describeValue(value)} (${reason})>`;
72
+ } catch {
73
+ summary = `<unserializable (${reason})>`;
74
+ }
75
+ return { json: summary };
76
+ }
77
+ function serializeValue(value) {
78
+ try {
79
+ const { json, meta } = import_superjson.default.serialize(value);
80
+ let size;
81
+ try {
82
+ size = JSON.stringify(json).length;
83
+ } catch {
84
+ return unserializableStub(value, "stringify_failed_after_superjson");
85
+ }
86
+ if (size > MAX_SERIALIZED_BYTES) {
87
+ return unserializableStub(value, `too_large_${size}_bytes`);
88
+ }
89
+ return meta ? { json, meta } : { json };
90
+ } catch {
91
+ try {
92
+ return { json: JSON.parse(JSON.stringify(value)) };
93
+ } catch {
94
+ return unserializableStub(value, "json_stringify_failed");
95
+ }
96
+ }
97
+ }
98
+ function deserializeValue(serialized) {
99
+ if (serialized.meta === void 0) {
100
+ return serialized.json;
101
+ }
102
+ return import_superjson.default.deserialize({
103
+ json: serialized.json,
104
+ meta: serialized.meta
105
+ });
106
+ }
107
+ function toJsonSafe(value) {
108
+ return toJsonSafeInner(value, 0, /* @__PURE__ */ new WeakSet());
109
+ }
110
+ function toJsonSafeInner(value, depth, seen) {
111
+ if (value === null || value === void 0) {
112
+ return value;
113
+ }
114
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
115
+ return value;
116
+ }
117
+ const className = value?.constructor?.name ?? typeof value;
118
+ if (depth > MAX_SAFE_DEPTH) {
119
+ return `<${className}>`;
120
+ }
121
+ if (typeof value !== "object") {
122
+ try {
123
+ return String(value);
124
+ } catch {
125
+ return `<${className}>`;
126
+ }
127
+ }
128
+ if (seen.has(value)) {
129
+ return `<cycle ${className}>`;
130
+ }
131
+ seen.add(value);
132
+ let result;
133
+ if (Array.isArray(value)) {
134
+ result = value.map((item) => toJsonSafeInner(item, depth + 1, seen));
135
+ } else if (typeof value.toJSON === "function") {
136
+ try {
137
+ result = toJsonSafeInner(
138
+ value.toJSON(),
139
+ depth + 1,
140
+ seen
141
+ );
142
+ } catch {
143
+ result = `<${className}>`;
144
+ }
145
+ } else {
146
+ try {
147
+ const obj = {};
148
+ for (const [k, v] of Object.entries(value)) {
149
+ if (!k.startsWith("_")) {
150
+ obj[k] = toJsonSafeInner(v, depth + 1, seen);
151
+ }
152
+ }
153
+ result = obj;
154
+ } catch {
155
+ result = `<${className}>`;
156
+ }
157
+ }
158
+ seen.delete(value);
159
+ return result;
160
+ }
161
+ var import_superjson, MAX_SERIALIZED_BYTES, MAX_SAFE_DEPTH;
162
+ var init_serialize = __esm({
163
+ "src/serialize.ts"() {
164
+ "use strict";
165
+ import_superjson = __toESM(require("superjson"), 1);
166
+ MAX_SERIALIZED_BYTES = 512e3;
167
+ MAX_SAFE_DEPTH = 6;
168
+ }
169
+ });
170
+
57
171
  // src/asyncStorage.ts
58
172
  function registerAsyncLocalStorageClass(cls) {
59
173
  if (!AsyncLocalStorageClass) {
@@ -115,65 +229,6 @@ var init_replayContext = __esm({
115
229
  }
116
230
  });
117
231
 
118
- // src/serialize.ts
119
- function describeValue(value) {
120
- try {
121
- const ctorName = value?.constructor?.name;
122
- if (ctorName && ctorName !== "Object") {
123
- return ctorName;
124
- }
125
- } catch {
126
- }
127
- return typeof value;
128
- }
129
- function unserializableStub(value, reason) {
130
- let summary;
131
- try {
132
- summary = `<unserializable: ${describeValue(value)} (${reason})>`;
133
- } catch {
134
- summary = `<unserializable (${reason})>`;
135
- }
136
- return { json: summary };
137
- }
138
- function serializeValue(value) {
139
- try {
140
- const { json, meta } = import_superjson.default.serialize(value);
141
- let size;
142
- try {
143
- size = JSON.stringify(json).length;
144
- } catch {
145
- return unserializableStub(value, "stringify_failed_after_superjson");
146
- }
147
- if (size > MAX_SERIALIZED_BYTES) {
148
- return unserializableStub(value, `too_large_${size}_bytes`);
149
- }
150
- return meta ? { json, meta } : { json };
151
- } catch {
152
- try {
153
- return { json: JSON.parse(JSON.stringify(value)) };
154
- } catch {
155
- return unserializableStub(value, "json_stringify_failed");
156
- }
157
- }
158
- }
159
- function deserializeValue(serialized) {
160
- if (serialized.meta === void 0) {
161
- return serialized.json;
162
- }
163
- return import_superjson.default.deserialize({
164
- json: serialized.json,
165
- meta: serialized.meta
166
- });
167
- }
168
- var import_superjson, MAX_SERIALIZED_BYTES;
169
- var init_serialize = __esm({
170
- "src/serialize.ts"() {
171
- "use strict";
172
- import_superjson = __toESM(require("superjson"), 1);
173
- MAX_SERIALIZED_BYTES = 512e3;
174
- }
175
- });
176
-
177
232
  // src/replay.ts
178
233
  var replay_exports = {};
179
234
  __export(replay_exports, {
@@ -442,13 +497,93 @@ __export(index_exports, {
442
497
  module.exports = __toCommonJS(index_exports);
443
498
 
444
499
  // src/version.generated.ts
445
- var __version__ = "0.18.1";
500
+ var __version__ = "0.19.0";
446
501
 
447
502
  // src/constants.ts
448
503
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
449
504
 
450
505
  // src/http.ts
451
506
  init_errors();
507
+ function serializePayloadBody(payload) {
508
+ try {
509
+ return { body: JSON.stringify(payload), dropped: [] };
510
+ } catch {
511
+ const dropped = [];
512
+ const sanitize = (value, seen) => {
513
+ const t = typeof value;
514
+ if (value === null || t === "string" || t === "number" || t === "boolean") {
515
+ return value;
516
+ }
517
+ if (t === "bigint") {
518
+ dropped.push("BigInt");
519
+ return "<unserializable: BigInt>";
520
+ }
521
+ if (t === "function") {
522
+ const name = value.name || "Function";
523
+ dropped.push(name);
524
+ return `<unserializable: ${name}>`;
525
+ }
526
+ if (t === "symbol") {
527
+ dropped.push("Symbol");
528
+ return "<unserializable: Symbol>";
529
+ }
530
+ if (t !== "object") {
531
+ return void 0;
532
+ }
533
+ const obj = value;
534
+ const className = obj.constructor?.name || "object";
535
+ if (seen.has(obj)) {
536
+ dropped.push(className);
537
+ return `<cycle: ${className}>`;
538
+ }
539
+ seen.add(obj);
540
+ let result;
541
+ if (Array.isArray(obj)) {
542
+ result = obj.map((item) => sanitize(item, seen));
543
+ } else if (typeof obj.toJSON === "function") {
544
+ try {
545
+ result = sanitize(obj.toJSON(), seen);
546
+ } catch {
547
+ dropped.push(className);
548
+ result = `<unserializable: ${className}>`;
549
+ }
550
+ } else {
551
+ const out = {};
552
+ for (const [k, v] of Object.entries(obj)) {
553
+ out[k] = sanitize(v, seen);
554
+ }
555
+ result = out;
556
+ }
557
+ seen.delete(obj);
558
+ return result;
559
+ };
560
+ let sanitized;
561
+ try {
562
+ sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
563
+ } catch (error) {
564
+ const message = error instanceof Error ? error.message : String(error);
565
+ return {
566
+ body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
567
+ dropped
568
+ };
569
+ }
570
+ if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
571
+ const obj = sanitized;
572
+ const existing = Array.isArray(obj.errors) ? obj.errors : [];
573
+ obj.errors = [
574
+ ...existing,
575
+ {
576
+ source: "sdk",
577
+ step: "json_serialize",
578
+ error: `stubbed non-serializable value(s): ${[
579
+ ...new Set(dropped)
580
+ ].join(", ")}`
581
+ }
582
+ ];
583
+ }
584
+ return { body: JSON.stringify(sanitized), dropped };
585
+ }
586
+ }
452
587
  var pendingTracePromises = /* @__PURE__ */ new Set();
453
588
  function awaitOnExit(promise) {
454
589
  pendingTracePromises.add(promise);
@@ -507,23 +642,14 @@ var HttpClient = class {
507
642
  const method = options?.method ?? "POST";
508
643
  const controller = new AbortController();
509
644
  const timeoutId = setTimeout(() => controller.abort(), timeout);
510
- let body;
511
- let serializationError;
512
- try {
513
- body = JSON.stringify(payload);
514
- } catch (error) {
515
- serializationError = error instanceof Error ? error.message : String(error);
516
- body = JSON.stringify({
517
- ...Object.fromEntries(
518
- Object.entries(payload).filter(
519
- ([, v]) => typeof v === "string" || typeof v === "number"
520
- )
521
- ),
522
- rawSpan: {},
523
- errors: [
524
- { source: "sdk", step: "json_serialize", error: serializationError }
525
- ]
526
- });
645
+ const { body, dropped } = serializePayloadBody(payload);
646
+ if (dropped.length > 0) {
647
+ try {
648
+ console.warn(
649
+ `Bitfab: request body to ${endpoint} held ${dropped.length} non-serializable value(s) (${[...new Set(dropped)].join(", ")}); they were stubbed so the span still sends, but the trace may be incomplete or not replayable. Capture a JSON-safe projection of this input to make it replayable.`
650
+ );
651
+ } catch {
652
+ }
527
653
  }
528
654
  try {
529
655
  const response = await fetch(url, {
@@ -781,33 +907,11 @@ var HttpClient = class {
781
907
  };
782
908
 
783
909
  // src/claudeAgentSdk.ts
910
+ init_serialize();
784
911
  function nowIso() {
785
912
  return (/* @__PURE__ */ new Date()).toISOString();
786
913
  }
787
- function safeSerialize(value) {
788
- if (value === null || value === void 0) {
789
- return value;
790
- }
791
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
792
- return value;
793
- }
794
- if (Array.isArray(value)) {
795
- return value.map(safeSerialize);
796
- }
797
- if (typeof value === "object") {
798
- if (typeof value.toJSON === "function") {
799
- return value.toJSON();
800
- }
801
- const result = {};
802
- for (const [k, v] of Object.entries(value)) {
803
- if (!k.startsWith("_")) {
804
- result[k] = safeSerialize(v);
805
- }
806
- }
807
- return result;
808
- }
809
- return String(value);
810
- }
914
+ var safeSerialize = toJsonSafe;
811
915
  function extractContentBlocks(content) {
812
916
  if (!Array.isArray(content)) {
813
917
  return [];
@@ -1563,6 +1667,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1563
1667
  }
1564
1668
 
1565
1669
  // src/langgraph.ts
1670
+ init_serialize();
1566
1671
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1567
1672
  var LANGGRAPH_METADATA_KEYS = [
1568
1673
  "langgraph_step",
@@ -1574,56 +1679,7 @@ var LANGGRAPH_METADATA_KEYS = [
1574
1679
  function nowIso2() {
1575
1680
  return (/* @__PURE__ */ new Date()).toISOString();
1576
1681
  }
1577
- var MAX_SERIALIZE_DEPTH = 6;
1578
- function safeSerialize2(value) {
1579
- return safeSerializeInner(value, 0, /* @__PURE__ */ new WeakSet());
1580
- }
1581
- function safeSerializeInner(value, depth, seen) {
1582
- if (value === null || value === void 0) {
1583
- return value;
1584
- }
1585
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1586
- return value;
1587
- }
1588
- const className = value?.constructor?.name ?? typeof value;
1589
- if (depth > MAX_SERIALIZE_DEPTH) {
1590
- return `<${className}>`;
1591
- }
1592
- if (typeof value === "object") {
1593
- if (seen.has(value)) {
1594
- return `<cycle ${className}>`;
1595
- }
1596
- seen.add(value);
1597
- }
1598
- if (Array.isArray(value)) {
1599
- return value.map((item) => safeSerializeInner(item, depth + 1, seen));
1600
- }
1601
- if (typeof value === "object") {
1602
- if (typeof value.toJSON === "function") {
1603
- try {
1604
- return value.toJSON();
1605
- } catch {
1606
- return `<${className}>`;
1607
- }
1608
- }
1609
- try {
1610
- const result = {};
1611
- for (const [k, v] of Object.entries(value)) {
1612
- if (!k.startsWith("_")) {
1613
- result[k] = safeSerializeInner(v, depth + 1, seen);
1614
- }
1615
- }
1616
- return result;
1617
- } catch {
1618
- return `<${className}>`;
1619
- }
1620
- }
1621
- try {
1622
- return String(value);
1623
- } catch {
1624
- return `<${className}>`;
1625
- }
1626
- }
1682
+ var safeSerialize2 = toJsonSafe;
1627
1683
  function convertMessage(message) {
1628
1684
  if (typeof message !== "object" || message === null) {
1629
1685
  return { role: "unknown", content: String(message) };
@@ -2157,7 +2213,9 @@ var ReplayEnvironment = class {
2157
2213
  * Throws if read outside a replay item.
2158
2214
  */
2159
2215
  get databaseUrl() {
2160
- return this.require().databaseUrl;
2216
+ const snapshot = this.require();
2217
+ this.markAccessed();
2218
+ return snapshot.databaseUrl;
2161
2219
  }
2162
2220
  /** When the per-trace branch URL stops being valid. ISO-8601. */
2163
2221
  get expiresAt() {
@@ -2185,7 +2243,24 @@ var ReplayEnvironment = class {
2185
2243
  }
2186
2244
  /** Non-throwing variant for callers that handle the inactive case. */
2187
2245
  snapshot() {
2188
- return this.read();
2246
+ const snapshot = this.read();
2247
+ if (snapshot) {
2248
+ this.markAccessed();
2249
+ }
2250
+ return snapshot;
2251
+ }
2252
+ /**
2253
+ * Record on the replay context that customer code obtained the branch
2254
+ * URL. Only `databaseUrl` and `snapshot()` count — `active`, `readOnly`
2255
+ * and friends inspect the lease without exposing the connection string,
2256
+ * so they don't prove the replayed code could have connected to the
2257
+ * branch.
2258
+ */
2259
+ markAccessed() {
2260
+ const ctx = getReplayContext();
2261
+ if (ctx?.dbBranchLease) {
2262
+ ctx.dbSnapshotAccessed = true;
2263
+ }
2189
2264
  }
2190
2265
  read() {
2191
2266
  const ctx = getReplayContext();
@@ -3117,7 +3192,19 @@ var Bitfab = class {
3117
3192
  contexts: traceState?.contexts ?? [],
3118
3193
  testRunId: traceState?.testRunId,
3119
3194
  inputSourceTraceId: traceState?.inputSourceTraceId,
3120
- dbSnapshotRef: traceState?.dbSnapshotRef
3195
+ dbSnapshotRef: traceState?.dbSnapshotRef,
3196
+ // Built AFTER the wrapped fn finished, so `accessed` reflects
3197
+ // whether customer code obtained the branch URL during this
3198
+ // item. Omitted entirely when no lease was attached, so the
3199
+ // server can distinguish "no branch" from "branch ignored".
3200
+ ...replayCtx?.dbBranchLease && {
3201
+ dbSnapshotUsage: {
3202
+ neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3203
+ snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3204
+ sourceTraceId: replayCtx.sourceBitfabTraceId,
3205
+ accessed: replayCtx.dbSnapshotAccessed === true
3206
+ }
3207
+ }
3121
3208
  });
3122
3209
  activeTraceStates.delete(traceId);
3123
3210
  if (persistenceCollector) {
@@ -3289,6 +3376,18 @@ var Bitfab = class {
3289
3376
  if (params.dbSnapshotRef) {
3290
3377
  rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3291
3378
  }
3379
+ if (params.dbSnapshotUsage) {
3380
+ rawTrace.db_snapshot_usage = {
3381
+ neon_branch_id: params.dbSnapshotUsage.neonBranchId,
3382
+ ...params.dbSnapshotUsage.snapshotTimestamp && {
3383
+ snapshot_timestamp: params.dbSnapshotUsage.snapshotTimestamp
3384
+ },
3385
+ ...params.dbSnapshotUsage.sourceTraceId && {
3386
+ source_trace_id: params.dbSnapshotUsage.sourceTraceId
3387
+ },
3388
+ accessed: params.dbSnapshotUsage.accessed
3389
+ };
3390
+ }
3292
3391
  return this.httpClient.sendExternalTrace({
3293
3392
  type: "sdk-function",
3294
3393
  source: "typescript-sdk-function",