bitfab 0.28.9 → 0.28.11

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
@@ -692,7 +692,7 @@ __export(index_exports, {
692
692
  module.exports = __toCommonJS(index_exports);
693
693
 
694
694
  // src/version.generated.ts
695
- var __version__ = "0.28.9";
695
+ var __version__ = "0.28.11";
696
696
 
697
697
  // src/constants.ts
698
698
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -932,6 +932,50 @@ var HttpClient = class {
932
932
  async lookupFunction(name) {
933
933
  return this.request("/api/sdk/functions/lookup", { name });
934
934
  }
935
+ async getTraceSpan(traceId, lookup) {
936
+ const searchParams = new URLSearchParams();
937
+ if (lookup.id !== void 0) {
938
+ searchParams.set("id", lookup.id);
939
+ } else {
940
+ searchParams.set("name", lookup.name);
941
+ searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
942
+ }
943
+ const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
944
+ const response = await this.get(endpoint);
945
+ return response.span;
946
+ }
947
+ async get(endpoint) {
948
+ const url = `${this.serviceUrl}${endpoint}`;
949
+ const controller = new AbortController();
950
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
951
+ try {
952
+ const response = await fetch(url, {
953
+ method: "GET",
954
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
955
+ signal: controller.signal
956
+ });
957
+ if (!response.ok) {
958
+ const errorText = await response.text();
959
+ throw new BitfabError(
960
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`
961
+ );
962
+ }
963
+ return await response.json();
964
+ } catch (error) {
965
+ if (error instanceof BitfabError) {
966
+ throw error;
967
+ }
968
+ if (error instanceof Error) {
969
+ if (error.name === "AbortError") {
970
+ throw new BitfabError(`Request timed out after ${this.timeout}ms`);
971
+ }
972
+ throw new BitfabError(error.message);
973
+ }
974
+ throw new BitfabError("Unknown error occurred");
975
+ } finally {
976
+ clearTimeout(timeoutId);
977
+ }
978
+ }
935
979
  /**
936
980
  * Send an internal trace (from BAML execution).
937
981
  * Fire-and-forget with awaitOnExit - doesn't block the caller.
@@ -988,12 +1032,12 @@ var HttpClient = class {
988
1032
  });
989
1033
  }
990
1034
  /**
991
- * Partial update of an existing external trace identified by sourceTraceId.
1035
+ * Partial update of an existing trace identified by its Bitfab trace ID.
992
1036
  * Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
993
1037
  * returns a tracked promise that callers may optionally await.
994
1038
  */
995
- patchTrace(sourceTraceId, payload) {
996
- const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`;
1039
+ patchTrace(traceId, payload) {
1040
+ const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
997
1041
  return awaitOnExit(
998
1042
  this.request(endpoint, payload, { method: "PATCH" })
999
1043
  ).catch((error) => {
@@ -1331,6 +1375,7 @@ var BitfabClaudeAgentHandler = class {
1331
1375
  const traceId = this.ensureTrace();
1332
1376
  const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
1333
1377
  const spanInfo = {
1378
+ id: randomUuid(),
1334
1379
  spanId,
1335
1380
  traceId,
1336
1381
  parentId: parentId ?? null,
@@ -1394,6 +1439,8 @@ var BitfabClaudeAgentHandler = class {
1394
1439
  rawSpan.parent_id = spanInfo.parentId;
1395
1440
  }
1396
1441
  const payload = {
1442
+ id: spanInfo.id,
1443
+ traceId: spanInfo.traceId,
1397
1444
  type: "sdk-function",
1398
1445
  source: "typescript-sdk-claude-agent-sdk",
1399
1446
  traceFunctionKey: this.traceFunctionKey,
@@ -1423,6 +1470,7 @@ var BitfabClaudeAgentHandler = class {
1423
1470
  externalTrace.metadata = metadata;
1424
1471
  }
1425
1472
  const traceData = {
1473
+ id: traceId,
1426
1474
  type: "sdk-function",
1427
1475
  source: "typescript-sdk-claude-agent-sdk",
1428
1476
  traceFunctionKey: this.traceFunctionKey,
@@ -1691,6 +1739,7 @@ var BitfabClaudeAgentHandler = class {
1691
1739
  }
1692
1740
  Object.assign(llmContext, this.currentLlmUsage);
1693
1741
  const spanInfo = {
1742
+ id: randomUuid(),
1694
1743
  spanId,
1695
1744
  traceId,
1696
1745
  parentId,
@@ -2337,6 +2386,7 @@ var BitfabLangGraphCallbackHandler = class {
2337
2386
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
2338
2387
  const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
2339
2388
  const spanInfo = {
2389
+ id: randomUuid(),
2340
2390
  spanId: runId,
2341
2391
  traceId: invocation.traceId,
2342
2392
  rootRunId: invocation.rootRunId,
@@ -2415,6 +2465,8 @@ var BitfabLangGraphCallbackHandler = class {
2415
2465
  rawSpan.parent_id = spanInfo.parentId;
2416
2466
  }
2417
2467
  const payload = {
2468
+ id: spanInfo.id,
2469
+ traceId: spanInfo.traceId,
2418
2470
  type: "sdk-function",
2419
2471
  source: "typescript-sdk-langgraph",
2420
2472
  traceFunctionKey: this.traceFunctionKey,
@@ -2430,6 +2482,7 @@ var BitfabLangGraphCallbackHandler = class {
2430
2482
  sendTraceCompletion(rootSpan, activeContext) {
2431
2483
  const completed = activeContext === null;
2432
2484
  const traceData = {
2485
+ id: rootSpan.traceId,
2433
2486
  type: "sdk-function",
2434
2487
  source: "typescript-sdk-langgraph",
2435
2488
  traceFunctionKey: this.traceFunctionKey,
@@ -2449,6 +2502,7 @@ var BitfabLangGraphCallbackHandler = class {
2449
2502
  }
2450
2503
  sendTraceStart(rootSpan) {
2451
2504
  const traceData = {
2505
+ id: rootSpan.traceId,
2452
2506
  type: "sdk-function",
2453
2507
  source: "typescript-sdk-langgraph",
2454
2508
  traceFunctionKey: this.traceFunctionKey,
@@ -2807,6 +2861,7 @@ var ReplayEnvironment = class {
2807
2861
  init_serialize();
2808
2862
 
2809
2863
  // src/tracing.ts
2864
+ init_randomUuid();
2810
2865
  var BitfabOpenAITracingProcessor = class {
2811
2866
  /**
2812
2867
  * Initialize the tracing processor.
@@ -2816,6 +2871,7 @@ var BitfabOpenAITracingProcessor = class {
2816
2871
  constructor(config) {
2817
2872
  this.activeTraces = {};
2818
2873
  this.activeSpanMappings = {};
2874
+ this.canonicalTraceIds = {};
2819
2875
  this.httpClient = new HttpClient({
2820
2876
  apiKey: config.apiKey,
2821
2877
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -2823,6 +2879,15 @@ var BitfabOpenAITracingProcessor = class {
2823
2879
  });
2824
2880
  this.getActiveSpanContext = config.getActiveSpanContext ?? null;
2825
2881
  }
2882
+ getCanonicalTraceId(sourceTraceId) {
2883
+ const existing = this.canonicalTraceIds[sourceTraceId];
2884
+ if (existing) {
2885
+ return existing;
2886
+ }
2887
+ const created = randomUuid();
2888
+ this.canonicalTraceIds[sourceTraceId] = created;
2889
+ return created;
2890
+ }
2826
2891
  /**
2827
2892
  * Called when a trace is started.
2828
2893
  * If there's an active withSpan context, the trace ID is remapped to the
@@ -2834,7 +2899,12 @@ var BitfabOpenAITracingProcessor = class {
2834
2899
  if (activeContext) {
2835
2900
  this.activeSpanMappings[trace.traceId] = activeContext;
2836
2901
  }
2837
- this.sendTrace(trace, activeContext ? { id: activeContext.traceId } : {});
2902
+ const canonicalTraceId = activeContext?.traceId ?? this.getCanonicalTraceId(trace.traceId);
2903
+ this.canonicalTraceIds[trace.traceId] = canonicalTraceId;
2904
+ this.sendTrace(trace, {
2905
+ id: canonicalTraceId,
2906
+ sourceTraceId: activeContext?.traceId
2907
+ });
2838
2908
  }
2839
2909
  /**
2840
2910
  * Called when a trace is ended.
@@ -2843,11 +2913,13 @@ var BitfabOpenAITracingProcessor = class {
2843
2913
  */
2844
2914
  async onTraceEnd(trace) {
2845
2915
  const mapping = this.activeSpanMappings[trace.traceId];
2846
- this.sendTrace(
2847
- trace,
2848
- mapping ? { id: mapping.traceId } : { completed: true }
2849
- );
2916
+ this.sendTrace(trace, {
2917
+ completed: mapping === void 0,
2918
+ id: mapping?.traceId ?? this.getCanonicalTraceId(trace.traceId),
2919
+ sourceTraceId: mapping?.traceId
2920
+ });
2850
2921
  delete this.activeSpanMappings[trace.traceId];
2922
+ delete this.canonicalTraceIds[trace.traceId];
2851
2923
  delete this.activeTraces[trace.traceId];
2852
2924
  }
2853
2925
  /**
@@ -2877,22 +2949,25 @@ var BitfabOpenAITracingProcessor = class {
2877
2949
  async shutdown(_timeout) {
2878
2950
  this.activeTraces = {};
2879
2951
  this.activeSpanMappings = {};
2952
+ this.canonicalTraceIds = {};
2880
2953
  }
2881
2954
  /**
2882
2955
  * Send trace to Bitfab API (fire-and-forget).
2883
2956
  * When traceIdOverride is provided, the trace ID is remapped to link
2884
2957
  * the OpenAI trace into an outer withSpan trace.
2885
2958
  */
2886
- sendTrace(trace, overrides = {}) {
2959
+ sendTrace(trace, options = {}) {
2887
2960
  try {
2888
- const { completed, ...traceOverrides } = overrides;
2889
2961
  const traceData = trace.toJSON();
2890
- Object.assign(traceData, traceOverrides);
2962
+ if (options.sourceTraceId) {
2963
+ traceData.id = options.sourceTraceId;
2964
+ }
2891
2965
  this.httpClient.sendExternalTrace({
2966
+ ...options.id && { id: options.id },
2892
2967
  type: "openai",
2893
2968
  source: "typescript-sdk-openai-tracing",
2894
2969
  externalTrace: traceData,
2895
- completed: completed ?? false
2970
+ completed: options.completed ?? false
2896
2971
  });
2897
2972
  } catch {
2898
2973
  }
@@ -2978,6 +3053,7 @@ var BitfabOpenAITracingProcessor = class {
2978
3053
  */
2979
3054
  buildSpanPayload(serializedSpan, errors) {
2980
3055
  const payload = {
3056
+ id: randomUuid(),
2981
3057
  type: "openai",
2982
3058
  source: "typescript-sdk-openai-tracing",
2983
3059
  sourceTraceId: serializedSpan.trace_id ?? "unknown",
@@ -3000,6 +3076,10 @@ var BitfabOpenAITracingProcessor = class {
3000
3076
  this.extractSpanInputResponse(span, serializedSpan, errors);
3001
3077
  this.applySpanOverrides(serializedSpan, span.traceId ?? "");
3002
3078
  const payload = this.buildSpanPayload(serializedSpan, errors);
3079
+ const canonicalTraceId = span.traceId ? this.getCanonicalTraceId(span.traceId) : void 0;
3080
+ if (canonicalTraceId) {
3081
+ payload.traceId = canonicalTraceId;
3082
+ }
3003
3083
  this.httpClient.sendExternalSpan(payload);
3004
3084
  }
3005
3085
  };
@@ -3138,8 +3218,11 @@ init_warnOnce();
3138
3218
  var activeTraceStates = /* @__PURE__ */ new Map();
3139
3219
  var pendingSpanPromises = /* @__PURE__ */ new Map();
3140
3220
  var asyncLocalStorage = null;
3221
+ var initializeAsyncContext = () => {
3222
+ asyncLocalStorage ?? (asyncLocalStorage = createAsyncLocalStorage());
3223
+ };
3141
3224
  var asyncLocalStorageReady = asyncStorageReady.then(() => {
3142
- asyncLocalStorage = createAsyncLocalStorage();
3225
+ initializeAsyncContext();
3143
3226
  });
3144
3227
  var browserSpanStack = [];
3145
3228
  function getSpanStack() {
@@ -3313,24 +3396,19 @@ function extractContextFromCollector(collector) {
3313
3396
  return null;
3314
3397
  }
3315
3398
  }
3316
- var TRACE_ID_PATTERN = /^[a-zA-Z0-9_\-.:]+$/;
3317
- var TRACE_ID_MAX_LENGTH = 256;
3399
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
3318
3400
  function validateTraceId(traceId) {
3319
- if (typeof traceId !== "string" || traceId.length === 0) {
3320
- throw new BitfabError("traceId is required and must be a non-empty string");
3401
+ if (typeof traceId !== "string" || !UUID_PATTERN.test(traceId)) {
3402
+ throw new BitfabError("traceId must be a valid Bitfab trace ID");
3321
3403
  }
3322
- if (traceId.length > TRACE_ID_MAX_LENGTH) {
3323
- throw new BitfabError(
3324
- `traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
3325
- );
3326
- }
3327
- if (!TRACE_ID_PATTERN.test(traceId)) {
3328
- throw new BitfabError(
3329
- `traceId may only contain letters, digits, "_", "-", ".", ":"`
3330
- );
3404
+ }
3405
+ function validateSpanId(id) {
3406
+ if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
3407
+ throw new BitfabError("id must be a valid Bitfab span ID");
3331
3408
  }
3332
3409
  }
3333
3410
  var noOpSpan = {
3411
+ id: "",
3334
3412
  traceId: "",
3335
3413
  addContext() {
3336
3414
  },
@@ -3354,6 +3432,7 @@ function getCurrentSpan() {
3354
3432
  return noOpSpan;
3355
3433
  }
3356
3434
  return {
3435
+ id: current.spanId,
3357
3436
  traceId: current.traceId,
3358
3437
  addContext(context) {
3359
3438
  try {
@@ -3908,6 +3987,7 @@ var Bitfab = class {
3908
3987
  if (!self.isTracingEnabled()) {
3909
3988
  return fn.apply(this, args);
3910
3989
  }
3990
+ initializeAsyncContext();
3911
3991
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3912
3992
  return asyncLocalStorageReady.then(
3913
3993
  () => wrappedFn.apply(this, args)
@@ -4149,20 +4229,19 @@ var Bitfab = class {
4149
4229
  }
4150
4230
  /**
4151
4231
  * Get a detached handle to a previously-created trace, looked up by the
4152
- * caller-supplied id (the same id passed at trace creation).
4232
+ * canonical Bitfab trace ID.
4153
4233
  *
4154
4234
  * The returned handle is not tied to AsyncLocalStorage - each method sends
4155
4235
  * to the server immediately. Useful for adding context to a trace from a
4156
4236
  * different process or thread than the one that created it.
4157
4237
  *
4158
- * Throws synchronously if `traceId` is malformed (empty, too long, or
4159
- * contains characters outside `[a-zA-Z0-9_\-.:]`). Server returns 404 if
4160
- * no trace exists with that id in the org; the failure surfaces as a
4238
+ * Throws synchronously if `traceId` is not a valid Bitfab trace ID. The
4239
+ * server returns 404 if no trace exists with that ID in the org; the failure surfaces as a
4161
4240
  * logged warning (fire-and-forget) or via the awaited promise.
4162
4241
  *
4163
4242
  * Example:
4164
4243
  * ```typescript
4165
- * const trace = client.getTrace("order_abc_123");
4244
+ * const trace = client.getTrace(traceId);
4166
4245
  * await trace.addContext({ refund_status: "approved" });
4167
4246
  * await trace.setMetadata({ region: "us-west" });
4168
4247
  * ```
@@ -4202,6 +4281,33 @@ var Bitfab = class {
4202
4281
  }
4203
4282
  };
4204
4283
  }
4284
+ /**
4285
+ * Fetch one persisted span from a trace without loading the full trace.
4286
+ * Name lookups return the last matching span by default. Pass `occurrence`
4287
+ * as `"first"` or a zero-based index to select a different match.
4288
+ */
4289
+ async getTraceSpan(traceId, lookup) {
4290
+ validateTraceId(traceId);
4291
+ const hasId = lookup.id !== void 0;
4292
+ const hasName = lookup.name !== void 0;
4293
+ if (hasId === hasName) {
4294
+ throw new BitfabError("Provide exactly one of id or name");
4295
+ }
4296
+ if (hasId) {
4297
+ validateSpanId(lookup.id);
4298
+ } else {
4299
+ if (lookup.name.length === 0) {
4300
+ throw new BitfabError("name must be a non-empty string");
4301
+ }
4302
+ const occurrence = lookup.occurrence ?? "last";
4303
+ if (occurrence !== "first" && occurrence !== "last" && (!Number.isInteger(occurrence) || occurrence < 0)) {
4304
+ throw new BitfabError(
4305
+ 'occurrence must be "first", "last", or a non-negative integer'
4306
+ );
4307
+ }
4308
+ }
4309
+ return this.httpClient.getTraceSpan(traceId, lookup);
4310
+ }
4205
4311
  /**
4206
4312
  * Get a function wrapper for a specific trace function key.
4207
4313
  *
@@ -4260,6 +4366,7 @@ var Bitfab = class {
4260
4366
  };
4261
4367
  }
4262
4368
  return this.httpClient.sendExternalTrace({
4369
+ id: params.traceId,
4263
4370
  type: "sdk-function",
4264
4371
  source: "typescript-sdk-function",
4265
4372
  traceFunctionKey: params.traceFunctionKey,
@@ -4315,6 +4422,8 @@ var Bitfab = class {
4315
4422
  externalSpan.input_source_span_id = params.inputSourceSpanId;
4316
4423
  }
4317
4424
  return this.httpClient.sendExternalSpan({
4425
+ id: params.spanId,
4426
+ traceId: params.traceId,
4318
4427
  type: "sdk-function",
4319
4428
  source: "typescript-sdk-function",
4320
4429
  sourceTraceId: params.traceId,