@bitfab/sdk 0.27.1 → 0.28.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.
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-MD4XQGAF.js";
14
14
 
15
15
  // src/version.generated.ts
16
- var __version__ = "0.27.1";
16
+ var __version__ = "0.28.0";
17
17
 
18
18
  // src/constants.ts
19
19
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -168,6 +168,13 @@ var HttpClient = class {
168
168
  this.serviceUrl = config.serviceUrl;
169
169
  this.timeout = config.timeout ?? 12e4;
170
170
  }
171
+ /**
172
+ * Resolve the API key at the moment it is needed (request time), invoking
173
+ * the function form if one was supplied. Never read at construction.
174
+ */
175
+ resolveApiKey() {
176
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
177
+ }
171
178
  /**
172
179
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
173
180
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -198,7 +205,7 @@ var HttpClient = class {
198
205
  method,
199
206
  headers: {
200
207
  "Content-Type": "application/json",
201
- Authorization: `Bearer ${this.apiKey}`
208
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
202
209
  },
203
210
  body,
204
211
  signal: controller.signal
@@ -359,7 +366,7 @@ var HttpClient = class {
359
366
  try {
360
367
  const response = await fetch(url, {
361
368
  method: "GET",
362
- headers: { Authorization: `Bearer ${this.apiKey}` },
369
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
363
370
  signal: controller.signal
364
371
  });
365
372
  if (!response.ok) {
@@ -395,7 +402,7 @@ var HttpClient = class {
395
402
  try {
396
403
  const response = await fetch(url, {
397
404
  method: "GET",
398
- headers: { Authorization: `Bearer ${this.apiKey}` },
405
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
399
406
  signal: controller.signal
400
407
  });
401
408
  if (!response.ok) {
@@ -1359,6 +1366,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1359
1366
 
1360
1367
  // src/langgraph.ts
1361
1368
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
1369
+ var CHAIN_RUN_TYPES = /* @__PURE__ */ new Set(["chain", "parser", "prompt"]);
1362
1370
  var LANGGRAPH_METADATA_KEYS = [
1363
1371
  "langgraph_step",
1364
1372
  "langgraph_node",
@@ -1369,6 +1377,18 @@ var LANGGRAPH_METADATA_KEYS = [
1369
1377
  function nowIso2() {
1370
1378
  return (/* @__PURE__ */ new Date()).toISOString();
1371
1379
  }
1380
+ function normalizeChainStartArgs(parentRunIdOrRunType, runTypeOrRunName, runNameOrParentRunId) {
1381
+ if (parentRunIdOrRunType && CHAIN_RUN_TYPES.has(parentRunIdOrRunType)) {
1382
+ return {
1383
+ parentRunId: runNameOrParentRunId,
1384
+ runName: runTypeOrRunName
1385
+ };
1386
+ }
1387
+ return {
1388
+ parentRunId: parentRunIdOrRunType,
1389
+ runName: runNameOrParentRunId
1390
+ };
1391
+ }
1372
1392
  function convertMessage(message) {
1373
1393
  if (typeof message !== "object" || message === null) {
1374
1394
  return { role: "unknown", content: String(message) };
@@ -1580,6 +1600,7 @@ var BitfabLangGraphCallbackHandler = class {
1580
1600
  const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
1581
1601
  let invocation;
1582
1602
  let effectiveParentId;
1603
+ let isRootInvocation = false;
1583
1604
  if (parentSpan) {
1584
1605
  const existing = this.invocations.get(parentSpan.rootRunId);
1585
1606
  if (existing) {
@@ -1610,6 +1631,7 @@ var BitfabLangGraphCallbackHandler = class {
1610
1631
  };
1611
1632
  this.invocations.set(runId, invocation);
1612
1633
  effectiveParentId = activeContext?.spanId ?? null;
1634
+ isRootInvocation = true;
1613
1635
  }
1614
1636
  const lgMetadata = extractLangGraphMetadata(metadata);
1615
1637
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
@@ -1632,6 +1654,9 @@ var BitfabLangGraphCallbackHandler = class {
1632
1654
  spanInfo.hidden = true;
1633
1655
  }
1634
1656
  this.runToSpan.set(runId, spanInfo);
1657
+ if (isRootInvocation) {
1658
+ this.sendTraceStart(spanInfo);
1659
+ }
1635
1660
  return spanInfo;
1636
1661
  }
1637
1662
  completeSpan(runId, output, error, extraContexts) {
@@ -1722,11 +1747,35 @@ var BitfabLangGraphCallbackHandler = class {
1722
1747
  } catch {
1723
1748
  }
1724
1749
  }
1750
+ sendTraceStart(rootSpan) {
1751
+ const traceData = {
1752
+ type: "sdk-function",
1753
+ source: "typescript-sdk-langgraph",
1754
+ traceFunctionKey: this.traceFunctionKey,
1755
+ externalTrace: {
1756
+ id: rootSpan.traceId,
1757
+ started_at: rootSpan.startedAt,
1758
+ workflow_name: this.traceFunctionKey
1759
+ },
1760
+ completed: false
1761
+ };
1762
+ const finalized = finalizeTracePayload(traceData);
1763
+ try {
1764
+ this.httpClient.sendExternalTrace(finalized);
1765
+ } catch {
1766
+ }
1767
+ }
1725
1768
  // ── chain callbacks (graph nodes) ─────────────────────────────
1726
- async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
1769
+ async handleChainStart(chain, inputs, runId, parentRunIdOrRunType, tags, metadata, runTypeOrRunName, runNameOrParentRunId) {
1727
1770
  try {
1728
- const idArr = chain.id;
1729
- const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
1771
+ const { parentRunId, runName } = normalizeChainStartArgs(
1772
+ parentRunIdOrRunType,
1773
+ runTypeOrRunName,
1774
+ runNameOrParentRunId
1775
+ );
1776
+ const serialized = chain ?? {};
1777
+ const idArr = serialized.id;
1778
+ const name = runName ?? serialized.name ?? idArr?.[idArr.length - 1] ?? "chain";
1730
1779
  this.startSpan(
1731
1780
  runId,
1732
1781
  parentRunId,
@@ -1761,11 +1810,12 @@ var BitfabLangGraphCallbackHandler = class {
1761
1810
  }
1762
1811
  }
1763
1812
  // ── LLM callbacks ─────────────────────────────────────────────
1764
- async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
1813
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata, runName) {
1765
1814
  try {
1766
- const model = extractModelName(llm, metadata);
1767
- const idArr = llm.id;
1768
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1815
+ const serialized = llm ?? {};
1816
+ const model = extractModelName(serialized, metadata);
1817
+ const idArr = serialized.id;
1818
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
1769
1819
  const converted = messages.map((batch) => batch.map(convertMessage));
1770
1820
  const spanInfo = this.startSpan(
1771
1821
  runId,
@@ -1780,11 +1830,12 @@ var BitfabLangGraphCallbackHandler = class {
1780
1830
  } catch {
1781
1831
  }
1782
1832
  }
1783
- async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
1833
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata, runName) {
1784
1834
  try {
1785
- const model = extractModelName(llm, metadata);
1786
- const idArr = llm.id;
1787
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
1835
+ const serialized = llm ?? {};
1836
+ const model = extractModelName(serialized, metadata);
1837
+ const idArr = serialized.id;
1838
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
1788
1839
  const spanInfo = this.startSpan(
1789
1840
  runId,
1790
1841
  parentRunId,
@@ -1837,9 +1888,10 @@ var BitfabLangGraphCallbackHandler = class {
1837
1888
  async handleLLMNewToken() {
1838
1889
  }
1839
1890
  // ── tool callbacks ────────────────────────────────────────────
1840
- async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
1891
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
1841
1892
  try {
1842
- const name = tool.name ?? "tool";
1893
+ const serialized = tool ?? {};
1894
+ const name = runName ?? serialized.name ?? "tool";
1843
1895
  this.startSpan(
1844
1896
  runId,
1845
1897
  parentRunId,
@@ -1869,9 +1921,10 @@ var BitfabLangGraphCallbackHandler = class {
1869
1921
  }
1870
1922
  }
1871
1923
  // ── retriever callbacks ───────────────────────────────────────
1872
- async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
1924
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, runName) {
1873
1925
  try {
1874
- const name = retriever.name ?? "retriever";
1926
+ const serialized = retriever ?? {};
1927
+ const name = runName ?? serialized.name ?? "retriever";
1875
1928
  this.startSpan(
1876
1929
  runId,
1877
1930
  parentRunId,
@@ -2660,6 +2713,12 @@ function getCurrentTrace() {
2660
2713
  }
2661
2714
  };
2662
2715
  }
2716
+ function readEnv(name) {
2717
+ if (typeof process !== "undefined" && process.env) {
2718
+ return process.env[name];
2719
+ }
2720
+ return void 0;
2721
+ }
2663
2722
  var Bitfab = class {
2664
2723
  /**
2665
2724
  * Initialize the Bitfab client.
@@ -2667,30 +2726,75 @@ var Bitfab = class {
2667
2726
  * @param config - Configuration options for the client
2668
2727
  */
2669
2728
  constructor(config) {
2670
- this.apiKey = config.apiKey;
2729
+ /** Gate the empty-key warning to fire at most once. */
2730
+ this.apiKeyWarned = false;
2731
+ this.apiKeyConfig = config.apiKey;
2671
2732
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
2672
2733
  this.timeout = config.timeout ?? 12e4;
2673
2734
  this.envVars = config.envVars ?? {};
2674
- const enabled = config.enabled ?? true;
2675
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
2676
- console.warn(
2677
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
2678
- );
2679
- this.enabled = false;
2680
- } else {
2681
- this.enabled = enabled;
2682
- }
2735
+ this.explicitlyEnabled = config.enabled ?? true;
2736
+ this.strict = config.strict ?? false;
2683
2737
  this.bamlClient = config.bamlClient ?? null;
2684
2738
  if (config.dbSnapshot) {
2685
2739
  validateDbSnapshotConfig(config.dbSnapshot);
2686
2740
  }
2687
2741
  this.dbSnapshot = config.dbSnapshot;
2688
2742
  this.httpClient = new HttpClient({
2689
- apiKey: this.apiKey,
2743
+ apiKey: () => this.resolveApiKey(),
2690
2744
  serviceUrl: this.serviceUrl,
2691
2745
  timeout: this.timeout
2692
2746
  });
2693
2747
  }
2748
+ /**
2749
+ * Resolve the API key lazily, the first time a span actually needs it.
2750
+ *
2751
+ * The key is intentionally NOT read at construction. In ESM, a shim that
2752
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
2753
+ * evaluated before the importing script's body runs `dotenv.config()`, so
2754
+ * the key would be empty at construction even though it is set moments
2755
+ * later. Resolving here (at first `withSpan` call / first request) reads
2756
+ * the key after env loading has run.
2757
+ *
2758
+ * Resolution order: the configured value (string, or function called each
2759
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
2760
+ * from the environment. Once a non-empty key is found it is cached, so an
2761
+ * early resolve that found nothing never poisons a later one.
2762
+ */
2763
+ resolveApiKey() {
2764
+ if (this.resolvedApiKey !== void 0) {
2765
+ return this.resolvedApiKey;
2766
+ }
2767
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
2768
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
2769
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
2770
+ if (key) {
2771
+ this.resolvedApiKey = key;
2772
+ return key;
2773
+ }
2774
+ if (this.strict) {
2775
+ throw new BitfabError(
2776
+ "Bitfab: no API key resolved. Set BITFAB_API_KEY or pass apiKey to new Bitfab(). If a script loads env with dotenv, load it before the module that constructs the client is imported (e.g. `node --env-file=.env script.ts`), or pass `apiKey: () => process.env.BITFAB_API_KEY`."
2777
+ );
2778
+ }
2779
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
2780
+ this.apiKeyWarned = true;
2781
+ console.warn(
2782
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
2783
+ );
2784
+ }
2785
+ return void 0;
2786
+ }
2787
+ /**
2788
+ * Whether tracing should run, decided lazily at call time (not frozen at
2789
+ * construction). An explicit `enabled: false` short-circuits without ever
2790
+ * touching the key.
2791
+ */
2792
+ isTracingEnabled() {
2793
+ if (!this.explicitlyEnabled) {
2794
+ return false;
2795
+ }
2796
+ return this.resolveApiKey() !== void 0;
2797
+ }
2694
2798
  /**
2695
2799
  * Fetch the function with its current version and BAML prompt from the server.
2696
2800
  *
@@ -2783,7 +2887,9 @@ var Bitfab = class {
2783
2887
  */
2784
2888
  getOpenAiTracingProcessor() {
2785
2889
  return new BitfabOpenAITracingProcessor({
2786
- apiKey: this.apiKey,
2890
+ // Resolved at getter-call time (framework handlers are set up after env
2891
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
2892
+ apiKey: this.resolveApiKey(),
2787
2893
  serviceUrl: this.serviceUrl,
2788
2894
  getActiveSpanContext: () => {
2789
2895
  const stack = getSpanStack();
@@ -2842,7 +2948,7 @@ var Bitfab = class {
2842
2948
  */
2843
2949
  getLangGraphCallbackHandler(traceFunctionKey) {
2844
2950
  return new BitfabLangGraphCallbackHandler({
2845
- apiKey: this.apiKey,
2951
+ apiKey: this.resolveApiKey(),
2846
2952
  traceFunctionKey,
2847
2953
  serviceUrl: this.serviceUrl,
2848
2954
  getActiveSpanContext: () => {
@@ -2894,7 +3000,7 @@ var Bitfab = class {
2894
3000
  */
2895
3001
  getClaudeAgentHandler(traceFunctionKey) {
2896
3002
  return new BitfabClaudeAgentHandler({
2897
- apiKey: this.apiKey,
3003
+ apiKey: this.resolveApiKey(),
2898
3004
  traceFunctionKey,
2899
3005
  serviceUrl: this.serviceUrl,
2900
3006
  getActiveSpanContext: () => {
@@ -3066,9 +3172,8 @@ var Bitfab = class {
3066
3172
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3067
3173
  */
3068
3174
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3069
- if (!this.enabled) {
3070
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3071
- return fn2;
3175
+ if (!this.explicitlyEnabled) {
3176
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3072
3177
  }
3073
3178
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3074
3179
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3083,6 +3188,9 @@ var Bitfab = class {
3083
3188
  }
3084
3189
  })();
3085
3190
  const wrappedFn = function(...args) {
3191
+ if (!self.isTracingEnabled()) {
3192
+ return fn.apply(this, args);
3193
+ }
3086
3194
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3087
3195
  return asyncLocalStorageReady.then(
3088
3196
  () => wrappedFn.apply(this, args)
@@ -3343,7 +3451,7 @@ var Bitfab = class {
3343
3451
  return {
3344
3452
  traceId,
3345
3453
  addContext: (context) => {
3346
- if (!this.enabled) {
3454
+ if (!this.isTracingEnabled()) {
3347
3455
  return Promise.resolve();
3348
3456
  }
3349
3457
  if (typeof context !== "object" || context === null) {
@@ -3354,7 +3462,7 @@ var Bitfab = class {
3354
3462
  });
3355
3463
  },
3356
3464
  setMetadata: (metadata) => {
3357
- if (!this.enabled) {
3465
+ if (!this.isTracingEnabled()) {
3358
3466
  return Promise.resolve();
3359
3467
  }
3360
3468
  if (typeof metadata !== "object" || metadata === null) {
@@ -3363,7 +3471,7 @@ var Bitfab = class {
3363
3471
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
3364
3472
  },
3365
3473
  setSessionId: (sessionId) => {
3366
- if (!this.enabled) {
3474
+ if (!this.isTracingEnabled()) {
3367
3475
  return Promise.resolve();
3368
3476
  }
3369
3477
  if (typeof sessionId !== "string" || sessionId.length === 0) {
@@ -3752,4 +3860,4 @@ export {
3752
3860
  BitfabFunction,
3753
3861
  finalizers
3754
3862
  };
3755
- //# sourceMappingURL=chunk-H5QQ54UI.js.map
3863
+ //# sourceMappingURL=chunk-RS5Z6YXY.js.map