@bitfab/sdk 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
@@ -683,7 +683,7 @@ __export(index_exports, {
683
683
  module.exports = __toCommonJS(index_exports);
684
684
 
685
685
  // src/version.generated.ts
686
- var __version__ = "0.28.9";
686
+ var __version__ = "0.28.11";
687
687
 
688
688
  // src/constants.ts
689
689
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -923,6 +923,50 @@ var HttpClient = class {
923
923
  async lookupFunction(name) {
924
924
  return this.request("/api/sdk/functions/lookup", { name });
925
925
  }
926
+ async getTraceSpan(traceId, lookup) {
927
+ const searchParams = new URLSearchParams();
928
+ if (lookup.id !== void 0) {
929
+ searchParams.set("id", lookup.id);
930
+ } else {
931
+ searchParams.set("name", lookup.name);
932
+ searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
933
+ }
934
+ const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
935
+ const response = await this.get(endpoint);
936
+ return response.span;
937
+ }
938
+ async get(endpoint) {
939
+ const url = `${this.serviceUrl}${endpoint}`;
940
+ const controller = new AbortController();
941
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
942
+ try {
943
+ const response = await fetch(url, {
944
+ method: "GET",
945
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
946
+ signal: controller.signal
947
+ });
948
+ if (!response.ok) {
949
+ const errorText = await response.text();
950
+ throw new BitfabError(
951
+ `HTTP ${response.status}: ${errorText.slice(0, 500)}`
952
+ );
953
+ }
954
+ return await response.json();
955
+ } catch (error) {
956
+ if (error instanceof BitfabError) {
957
+ throw error;
958
+ }
959
+ if (error instanceof Error) {
960
+ if (error.name === "AbortError") {
961
+ throw new BitfabError(`Request timed out after ${this.timeout}ms`);
962
+ }
963
+ throw new BitfabError(error.message);
964
+ }
965
+ throw new BitfabError("Unknown error occurred");
966
+ } finally {
967
+ clearTimeout(timeoutId);
968
+ }
969
+ }
926
970
  /**
927
971
  * Send an internal trace (from BAML execution).
928
972
  * Fire-and-forget with awaitOnExit - doesn't block the caller.
@@ -979,12 +1023,12 @@ var HttpClient = class {
979
1023
  });
980
1024
  }
981
1025
  /**
982
- * Partial update of an existing external trace identified by sourceTraceId.
1026
+ * Partial update of an existing trace identified by its Bitfab trace ID.
983
1027
  * Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
984
1028
  * returns a tracked promise that callers may optionally await.
985
1029
  */
986
- patchTrace(sourceTraceId, payload) {
987
- const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`;
1030
+ patchTrace(traceId, payload) {
1031
+ const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
988
1032
  return awaitOnExit(
989
1033
  this.request(endpoint, payload, { method: "PATCH" })
990
1034
  ).catch((error) => {
@@ -1322,6 +1366,7 @@ var BitfabClaudeAgentHandler = class {
1322
1366
  const traceId = this.ensureTrace();
1323
1367
  const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
1324
1368
  const spanInfo = {
1369
+ id: randomUuid(),
1325
1370
  spanId,
1326
1371
  traceId,
1327
1372
  parentId: parentId ?? null,
@@ -1385,6 +1430,8 @@ var BitfabClaudeAgentHandler = class {
1385
1430
  rawSpan.parent_id = spanInfo.parentId;
1386
1431
  }
1387
1432
  const payload = {
1433
+ id: spanInfo.id,
1434
+ traceId: spanInfo.traceId,
1388
1435
  type: "sdk-function",
1389
1436
  source: "typescript-sdk-claude-agent-sdk",
1390
1437
  traceFunctionKey: this.traceFunctionKey,
@@ -1414,6 +1461,7 @@ var BitfabClaudeAgentHandler = class {
1414
1461
  externalTrace.metadata = metadata;
1415
1462
  }
1416
1463
  const traceData = {
1464
+ id: traceId,
1417
1465
  type: "sdk-function",
1418
1466
  source: "typescript-sdk-claude-agent-sdk",
1419
1467
  traceFunctionKey: this.traceFunctionKey,
@@ -1682,6 +1730,7 @@ var BitfabClaudeAgentHandler = class {
1682
1730
  }
1683
1731
  Object.assign(llmContext, this.currentLlmUsage);
1684
1732
  const spanInfo = {
1733
+ id: randomUuid(),
1685
1734
  spanId,
1686
1735
  traceId,
1687
1736
  parentId,
@@ -2328,6 +2377,7 @@ var BitfabLangGraphCallbackHandler = class {
2328
2377
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
2329
2378
  const { safe: safeInput, dropped: inputDropped } = toJsonSafeReport(inputData);
2330
2379
  const spanInfo = {
2380
+ id: randomUuid(),
2331
2381
  spanId: runId,
2332
2382
  traceId: invocation.traceId,
2333
2383
  rootRunId: invocation.rootRunId,
@@ -2406,6 +2456,8 @@ var BitfabLangGraphCallbackHandler = class {
2406
2456
  rawSpan.parent_id = spanInfo.parentId;
2407
2457
  }
2408
2458
  const payload = {
2459
+ id: spanInfo.id,
2460
+ traceId: spanInfo.traceId,
2409
2461
  type: "sdk-function",
2410
2462
  source: "typescript-sdk-langgraph",
2411
2463
  traceFunctionKey: this.traceFunctionKey,
@@ -2421,6 +2473,7 @@ var BitfabLangGraphCallbackHandler = class {
2421
2473
  sendTraceCompletion(rootSpan, activeContext) {
2422
2474
  const completed = activeContext === null;
2423
2475
  const traceData = {
2476
+ id: rootSpan.traceId,
2424
2477
  type: "sdk-function",
2425
2478
  source: "typescript-sdk-langgraph",
2426
2479
  traceFunctionKey: this.traceFunctionKey,
@@ -2440,6 +2493,7 @@ var BitfabLangGraphCallbackHandler = class {
2440
2493
  }
2441
2494
  sendTraceStart(rootSpan) {
2442
2495
  const traceData = {
2496
+ id: rootSpan.traceId,
2443
2497
  type: "sdk-function",
2444
2498
  source: "typescript-sdk-langgraph",
2445
2499
  traceFunctionKey: this.traceFunctionKey,
@@ -2798,6 +2852,7 @@ var ReplayEnvironment = class {
2798
2852
  init_serialize();
2799
2853
 
2800
2854
  // src/tracing.ts
2855
+ init_randomUuid();
2801
2856
  var BitfabOpenAITracingProcessor = class {
2802
2857
  /**
2803
2858
  * Initialize the tracing processor.
@@ -2807,6 +2862,7 @@ var BitfabOpenAITracingProcessor = class {
2807
2862
  constructor(config) {
2808
2863
  this.activeTraces = {};
2809
2864
  this.activeSpanMappings = {};
2865
+ this.canonicalTraceIds = {};
2810
2866
  this.httpClient = new HttpClient({
2811
2867
  apiKey: config.apiKey,
2812
2868
  serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
@@ -2814,6 +2870,15 @@ var BitfabOpenAITracingProcessor = class {
2814
2870
  });
2815
2871
  this.getActiveSpanContext = config.getActiveSpanContext ?? null;
2816
2872
  }
2873
+ getCanonicalTraceId(sourceTraceId) {
2874
+ const existing = this.canonicalTraceIds[sourceTraceId];
2875
+ if (existing) {
2876
+ return existing;
2877
+ }
2878
+ const created = randomUuid();
2879
+ this.canonicalTraceIds[sourceTraceId] = created;
2880
+ return created;
2881
+ }
2817
2882
  /**
2818
2883
  * Called when a trace is started.
2819
2884
  * If there's an active withSpan context, the trace ID is remapped to the
@@ -2825,7 +2890,12 @@ var BitfabOpenAITracingProcessor = class {
2825
2890
  if (activeContext) {
2826
2891
  this.activeSpanMappings[trace.traceId] = activeContext;
2827
2892
  }
2828
- this.sendTrace(trace, activeContext ? { id: activeContext.traceId } : {});
2893
+ const canonicalTraceId = activeContext?.traceId ?? this.getCanonicalTraceId(trace.traceId);
2894
+ this.canonicalTraceIds[trace.traceId] = canonicalTraceId;
2895
+ this.sendTrace(trace, {
2896
+ id: canonicalTraceId,
2897
+ sourceTraceId: activeContext?.traceId
2898
+ });
2829
2899
  }
2830
2900
  /**
2831
2901
  * Called when a trace is ended.
@@ -2834,11 +2904,13 @@ var BitfabOpenAITracingProcessor = class {
2834
2904
  */
2835
2905
  async onTraceEnd(trace) {
2836
2906
  const mapping = this.activeSpanMappings[trace.traceId];
2837
- this.sendTrace(
2838
- trace,
2839
- mapping ? { id: mapping.traceId } : { completed: true }
2840
- );
2907
+ this.sendTrace(trace, {
2908
+ completed: mapping === void 0,
2909
+ id: mapping?.traceId ?? this.getCanonicalTraceId(trace.traceId),
2910
+ sourceTraceId: mapping?.traceId
2911
+ });
2841
2912
  delete this.activeSpanMappings[trace.traceId];
2913
+ delete this.canonicalTraceIds[trace.traceId];
2842
2914
  delete this.activeTraces[trace.traceId];
2843
2915
  }
2844
2916
  /**
@@ -2868,22 +2940,25 @@ var BitfabOpenAITracingProcessor = class {
2868
2940
  async shutdown(_timeout) {
2869
2941
  this.activeTraces = {};
2870
2942
  this.activeSpanMappings = {};
2943
+ this.canonicalTraceIds = {};
2871
2944
  }
2872
2945
  /**
2873
2946
  * Send trace to Bitfab API (fire-and-forget).
2874
2947
  * When traceIdOverride is provided, the trace ID is remapped to link
2875
2948
  * the OpenAI trace into an outer withSpan trace.
2876
2949
  */
2877
- sendTrace(trace, overrides = {}) {
2950
+ sendTrace(trace, options = {}) {
2878
2951
  try {
2879
- const { completed, ...traceOverrides } = overrides;
2880
2952
  const traceData = trace.toJSON();
2881
- Object.assign(traceData, traceOverrides);
2953
+ if (options.sourceTraceId) {
2954
+ traceData.id = options.sourceTraceId;
2955
+ }
2882
2956
  this.httpClient.sendExternalTrace({
2957
+ ...options.id && { id: options.id },
2883
2958
  type: "openai",
2884
2959
  source: "typescript-sdk-openai-tracing",
2885
2960
  externalTrace: traceData,
2886
- completed: completed ?? false
2961
+ completed: options.completed ?? false
2887
2962
  });
2888
2963
  } catch {
2889
2964
  }
@@ -2969,6 +3044,7 @@ var BitfabOpenAITracingProcessor = class {
2969
3044
  */
2970
3045
  buildSpanPayload(serializedSpan, errors) {
2971
3046
  const payload = {
3047
+ id: randomUuid(),
2972
3048
  type: "openai",
2973
3049
  source: "typescript-sdk-openai-tracing",
2974
3050
  sourceTraceId: serializedSpan.trace_id ?? "unknown",
@@ -2991,6 +3067,10 @@ var BitfabOpenAITracingProcessor = class {
2991
3067
  this.extractSpanInputResponse(span, serializedSpan, errors);
2992
3068
  this.applySpanOverrides(serializedSpan, span.traceId ?? "");
2993
3069
  const payload = this.buildSpanPayload(serializedSpan, errors);
3070
+ const canonicalTraceId = span.traceId ? this.getCanonicalTraceId(span.traceId) : void 0;
3071
+ if (canonicalTraceId) {
3072
+ payload.traceId = canonicalTraceId;
3073
+ }
2994
3074
  this.httpClient.sendExternalSpan(payload);
2995
3075
  }
2996
3076
  };
@@ -3129,8 +3209,11 @@ init_warnOnce();
3129
3209
  var activeTraceStates = /* @__PURE__ */ new Map();
3130
3210
  var pendingSpanPromises = /* @__PURE__ */ new Map();
3131
3211
  var asyncLocalStorage = null;
3212
+ var initializeAsyncContext = () => {
3213
+ asyncLocalStorage ?? (asyncLocalStorage = createAsyncLocalStorage());
3214
+ };
3132
3215
  var asyncLocalStorageReady = asyncStorageReady.then(() => {
3133
- asyncLocalStorage = createAsyncLocalStorage();
3216
+ initializeAsyncContext();
3134
3217
  });
3135
3218
  var browserSpanStack = [];
3136
3219
  function getSpanStack() {
@@ -3304,24 +3387,19 @@ function extractContextFromCollector(collector) {
3304
3387
  return null;
3305
3388
  }
3306
3389
  }
3307
- var TRACE_ID_PATTERN = /^[a-zA-Z0-9_\-.:]+$/;
3308
- var TRACE_ID_MAX_LENGTH = 256;
3390
+ 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;
3309
3391
  function validateTraceId(traceId) {
3310
- if (typeof traceId !== "string" || traceId.length === 0) {
3311
- throw new BitfabError("traceId is required and must be a non-empty string");
3392
+ if (typeof traceId !== "string" || !UUID_PATTERN.test(traceId)) {
3393
+ throw new BitfabError("traceId must be a valid Bitfab trace ID");
3312
3394
  }
3313
- if (traceId.length > TRACE_ID_MAX_LENGTH) {
3314
- throw new BitfabError(
3315
- `traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
3316
- );
3317
- }
3318
- if (!TRACE_ID_PATTERN.test(traceId)) {
3319
- throw new BitfabError(
3320
- `traceId may only contain letters, digits, "_", "-", ".", ":"`
3321
- );
3395
+ }
3396
+ function validateSpanId(id) {
3397
+ if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
3398
+ throw new BitfabError("id must be a valid Bitfab span ID");
3322
3399
  }
3323
3400
  }
3324
3401
  var noOpSpan = {
3402
+ id: "",
3325
3403
  traceId: "",
3326
3404
  addContext() {
3327
3405
  },
@@ -3345,6 +3423,7 @@ function getCurrentSpan() {
3345
3423
  return noOpSpan;
3346
3424
  }
3347
3425
  return {
3426
+ id: current.spanId,
3348
3427
  traceId: current.traceId,
3349
3428
  addContext(context) {
3350
3429
  try {
@@ -3899,6 +3978,7 @@ var Bitfab = class {
3899
3978
  if (!self.isTracingEnabled()) {
3900
3979
  return fn.apply(this, args);
3901
3980
  }
3981
+ initializeAsyncContext();
3902
3982
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3903
3983
  return asyncLocalStorageReady.then(
3904
3984
  () => wrappedFn.apply(this, args)
@@ -4140,20 +4220,19 @@ var Bitfab = class {
4140
4220
  }
4141
4221
  /**
4142
4222
  * Get a detached handle to a previously-created trace, looked up by the
4143
- * caller-supplied id (the same id passed at trace creation).
4223
+ * canonical Bitfab trace ID.
4144
4224
  *
4145
4225
  * The returned handle is not tied to AsyncLocalStorage - each method sends
4146
4226
  * to the server immediately. Useful for adding context to a trace from a
4147
4227
  * different process or thread than the one that created it.
4148
4228
  *
4149
- * Throws synchronously if `traceId` is malformed (empty, too long, or
4150
- * contains characters outside `[a-zA-Z0-9_\-.:]`). Server returns 404 if
4151
- * no trace exists with that id in the org; the failure surfaces as a
4229
+ * Throws synchronously if `traceId` is not a valid Bitfab trace ID. The
4230
+ * server returns 404 if no trace exists with that ID in the org; the failure surfaces as a
4152
4231
  * logged warning (fire-and-forget) or via the awaited promise.
4153
4232
  *
4154
4233
  * Example:
4155
4234
  * ```typescript
4156
- * const trace = client.getTrace("order_abc_123");
4235
+ * const trace = client.getTrace(traceId);
4157
4236
  * await trace.addContext({ refund_status: "approved" });
4158
4237
  * await trace.setMetadata({ region: "us-west" });
4159
4238
  * ```
@@ -4193,6 +4272,33 @@ var Bitfab = class {
4193
4272
  }
4194
4273
  };
4195
4274
  }
4275
+ /**
4276
+ * Fetch one persisted span from a trace without loading the full trace.
4277
+ * Name lookups return the last matching span by default. Pass `occurrence`
4278
+ * as `"first"` or a zero-based index to select a different match.
4279
+ */
4280
+ async getTraceSpan(traceId, lookup) {
4281
+ validateTraceId(traceId);
4282
+ const hasId = lookup.id !== void 0;
4283
+ const hasName = lookup.name !== void 0;
4284
+ if (hasId === hasName) {
4285
+ throw new BitfabError("Provide exactly one of id or name");
4286
+ }
4287
+ if (hasId) {
4288
+ validateSpanId(lookup.id);
4289
+ } else {
4290
+ if (lookup.name.length === 0) {
4291
+ throw new BitfabError("name must be a non-empty string");
4292
+ }
4293
+ const occurrence = lookup.occurrence ?? "last";
4294
+ if (occurrence !== "first" && occurrence !== "last" && (!Number.isInteger(occurrence) || occurrence < 0)) {
4295
+ throw new BitfabError(
4296
+ 'occurrence must be "first", "last", or a non-negative integer'
4297
+ );
4298
+ }
4299
+ }
4300
+ return this.httpClient.getTraceSpan(traceId, lookup);
4301
+ }
4196
4302
  /**
4197
4303
  * Get a function wrapper for a specific trace function key.
4198
4304
  *
@@ -4251,6 +4357,7 @@ var Bitfab = class {
4251
4357
  };
4252
4358
  }
4253
4359
  return this.httpClient.sendExternalTrace({
4360
+ id: params.traceId,
4254
4361
  type: "sdk-function",
4255
4362
  source: "typescript-sdk-function",
4256
4363
  traceFunctionKey: params.traceFunctionKey,
@@ -4306,6 +4413,8 @@ var Bitfab = class {
4306
4413
  externalSpan.input_source_span_id = params.inputSourceSpanId;
4307
4414
  }
4308
4415
  return this.httpClient.sendExternalSpan({
4416
+ id: params.spanId,
4417
+ traceId: params.traceId,
4309
4418
  type: "sdk-function",
4310
4419
  source: "typescript-sdk-function",
4311
4420
  sourceTraceId: params.traceId,