bitfab 0.12.1 → 0.13.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.d.cts CHANGED
@@ -467,6 +467,38 @@ interface CurrentSpan {
467
467
  */
468
468
  setPrompt(prompt: string): void;
469
469
  }
470
+ /**
471
+ * A detached handle to a previously-created trace, looked up by its
472
+ * caller-supplied id (the same id passed when the trace was started).
473
+ *
474
+ * Unlike `getCurrentTrace()`, this handle is not tied to AsyncLocalStorage —
475
+ * each method sends to the server immediately. Useful for adding context
476
+ * to a trace from a different process, request, or thread (e.g. a forked
477
+ * agent that wants to annotate the original conversation's trace).
478
+ */
479
+ interface DetachedTrace {
480
+ /** The caller-supplied trace id this handle resolves. */
481
+ readonly traceId: string;
482
+ /**
483
+ * Append a context entry to this trace. Each call adds one entry to the
484
+ * server-side contexts array; existing entries are preserved.
485
+ *
486
+ * Returns a promise that the caller may await for confirmation, or ignore
487
+ * to fire-and-forget. The pending request is tracked so `flushTraces()`
488
+ * waits for it.
489
+ */
490
+ addContext(context: Record<string, unknown>): Promise<unknown>;
491
+ /**
492
+ * Merge metadata into this trace. Server-side shallow-merges the new keys
493
+ * into the existing metadata object; existing keys are preserved unless
494
+ * overwritten by the new values.
495
+ */
496
+ setMetadata(metadata: Record<string, unknown>): Promise<unknown>;
497
+ /**
498
+ * Set the sessionId for this trace. Replaces any existing sessionId.
499
+ */
500
+ setSessionId(sessionId: string): Promise<unknown>;
501
+ }
470
502
  /**
471
503
  * A handle to the current active trace, allowing trace-level context to be set.
472
504
  */
