bitfab 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.
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  flushTraces,
24
24
  getCurrentSpan,
25
25
  getCurrentTrace
26
- } from "./chunk-H5QQ54UI.js";
26
+ } from "./chunk-RS5Z6YXY.js";
27
27
  import {
28
28
  BITFAB_PROGRESS_PREFIX,
29
29
  BitfabError,
package/dist/node.cjs CHANGED
@@ -659,7 +659,7 @@ registerAsyncLocalStorageClass(
659
659
  );
660
660
 
661
661
  // src/version.generated.ts
662
- var __version__ = "0.27.1";
662
+ var __version__ = "0.28.0";
663
663
 
664
664
  // src/constants.ts
665
665
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -818,6 +818,13 @@ var HttpClient = class {
818
818
  this.serviceUrl = config.serviceUrl;
819
819
  this.timeout = config.timeout ?? 12e4;
820
820
  }
821
+ /**
822
+ * Resolve the API key at the moment it is needed (request time), invoking
823
+ * the function form if one was supplied. Never read at construction.
824
+ */
825
+ resolveApiKey() {
826
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
827
+ }
821
828
  /**
822
829
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
823
830
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -848,7 +855,7 @@ var HttpClient = class {
848
855
  method,
849
856
  headers: {
850
857
  "Content-Type": "application/json",
851
- Authorization: `Bearer ${this.apiKey}`
858
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
852
859
  },
853
860
  body,
854
861
  signal: controller.signal
@@ -1009,7 +1016,7 @@ var HttpClient = class {
1009
1016
  try {
1010
1017
  const response = await fetch(url, {
1011
1018
  method: "GET",
1012
- headers: { Authorization: `Bearer ${this.apiKey}` },
1019
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1013
1020
  signal: controller.signal
1014
1021
  });
1015
1022
  if (!response.ok) {
@@ -1045,7 +1052,7 @@ var HttpClient = class {
1045
1052
  try {
1046
1053
  const response = await fetch(url, {
1047
1054
  method: "GET",
1048
- headers: { Authorization: `Bearer ${this.apiKey}` },
1055
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1049
1056
  signal: controller.signal
1050
1057
  });
1051
1058
  if (!response.ok) {
@@ -2019,6 +2026,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
2019
2026
  init_randomUuid();
2020
2027
  init_serialize();
2021
2028
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
2029
+ var CHAIN_RUN_TYPES = /* @__PURE__ */ new Set(["chain", "parser", "prompt"]);
2022
2030
  var LANGGRAPH_METADATA_KEYS = [
2023
2031
  "langgraph_step",
2024
2032
  "langgraph_node",
@@ -2029,6 +2037,18 @@ var LANGGRAPH_METADATA_KEYS = [
2029
2037
  function nowIso2() {
2030
2038
  return (/* @__PURE__ */ new Date()).toISOString();
2031
2039
  }
2040
+ function normalizeChainStartArgs(parentRunIdOrRunType, runTypeOrRunName, runNameOrParentRunId) {
2041
+ if (parentRunIdOrRunType && CHAIN_RUN_TYPES.has(parentRunIdOrRunType)) {
2042
+ return {
2043
+ parentRunId: runNameOrParentRunId,
2044
+ runName: runTypeOrRunName
2045
+ };
2046
+ }
2047
+ return {
2048
+ parentRunId: parentRunIdOrRunType,
2049
+ runName: runNameOrParentRunId
2050
+ };
2051
+ }
2032
2052
  function convertMessage(message) {
2033
2053
  if (typeof message !== "object" || message === null) {
2034
2054
  return { role: "unknown", content: String(message) };
@@ -2240,6 +2260,7 @@ var BitfabLangGraphCallbackHandler = class {
2240
2260
  const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
2241
2261
  let invocation;
2242
2262
  let effectiveParentId;
2263
+ let isRootInvocation = false;
2243
2264
  if (parentSpan) {
2244
2265
  const existing = this.invocations.get(parentSpan.rootRunId);
2245
2266
  if (existing) {
@@ -2270,6 +2291,7 @@ var BitfabLangGraphCallbackHandler = class {
2270
2291
  };
2271
2292
  this.invocations.set(runId, invocation);
2272
2293
  effectiveParentId = activeContext?.spanId ?? null;
2294
+ isRootInvocation = true;
2273
2295
  }
2274
2296
  const lgMetadata = extractLangGraphMetadata(metadata);
2275
2297
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
@@ -2292,6 +2314,9 @@ var BitfabLangGraphCallbackHandler = class {
2292
2314
  spanInfo.hidden = true;
2293
2315
  }
2294
2316
  this.runToSpan.set(runId, spanInfo);
2317
+ if (isRootInvocation) {
2318
+ this.sendTraceStart(spanInfo);
2319
+ }
2295
2320
  return spanInfo;
2296
2321
  }
2297
2322
  completeSpan(runId, output, error, extraContexts) {
@@ -2382,11 +2407,35 @@ var BitfabLangGraphCallbackHandler = class {
2382
2407
  } catch {
2383
2408
  }
2384
2409
  }
2410
+ sendTraceStart(rootSpan) {
2411
+ const traceData = {
2412
+ type: "sdk-function",
2413
+ source: "typescript-sdk-langgraph",
2414
+ traceFunctionKey: this.traceFunctionKey,
2415
+ externalTrace: {
2416
+ id: rootSpan.traceId,
2417
+ started_at: rootSpan.startedAt,
2418
+ workflow_name: this.traceFunctionKey
2419
+ },
2420
+ completed: false
2421
+ };
2422
+ const finalized = finalizeTracePayload(traceData);
2423
+ try {
2424
+ this.httpClient.sendExternalTrace(finalized);
2425
+ } catch {
2426
+ }
2427
+ }
2385
2428
  // ── chain callbacks (graph nodes) ─────────────────────────────
2386
- async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
2429
+ async handleChainStart(chain, inputs, runId, parentRunIdOrRunType, tags, metadata, runTypeOrRunName, runNameOrParentRunId) {
2387
2430
  try {
2388
- const idArr = chain.id;
2389
- const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
2431
+ const { parentRunId, runName } = normalizeChainStartArgs(
2432
+ parentRunIdOrRunType,
2433
+ runTypeOrRunName,
2434
+ runNameOrParentRunId
2435
+ );
2436
+ const serialized = chain ?? {};
2437
+ const idArr = serialized.id;
2438
+ const name = runName ?? serialized.name ?? idArr?.[idArr.length - 1] ?? "chain";
2390
2439
  this.startSpan(
2391
2440
  runId,
2392
2441
  parentRunId,
@@ -2421,11 +2470,12 @@ var BitfabLangGraphCallbackHandler = class {
2421
2470
  }
2422
2471
  }
2423
2472
  // ── LLM callbacks ─────────────────────────────────────────────
2424
- async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
2473
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata, runName) {
2425
2474
  try {
2426
- const model = extractModelName(llm, metadata);
2427
- const idArr = llm.id;
2428
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2475
+ const serialized = llm ?? {};
2476
+ const model = extractModelName(serialized, metadata);
2477
+ const idArr = serialized.id;
2478
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2429
2479
  const converted = messages.map((batch) => batch.map(convertMessage));
2430
2480
  const spanInfo = this.startSpan(
2431
2481
  runId,
@@ -2440,11 +2490,12 @@ var BitfabLangGraphCallbackHandler = class {
2440
2490
  } catch {
2441
2491
  }
2442
2492
  }
2443
- async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
2493
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata, runName) {
2444
2494
  try {
2445
- const model = extractModelName(llm, metadata);
2446
- const idArr = llm.id;
2447
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2495
+ const serialized = llm ?? {};
2496
+ const model = extractModelName(serialized, metadata);
2497
+ const idArr = serialized.id;
2498
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2448
2499
  const spanInfo = this.startSpan(
2449
2500
  runId,
2450
2501
  parentRunId,
@@ -2497,9 +2548,10 @@ var BitfabLangGraphCallbackHandler = class {
2497
2548
  async handleLLMNewToken() {
2498
2549
  }
2499
2550
  // ── tool callbacks ────────────────────────────────────────────
2500
- async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
2551
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
2501
2552
  try {
2502
- const name = tool.name ?? "tool";
2553
+ const serialized = tool ?? {};
2554
+ const name = runName ?? serialized.name ?? "tool";
2503
2555
  this.startSpan(
2504
2556
  runId,
2505
2557
  parentRunId,
@@ -2529,9 +2581,10 @@ var BitfabLangGraphCallbackHandler = class {
2529
2581
  }
2530
2582
  }
2531
2583
  // ── retriever callbacks ───────────────────────────────────────
2532
- async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
2584
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, runName) {
2533
2585
  try {
2534
- const name = retriever.name ?? "retriever";
2586
+ const serialized = retriever ?? {};
2587
+ const name = runName ?? serialized.name ?? "retriever";
2535
2588
  this.startSpan(
2536
2589
  runId,
2537
2590
  parentRunId,
@@ -3329,6 +3382,12 @@ function getCurrentTrace() {
3329
3382
  }
3330
3383
  };
3331
3384
  }
3385
+ function readEnv(name) {
3386
+ if (typeof process !== "undefined" && process.env) {
3387
+ return process.env[name];
3388
+ }
3389
+ return void 0;
3390
+ }
3332
3391
  var Bitfab = class {
3333
3392
  /**
3334
3393
  * Initialize the Bitfab client.
@@ -3336,30 +3395,75 @@ var Bitfab = class {
3336
3395
  * @param config - Configuration options for the client
3337
3396
  */
3338
3397
  constructor(config) {
3339
- this.apiKey = config.apiKey;
3398
+ /** Gate the empty-key warning to fire at most once. */
3399
+ this.apiKeyWarned = false;
3400
+ this.apiKeyConfig = config.apiKey;
3340
3401
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3341
3402
  this.timeout = config.timeout ?? 12e4;
3342
3403
  this.envVars = config.envVars ?? {};
3343
- const enabled = config.enabled ?? true;
3344
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3345
- console.warn(
3346
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3347
- );
3348
- this.enabled = false;
3349
- } else {
3350
- this.enabled = enabled;
3351
- }
3404
+ this.explicitlyEnabled = config.enabled ?? true;
3405
+ this.strict = config.strict ?? false;
3352
3406
  this.bamlClient = config.bamlClient ?? null;
3353
3407
  if (config.dbSnapshot) {
3354
3408
  validateDbSnapshotConfig(config.dbSnapshot);
3355
3409
  }
3356
3410
  this.dbSnapshot = config.dbSnapshot;
3357
3411
  this.httpClient = new HttpClient({
3358
- apiKey: this.apiKey,
3412
+ apiKey: () => this.resolveApiKey(),
3359
3413
  serviceUrl: this.serviceUrl,
3360
3414
  timeout: this.timeout
3361
3415
  });
3362
3416
  }
3417
+ /**
3418
+ * Resolve the API key lazily, the first time a span actually needs it.
3419
+ *
3420
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3421
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3422
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3423
+ * the key would be empty at construction even though it is set moments
3424
+ * later. Resolving here (at first `withSpan` call / first request) reads
3425
+ * the key after env loading has run.
3426
+ *
3427
+ * Resolution order: the configured value (string, or function called each
3428
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3429
+ * from the environment. Once a non-empty key is found it is cached, so an
3430
+ * early resolve that found nothing never poisons a later one.
3431
+ */
3432
+ resolveApiKey() {
3433
+ if (this.resolvedApiKey !== void 0) {
3434
+ return this.resolvedApiKey;
3435
+ }
3436
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3437
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3438
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3439
+ if (key) {
3440
+ this.resolvedApiKey = key;
3441
+ return key;
3442
+ }
3443
+ if (this.strict) {
3444
+ throw new BitfabError(
3445
+ "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`."
3446
+ );
3447
+ }
3448
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3449
+ this.apiKeyWarned = true;
3450
+ console.warn(
3451
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3452
+ );
3453
+ }
3454
+ return void 0;
3455
+ }
3456
+ /**
3457
+ * Whether tracing should run, decided lazily at call time (not frozen at
3458
+ * construction). An explicit `enabled: false` short-circuits without ever
3459
+ * touching the key.
3460
+ */
3461
+ isTracingEnabled() {
3462
+ if (!this.explicitlyEnabled) {
3463
+ return false;
3464
+ }
3465
+ return this.resolveApiKey() !== void 0;
3466
+ }
3363
3467
  /**
3364
3468
  * Fetch the function with its current version and BAML prompt from the server.
3365
3469
  *
@@ -3452,7 +3556,9 @@ var Bitfab = class {
3452
3556
  */
3453
3557
  getOpenAiTracingProcessor() {
3454
3558
  return new BitfabOpenAITracingProcessor({
3455
- apiKey: this.apiKey,
3559
+ // Resolved at getter-call time (framework handlers are set up after env
3560
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3561
+ apiKey: this.resolveApiKey(),
3456
3562
  serviceUrl: this.serviceUrl,
3457
3563
  getActiveSpanContext: () => {
3458
3564
  const stack = getSpanStack();
@@ -3511,7 +3617,7 @@ var Bitfab = class {
3511
3617
  */
3512
3618
  getLangGraphCallbackHandler(traceFunctionKey) {
3513
3619
  return new BitfabLangGraphCallbackHandler({
3514
- apiKey: this.apiKey,
3620
+ apiKey: this.resolveApiKey(),
3515
3621
  traceFunctionKey,
3516
3622
  serviceUrl: this.serviceUrl,
3517
3623
  getActiveSpanContext: () => {
@@ -3563,7 +3669,7 @@ var Bitfab = class {
3563
3669
  */
3564
3670
  getClaudeAgentHandler(traceFunctionKey) {
3565
3671
  return new BitfabClaudeAgentHandler({
3566
- apiKey: this.apiKey,
3672
+ apiKey: this.resolveApiKey(),
3567
3673
  traceFunctionKey,
3568
3674
  serviceUrl: this.serviceUrl,
3569
3675
  getActiveSpanContext: () => {
@@ -3735,9 +3841,8 @@ var Bitfab = class {
3735
3841
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3736
3842
  */
3737
3843
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3738
- if (!this.enabled) {
3739
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3740
- return fn2;
3844
+ if (!this.explicitlyEnabled) {
3845
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3741
3846
  }
3742
3847
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3743
3848
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3752,6 +3857,9 @@ var Bitfab = class {
3752
3857
  }
3753
3858
  })();
3754
3859
  const wrappedFn = function(...args) {
3860
+ if (!self.isTracingEnabled()) {
3861
+ return fn.apply(this, args);
3862
+ }
3755
3863
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3756
3864
  return asyncLocalStorageReady.then(
3757
3865
  () => wrappedFn.apply(this, args)
@@ -4012,7 +4120,7 @@ var Bitfab = class {
4012
4120
  return {
4013
4121
  traceId,
4014
4122
  addContext: (context) => {
4015
- if (!this.enabled) {
4123
+ if (!this.isTracingEnabled()) {
4016
4124
  return Promise.resolve();
4017
4125
  }
4018
4126
  if (typeof context !== "object" || context === null) {
@@ -4023,7 +4131,7 @@ var Bitfab = class {
4023
4131
  });
4024
4132
  },
4025
4133
  setMetadata: (metadata) => {
4026
- if (!this.enabled) {
4134
+ if (!this.isTracingEnabled()) {
4027
4135
  return Promise.resolve();
4028
4136
  }
4029
4137
  if (typeof metadata !== "object" || metadata === null) {
@@ -4032,7 +4140,7 @@ var Bitfab = class {
4032
4140
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4033
4141
  },
4034
4142
  setSessionId: (sessionId) => {
4035
- if (!this.enabled) {
4143
+ if (!this.isTracingEnabled()) {
4036
4144
  return Promise.resolve();
4037
4145
  }
4038
4146
  if (typeof sessionId !== "string" || sessionId.length === 0) {