@bitfab/sdk 0.28.10 → 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.10";
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
  };
@@ -3307,24 +3387,19 @@ function extractContextFromCollector(collector) {
3307
3387
  return null;
3308
3388
  }
3309
3389
  }
3310
- var TRACE_ID_PATTERN = /^[a-zA-Z0-9_\-.:]+$/;
3311
- 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;
3312
3391
  function validateTraceId(traceId) {
3313
- if (typeof traceId !== "string" || traceId.length === 0) {
3314
- 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");
3315
3394
  }
3316
- if (traceId.length > TRACE_ID_MAX_LENGTH) {
3317
- throw new BitfabError(
3318
- `traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
3319
- );
3320
- }
3321
- if (!TRACE_ID_PATTERN.test(traceId)) {
3322
- throw new BitfabError(
3323
- `traceId may only contain letters, digits, "_", "-", ".", ":"`
3324
- );
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");
3325
3399
  }
3326
3400
  }
3327
3401
  var noOpSpan = {
3402
+ id: "",
3328
3403
  traceId: "",
3329
3404
  addContext() {
3330
3405
  },
@@ -3348,6 +3423,7 @@ function getCurrentSpan() {
3348
3423
  return noOpSpan;
3349
3424
  }
3350
3425
  return {
3426
+ id: current.spanId,
3351
3427
  traceId: current.traceId,
3352
3428
  addContext(context) {
3353
3429
  try {
@@ -4144,20 +4220,19 @@ var Bitfab = class {
4144
4220
  }
4145
4221
  /**
4146
4222
  * Get a detached handle to a previously-created trace, looked up by the
4147
- * caller-supplied id (the same id passed at trace creation).
4223
+ * canonical Bitfab trace ID.
4148
4224
  *
4149
4225
  * The returned handle is not tied to AsyncLocalStorage - each method sends
4150
4226
  * to the server immediately. Useful for adding context to a trace from a
4151
4227
  * different process or thread than the one that created it.
4152
4228
  *
4153
- * Throws synchronously if `traceId` is malformed (empty, too long, or
4154
- * contains characters outside `[a-zA-Z0-9_\-.:]`). Server returns 404 if
4155
- * 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
4156
4231
  * logged warning (fire-and-forget) or via the awaited promise.
4157
4232
  *
4158
4233
  * Example:
4159
4234
  * ```typescript
4160
- * const trace = client.getTrace("order_abc_123");
4235
+ * const trace = client.getTrace(traceId);
4161
4236
  * await trace.addContext({ refund_status: "approved" });
4162
4237
  * await trace.setMetadata({ region: "us-west" });
4163
4238
  * ```
@@ -4197,6 +4272,33 @@ var Bitfab = class {
4197
4272
  }
4198
4273
  };
4199
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
+ }
4200
4302
  /**
4201
4303
  * Get a function wrapper for a specific trace function key.
4202
4304
  *
@@ -4255,6 +4357,7 @@ var Bitfab = class {
4255
4357
  };
4256
4358
  }
4257
4359
  return this.httpClient.sendExternalTrace({
4360
+ id: params.traceId,
4258
4361
  type: "sdk-function",
4259
4362
  source: "typescript-sdk-function",
4260
4363
  traceFunctionKey: params.traceFunctionKey,
@@ -4310,6 +4413,8 @@ var Bitfab = class {
4310
4413
  externalSpan.input_source_span_id = params.inputSourceSpanId;
4311
4414
  }
4312
4415
  return this.httpClient.sendExternalSpan({
4416
+ id: params.spanId,
4417
+ traceId: params.traceId,
4313
4418
  type: "sdk-function",
4314
4419
  source: "typescript-sdk-function",
4315
4420
  sourceTraceId: params.traceId,