@@ -707,6 +739,27 @@ declare class Bitfab {
707
739
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
708
740
  */
709
741
  withSpan<TArgs extends unknown[], TReturn>(traceFunctionKey: string, optionsOrFn: SpanOptions | ((...args: TArgs) => TReturn), maybeFn?: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
742
+ /**
743
+ * Get a detached handle to a previously-created trace, looked up by the
744
+ * caller-supplied id (the same id passed at trace creation).
745
+ *
746
+ * The returned handle is not tied to AsyncLocalStorage — each method sends
747
+ * to the server immediately. Useful for adding context to a trace from a
748
+ * different process or thread than the one that created it.
749
+ *
750
+ * Throws synchronously if `traceId` is malformed (empty, too long, or
751
+ * contains characters outside `[a-zA-Z0-9_\-.:]`). Server returns 404 if
752
+ * no trace exists with that id in the org; the failure surfaces as a
753
+ * logged warning (fire-and-forget) or via the awaited promise.
754
+ *
755
+ * Example:
756
+ * ```typescript
757
+ * const trace = client.getTrace("order_abc_123");
758
+ * await trace.addContext({ refund_status: "approved" });
759
+ * await trace.setMetadata({ region: "us-west" });
760
+ * ```
761
+ */
762
+ getTrace(traceId: string): DetachedTrace;
710
763
  /**
711
764
  * Get a function wrapper for a specific trace function key.
712
765
  *
@@ -822,7 +875,7 @@ declare class BitfabFunction {
822
875
  /**
823
876
  * SDK version from package.json (injected at build time)
824
877
  */
825
- declare const __version__ = "0.12.1";
878
+ declare const __version__ = "0.13.0";
826
879
 
827
880
  /**
828
881
  * Constants for the Bitfab SDK.
@@ -832,4 +885,4 @@ declare const __version__ = "0.12.1";
832
885
  */
833
886
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
834
887
 
835
- export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type MockStrategy, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
888
+ export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type DetachedTrace, type MockStrategy, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
package/dist/index.d.ts CHANGED
@@ -467,6 +467,38 @@ interface CurrentSpan {
467
467
  */
468
468
  setPrompt(prompt: string): void;
469
469
  }
470
+ /**
471
+ * A detached handle to a previously-created trace, looked up by its
472
+ * caller-supplied id (the same id passed when the trace was started).
473
+ *
474
+ * Unlike `getCurrentTrace()`, this handle is not tied to AsyncLocalStorage —
475
+ * each method sends to the server immediately. Useful for adding context
476
+ * to a trace from a different process, request, or thread (e.g. a forked
477
+ * agent that wants to annotate the original conversation's trace).
478
+ */
479
+ interface DetachedTrace {
480
+ /** The caller-supplied trace id this handle resolves. */
481
+ readonly traceId: string;
482
+ /**
483
+ * Append a context entry to this trace. Each call adds one entry to the
484
+ * server-side contexts array; existing entries are preserved.
485
+ *
486
+ * Returns a promise that the caller may await for confirmation, or ignore
487
+ * to fire-and-forget. The pending request is tracked so `flushTraces()`
488
+ * waits for it.
489
+ */
490
+ addContext(context: Record<string, unknown>): Promise<unknown>;
491
+ /**
492
+ * Merge metadata into this trace. Server-side shallow-merges the new keys
493
+ * into the existing metadata object; existing keys are preserved unless
494
+ * overwritten by the new values.
495
+ */
496
+ setMetadata(metadata: Record<string, unknown>): Promise<unknown>;
497
+ /**
498
+ * Set the sessionId for this trace. Replaces any existing sessionId.
499
+ */
500
+ setSessionId(sessionId: string): Promise<unknown>;
501
+ }
470
502
  /**
471
503
  * A handle to the current active trace, allowing trace-level context to be set.
472
504
  */
@@ -707,6 +739,27 @@ declare class Bitfab {
707
739
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
708
740
  */
709
741
  withSpan<TArgs extends unknown[], TReturn>(traceFunctionKey: string, optionsOrFn: SpanOptions | ((...args: TArgs) => TReturn), maybeFn?: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
742
+ /**
743
+ * Get a detached handle to a previously-created trace, looked up by the
744
+ * caller-supplied id (the same id passed at trace creation).
745
+ *
746
+ * The returned handle is not tied to AsyncLocalStorage — each method sends
747
+ * to the server immediately. Useful for adding context to a trace from a
748
+ * different process or thread than the one that created it.
749
+ *
750
+ * Throws synchronously if `traceId` is malformed (empty, too long, or
751
+ * contains characters outside `[a-zA-Z0-9_\-.:]`). Server returns 404 if
752
+ * no trace exists with that id in the org; the failure surfaces as a
753
+ * logged warning (fire-and-forget) or via the awaited promise.
754
+ *
755
+ * Example:
756
+ * ```typescript
757
+ * const trace = client.getTrace("order_abc_123");
758
+ * await trace.addContext({ refund_status: "approved" });
759
+ * await trace.setMetadata({ region: "us-west" });
760
+ * ```
761
+ */
762
+ getTrace(traceId: string): DetachedTrace;
710
763
  /**
711
764
  * Get a function wrapper for a specific trace function key.
712
765
  *
@@ -822,7 +875,7 @@ declare class BitfabFunction {
822
875
  /**
823
876
  * SDK version from package.json (injected at build time)
824
877
  */
825
- declare const __version__ = "0.12.1";
878
+ declare const __version__ = "0.13.0";
826
879
 
827
880
  /**
828
881
  * Constants for the Bitfab SDK.
@@ -832,4 +885,4 @@ declare const __version__ = "0.12.1";
832
885
  */
833
886
  declare const DEFAULT_SERVICE_URL = "https://bitfab.ai";
834
887
 
835
- export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type MockStrategy, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
888
+ export { type ActiveSpanContext, type AllowedEnvVars, type BamlExecutionResult, Bitfab, BitfabClaudeAgentHandler, type BitfabConfig, BitfabError, BitfabFunction, BitfabLangGraphCallbackHandler, BitfabOpenAITracingProcessor, type CodeChangeFile, type CurrentSpan, type CurrentTrace, DEFAULT_SERVICE_URL, type DetachedTrace, type MockStrategy, type ProviderDefinition, type ReplayItem, type ReplayOptions, type ReplayResult, type SpanOptions, type SpanType, type TokenUsage, type TraceResponse, type TracingProcessor, type WrapBAMLOptions, type WrappedBamlFn, __version__, flushTraces, getCurrentSpan, getCurrentTrace };
package/dist/index.js CHANGED
@@ -6,13 +6,13 @@ import {
6
6
  BitfabOpenAITracingProcessor,
7
7
  getCurrentSpan,
8
8
  getCurrentTrace
9
- } from "./chunk-NU52P7HA.js";
9
+ } from "./chunk-62NGOY7Q.js";
10
10
  import {
11
11
  BitfabError,
12
12
  DEFAULT_SERVICE_URL,
13
13
  __version__,
14
14
  flushTraces
15
- } from "./chunk-OOKZ6S64.js";
15
+ } from "./chunk-QLVXAFGP.js";
16
16
  export {
17
17
  Bitfab,
18
18
  BitfabClaudeAgentHandler,
package/dist/node.cjs CHANGED
@@ -81,7 +81,7 @@ var __version__;
81
81
  var init_version_generated = __esm({
82
82
  "src/version.generated.ts"() {
83
83
  "use strict";
84
- __version__ = "0.12.1";
84
+ __version__ = "0.13.0";
85
85
  }
86
86
  });
87
87
 
@@ -151,7 +151,8 @@ var init_http = __esm({
151
151
  this.timeout = config.timeout ?? 12e4;
152
152
  }
153
153
  /**
154
- * Make an HTTP POST request to the Bitfab API.
154
+ * Make an HTTP request to the Bitfab API. Defaults to POST; pass
155
+ * `options.method` to use a different verb (e.g. "PATCH").
155
156
  *
156
157
  * @param endpoint - The API endpoint (without base URL)
157
158
  * @param payload - The request body
@@ -162,6 +163,7 @@ var init_http = __esm({
162
163
  async request(endpoint, payload, options) {
163
164
  const url = `${this.serviceUrl}${endpoint}`;
164
165
  const timeout = options?.timeout ?? this.timeout;
166
+ const method = options?.method ?? "POST";
165
167
  const controller = new AbortController();
166
168
  const timeoutId = setTimeout(() => controller.abort(), timeout);
167
169
  let body;
@@ -182,7 +184,7 @@ var init_http = __esm({
182
184
  }
183
185
  try {
184
186
  const response = await fetch(url, {
185
- method: "POST",
187
+ method,
186
188
  headers: {
187
189
  "Content-Type": "application/json",
188
190
  Authorization: `Bearer ${this.apiKey}`
@@ -281,6 +283,22 @@ var init_http = __esm({
281
283
  }
282
284
  });
283
285
  }
286
+ /**
287
+ * Partial update of an existing external trace identified by sourceTraceId.
288
+ * Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
289
+ * returns a tracked promise that callers may optionally await.
290
+ */
291
+ patchTrace(sourceTraceId, payload) {
292
+ const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`;
293
+ return awaitOnExit(
294
+ this.request(endpoint, payload, { method: "PATCH" })
295
+ ).catch((error) => {
296
+ try {
297
+ console.error("Bitfab: Failed to patch trace:", error);
298
+ } catch {
299
+ }
300
+ });
301
+ }
284
302
  /**
285
303
  * Start a replay session by fetching historical traces.
286
304
  * Blocking call — creates a test run and returns lightweight item references.
@@ -473,9 +491,11 @@ function buildMockTree(rootNode) {
473
491
  function walk(node) {
474
492
  const key = node.traceFunctionKey;
475
493
  if (key) {
476
- const index = counters.get(key) ?? 0;
477
- counters.set(key, index + 1);
478
- spans.set(`${key}:${index}`, {
494
+ const name = node.spanName;
495
+ const counterKey = `${key}:${name}`;
496
+ const index = counters.get(counterKey) ?? 0;
497
+ counters.set(counterKey, index + 1);
498
+ spans.set(`${counterKey}:${index}`, {
479
499
  sourceSpanId: node.sourceSpanId,
480
500
  output: node.output,
481
501
  outputMeta: node.outputMeta
@@ -485,7 +505,9 @@ function buildMockTree(rootNode) {
485
505
  walk(child);
486
506
  }
487
507
  }
488
- walk(rootNode);
508
+ for (const child of rootNode.children) {
509
+ walk(child);
510
+ }
489
511
  return { spans };
490
512
  }
491
513
  async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy) {
@@ -2223,6 +2245,23 @@ function extractContextFromCollector(collector) {
2223
2245
  return null;
2224
2246
  }
2225
2247
  }
2248
+ var TRACE_ID_PATTERN = /^[a-zA-Z0-9_\-.:]+$/;
2249
+ var TRACE_ID_MAX_LENGTH = 256;
2250
+ function validateTraceId(traceId) {
2251
+ if (typeof traceId !== "string" || traceId.length === 0) {
2252
+ throw new BitfabError("traceId is required and must be a non-empty string");
2253
+ }
2254
+ if (traceId.length > TRACE_ID_MAX_LENGTH) {
2255
+ throw new BitfabError(
2256
+ `traceId must be ${TRACE_ID_MAX_LENGTH} characters or fewer`
2257
+ );
2258
+ }
2259
+ if (!TRACE_ID_PATTERN.test(traceId)) {
2260
+ throw new BitfabError(
2261
+ `traceId may only contain letters, digits, "_", "-", ".", ":"`
2262
+ );
2263
+ }
2264
+ }
2226
2265
  var noOpSpan = {
2227
2266
  traceId: "",
2228
2267
  addContext() {
@@ -2615,6 +2654,15 @@ var Bitfab = class {
2615
2654
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
2616
2655
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
2617
2656
  const self = this;
2657
+ const fnIsAsyncFunction = fn.constructor.name === "AsyncFunction";
2658
+ const fnReturnsPromise = fnIsAsyncFunction || (() => {
2659
+ try {
2660
+ const src = fn.toString();
2661
+ return /\b(?:Promise|await)\b/.test(src);
2662
+ } catch {
2663
+ return false;
2664
+ }
2665
+ })();
2618
2666
  const wrappedFn = function(...args) {
2619
2667
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
2620
2668
  return asyncLocalStorageReady.then(
@@ -2708,11 +2756,12 @@ var Bitfab = class {
2708
2756
  const replayCtxForMock = getReplayContext();
2709
2757
  if (replayCtxForMock?.mockTree && !isRootSpan) {
2710
2758
  const counters = replayCtxForMock.callCounters;
2711
- const callIndex = counters.get(traceFunctionKey) ?? 0;
2712
- counters.set(traceFunctionKey, callIndex + 1);
2759
+ const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
2760
+ const callIndex = counters.get(counterKey) ?? 0;
2761
+ counters.set(counterKey, callIndex + 1);
2713
2762
  const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
2714
2763
  if (shouldMock) {
2715
- const mockKey = `${traceFunctionKey}:${callIndex}`;
2764
+ const mockKey = `${counterKey}:${callIndex}`;
2716
2765
  const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
2717
2766
  if (mockSpan) {
2718
2767
  let output = mockSpan.output;
@@ -2723,7 +2772,7 @@ var Bitfab = class {
2723
2772
  });
2724
2773
  }
2725
2774
  void sendSpan({ result: output });
2726
- if (fn.constructor.name === "AsyncFunction") {
2775
+ if (fnReturnsPromise) {
2727
2776
  return Promise.resolve(output);
2728
2777
  }
2729
2778
  return output;
@@ -2754,6 +2803,61 @@ var Bitfab = class {
2754
2803
  };
2755
2804
  return wrappedFn;
2756
2805
  }
2806
+ /**
2807
+ * Get a detached handle to a previously-created trace, looked up by the
2808
+ * caller-supplied id (the same id passed at trace creation).
2809
+ *
2810
+ * The returned handle is not tied to AsyncLocalStorage — each method sends
2811
+ * to the server immediately. Useful for adding context to a trace from a
2812
+ * different process or thread than the one that created it.
2813
+ *
2814
+ * Throws synchronously if `traceId` is malformed (empty, too long, or
2815
+ * contains characters outside `[a-zA-Z0-9_\-.:]`). Server returns 404 if
2816
+ * no trace exists with that id in the org; the failure surfaces as a
2817
+ * logged warning (fire-and-forget) or via the awaited promise.
2818
+ *
2819
+ * Example:
2820
+ * ```typescript
2821
+ * const trace = client.getTrace("order_abc_123");
2822
+ * await trace.addContext({ refund_status: "approved" });
2823
+ * await trace.setMetadata({ region: "us-west" });
2824
+ * ```
2825
+ */
2826
+ getTrace(traceId) {
2827
+ validateTraceId(traceId);
2828
+ return {
2829
+ traceId,
2830
+ addContext: (context) => {
2831
+ if (!this.enabled) {
2832
+ return Promise.resolve();
2833
+ }
2834
+ if (typeof context !== "object" || context === null) {
2835
+ return Promise.resolve();
2836
+ }
2837
+ return this.httpClient.patchTrace(traceId, {
2838
+ appendContexts: [context]
2839
+ });
2840
+ },
2841
+ setMetadata: (metadata) => {
2842
+ if (!this.enabled) {
2843
+ return Promise.resolve();
2844
+ }
2845
+ if (typeof metadata !== "object" || metadata === null) {
2846
+ return Promise.resolve();
2847
+ }
2848
+ return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
2849
+ },
2850
+ setSessionId: (sessionId) => {
2851
+ if (!this.enabled) {
2852
+ return Promise.resolve();
2853
+ }
2854
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
2855
+ return Promise.resolve();
2856
+ }
2857
+ return this.httpClient.patchTrace(traceId, { setSessionId: sessionId });
2858
+ }
2859
+ };
2860
+ }
2757
2861
  /**
2758
2862
  * Get a function wrapper for a specific trace function key.
2759
2863
  *