@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.
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  flushTraces,
15
15
  getCurrentSpan,
16
16
  getCurrentTrace
17
- } from "./chunk-H5QQ54UI.js";
17
+ } from "./chunk-RS5Z6YXY.js";
18
18
  import {
19
19
  BITFAB_PROGRESS_PREFIX,
20
20
  BitfabError,
package/dist/node.cjs CHANGED
@@ -650,7 +650,7 @@ registerAsyncLocalStorageClass(
650
650
  );
651
651
 
652
652
  // src/version.generated.ts
653
- var __version__ = "0.27.1";
653
+ var __version__ = "0.28.0";
654
654
 
655
655
  // src/constants.ts
656
656
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -809,6 +809,13 @@ var HttpClient = class {
809
809
  this.serviceUrl = config.serviceUrl;
810
810
  this.timeout = config.timeout ?? 12e4;
811
811
  }
812
+ /**
813
+ * Resolve the API key at the moment it is needed (request time), invoking
814
+ * the function form if one was supplied. Never read at construction.
815
+ */
816
+ resolveApiKey() {
817
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
818
+ }
812
819
  /**
813
820
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
814
821
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -839,7 +846,7 @@ var HttpClient = class {
839
846
  method,
840
847
  headers: {
841
848
  "Content-Type": "application/json",
842
- Authorization: `Bearer ${this.apiKey}`
849
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
843
850
  },
844
851
  body,
845
852
  signal: controller.signal
@@ -1000,7 +1007,7 @@ var HttpClient = class {
1000
1007
  try {
1001
1008
  const response = await fetch(url, {
1002
1009
  method: "GET",
1003
- headers: { Authorization: `Bearer ${this.apiKey}` },
1010
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1004
1011
  signal: controller.signal
1005
1012
  });
1006
1013
  if (!response.ok) {
@@ -1036,7 +1043,7 @@ var HttpClient = class {
1036
1043
  try {
1037
1044
  const response = await fetch(url, {
1038
1045
  method: "GET",
1039
- headers: { Authorization: `Bearer ${this.apiKey}` },
1046
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1040
1047
  signal: controller.signal
1041
1048
  });
1042
1049
  if (!response.ok) {
@@ -2010,6 +2017,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
2010
2017
  init_randomUuid();
2011
2018
  init_serialize();
2012
2019
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
2020
+ var CHAIN_RUN_TYPES = /* @__PURE__ */ new Set(["chain", "parser", "prompt"]);
2013
2021
  var LANGGRAPH_METADATA_KEYS = [
2014
2022
  "langgraph_step",
2015
2023
  "langgraph_node",
@@ -2020,6 +2028,18 @@ var LANGGRAPH_METADATA_KEYS = [
2020
2028
  function nowIso2() {
2021
2029
  return (/* @__PURE__ */ new Date()).toISOString();
2022
2030
  }
2031
+ function normalizeChainStartArgs(parentRunIdOrRunType, runTypeOrRunName, runNameOrParentRunId) {
2032
+ if (parentRunIdOrRunType && CHAIN_RUN_TYPES.has(parentRunIdOrRunType)) {
2033
+ return {
2034
+ parentRunId: runNameOrParentRunId,
2035
+ runName: runTypeOrRunName
2036
+ };
2037
+ }
2038
+ return {
2039
+ parentRunId: parentRunIdOrRunType,
2040
+ runName: runNameOrParentRunId
2041
+ };
2042
+ }
2023
2043
  function convertMessage(message) {
2024
2044
  if (typeof message !== "object" || message === null) {
2025
2045
  return { role: "unknown", content: String(message) };
@@ -2231,6 +2251,7 @@ var BitfabLangGraphCallbackHandler = class {
2231
2251
  const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
2232
2252
  let invocation;
2233
2253
  let effectiveParentId;
2254
+ let isRootInvocation = false;
2234
2255
  if (parentSpan) {
2235
2256
  const existing = this.invocations.get(parentSpan.rootRunId);
2236
2257
  if (existing) {
@@ -2261,6 +2282,7 @@ var BitfabLangGraphCallbackHandler = class {
2261
2282
  };
2262
2283
  this.invocations.set(runId, invocation);
2263
2284
  effectiveParentId = activeContext?.spanId ?? null;
2285
+ isRootInvocation = true;
2264
2286
  }
2265
2287
  const lgMetadata = extractLangGraphMetadata(metadata);
2266
2288
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
@@ -2283,6 +2305,9 @@ var BitfabLangGraphCallbackHandler = class {
2283
2305
  spanInfo.hidden = true;
2284
2306
  }
2285
2307
  this.runToSpan.set(runId, spanInfo);
2308
+ if (isRootInvocation) {
2309
+ this.sendTraceStart(spanInfo);
2310
+ }
2286
2311
  return spanInfo;
2287
2312
  }
2288
2313
  completeSpan(runId, output, error, extraContexts) {
@@ -2373,11 +2398,35 @@ var BitfabLangGraphCallbackHandler = class {
2373
2398
  } catch {
2374
2399
  }
2375
2400
  }
2401
+ sendTraceStart(rootSpan) {
2402
+ const traceData = {
2403
+ type: "sdk-function",
2404
+ source: "typescript-sdk-langgraph",
2405
+ traceFunctionKey: this.traceFunctionKey,
2406
+ externalTrace: {
2407
+ id: rootSpan.traceId,
2408
+ started_at: rootSpan.startedAt,
2409
+ workflow_name: this.traceFunctionKey
2410
+ },
2411
+ completed: false
2412
+ };
2413
+ const finalized = finalizeTracePayload(traceData);
2414
+ try {
2415
+ this.httpClient.sendExternalTrace(finalized);
2416
+ } catch {
2417
+ }
2418
+ }
2376
2419
  // ── chain callbacks (graph nodes) ─────────────────────────────
2377
- async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
2420
+ async handleChainStart(chain, inputs, runId, parentRunIdOrRunType, tags, metadata, runTypeOrRunName, runNameOrParentRunId) {
2378
2421
  try {
2379
- const idArr = chain.id;
2380
- const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
2422
+ const { parentRunId, runName } = normalizeChainStartArgs(
2423
+ parentRunIdOrRunType,
2424
+ runTypeOrRunName,
2425
+ runNameOrParentRunId
2426
+ );
2427
+ const serialized = chain ?? {};
2428
+ const idArr = serialized.id;
2429
+ const name = runName ?? serialized.name ?? idArr?.[idArr.length - 1] ?? "chain";
2381
2430
  this.startSpan(
2382
2431
  runId,
2383
2432
  parentRunId,
@@ -2412,11 +2461,12 @@ var BitfabLangGraphCallbackHandler = class {
2412
2461
  }
2413
2462
  }
2414
2463
  // ── LLM callbacks ─────────────────────────────────────────────
2415
- async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
2464
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata, runName) {
2416
2465
  try {
2417
- const model = extractModelName(llm, metadata);
2418
- const idArr = llm.id;
2419
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2466
+ const serialized = llm ?? {};
2467
+ const model = extractModelName(serialized, metadata);
2468
+ const idArr = serialized.id;
2469
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2420
2470
  const converted = messages.map((batch) => batch.map(convertMessage));
2421
2471
  const spanInfo = this.startSpan(
2422
2472
  runId,
@@ -2431,11 +2481,12 @@ var BitfabLangGraphCallbackHandler = class {
2431
2481
  } catch {
2432
2482
  }
2433
2483
  }
2434
- async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
2484
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata, runName) {
2435
2485
  try {
2436
- const model = extractModelName(llm, metadata);
2437
- const idArr = llm.id;
2438
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2486
+ const serialized = llm ?? {};
2487
+ const model = extractModelName(serialized, metadata);
2488
+ const idArr = serialized.id;
2489
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2439
2490
  const spanInfo = this.startSpan(
2440
2491
  runId,
2441
2492
  parentRunId,
@@ -2488,9 +2539,10 @@ var BitfabLangGraphCallbackHandler = class {
2488
2539
  async handleLLMNewToken() {
2489
2540
  }
2490
2541
  // ── tool callbacks ────────────────────────────────────────────
2491
- async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
2542
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
2492
2543
  try {
2493
- const name = tool.name ?? "tool";
2544
+ const serialized = tool ?? {};
2545
+ const name = runName ?? serialized.name ?? "tool";
2494
2546
  this.startSpan(
2495
2547
  runId,
2496
2548
  parentRunId,
@@ -2520,9 +2572,10 @@ var BitfabLangGraphCallbackHandler = class {
2520
2572
  }
2521
2573
  }
2522
2574
  // ── retriever callbacks ───────────────────────────────────────
2523
- async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
2575
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, runName) {
2524
2576
  try {
2525
- const name = retriever.name ?? "retriever";
2577
+ const serialized = retriever ?? {};
2578
+ const name = runName ?? serialized.name ?? "retriever";
2526
2579
  this.startSpan(
2527
2580
  runId,
2528
2581
  parentRunId,
@@ -3320,6 +3373,12 @@ function getCurrentTrace() {
3320
3373
  }
3321
3374
  };
3322
3375
  }
3376
+ function readEnv(name) {
3377
+ if (typeof process !== "undefined" && process.env) {
3378
+ return process.env[name];
3379
+ }
3380
+ return void 0;
3381
+ }
3323
3382
  var Bitfab = class {
3324
3383
  /**
3325
3384
  * Initialize the Bitfab client.
@@ -3327,30 +3386,75 @@ var Bitfab = class {
3327
3386
  * @param config - Configuration options for the client
3328
3387
  */
3329
3388
  constructor(config) {
3330
- this.apiKey = config.apiKey;
3389
+ /** Gate the empty-key warning to fire at most once. */
3390
+ this.apiKeyWarned = false;
3391
+ this.apiKeyConfig = config.apiKey;
3331
3392
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3332
3393
  this.timeout = config.timeout ?? 12e4;
3333
3394
  this.envVars = config.envVars ?? {};
3334
- const enabled = config.enabled ?? true;
3335
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3336
- console.warn(
3337
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3338
- );
3339
- this.enabled = false;
3340
- } else {
3341
- this.enabled = enabled;
3342
- }
3395
+ this.explicitlyEnabled = config.enabled ?? true;
3396
+ this.strict = config.strict ?? false;
3343
3397
  this.bamlClient = config.bamlClient ?? null;
3344
3398
  if (config.dbSnapshot) {
3345
3399
  validateDbSnapshotConfig(config.dbSnapshot);
3346
3400
  }
3347
3401
  this.dbSnapshot = config.dbSnapshot;
3348
3402
  this.httpClient = new HttpClient({
3349
- apiKey: this.apiKey,
3403
+ apiKey: () => this.resolveApiKey(),
3350
3404
  serviceUrl: this.serviceUrl,
3351
3405
  timeout: this.timeout
3352
3406
  });
3353
3407
  }
3408
+ /**
3409
+ * Resolve the API key lazily, the first time a span actually needs it.
3410
+ *
3411
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3412
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3413
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3414
+ * the key would be empty at construction even though it is set moments
3415
+ * later. Resolving here (at first `withSpan` call / first request) reads
3416
+ * the key after env loading has run.
3417
+ *
3418
+ * Resolution order: the configured value (string, or function called each
3419
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3420
+ * from the environment. Once a non-empty key is found it is cached, so an
3421
+ * early resolve that found nothing never poisons a later one.
3422
+ */
3423
+ resolveApiKey() {
3424
+ if (this.resolvedApiKey !== void 0) {
3425
+ return this.resolvedApiKey;
3426
+ }
3427
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3428
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3429
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3430
+ if (key) {
3431
+ this.resolvedApiKey = key;
3432
+ return key;
3433
+ }
3434
+ if (this.strict) {
3435
+ throw new BitfabError(
3436
+ "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`."
3437
+ );
3438
+ }
3439
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3440
+ this.apiKeyWarned = true;
3441
+ console.warn(
3442
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3443
+ );
3444
+ }
3445
+ return void 0;
3446
+ }
3447
+ /**
3448
+ * Whether tracing should run, decided lazily at call time (not frozen at
3449
+ * construction). An explicit `enabled: false` short-circuits without ever
3450
+ * touching the key.
3451
+ */
3452
+ isTracingEnabled() {
3453
+ if (!this.explicitlyEnabled) {
3454
+ return false;
3455
+ }
3456
+ return this.resolveApiKey() !== void 0;
3457
+ }
3354
3458
  /**
3355
3459
  * Fetch the function with its current version and BAML prompt from the server.
3356
3460
  *
@@ -3443,7 +3547,9 @@ var Bitfab = class {
3443
3547
  */
3444
3548
  getOpenAiTracingProcessor() {
3445
3549
  return new BitfabOpenAITracingProcessor({
3446
- apiKey: this.apiKey,
3550
+ // Resolved at getter-call time (framework handlers are set up after env
3551
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3552
+ apiKey: this.resolveApiKey(),
3447
3553
  serviceUrl: this.serviceUrl,
3448
3554
  getActiveSpanContext: () => {
3449
3555
  const stack = getSpanStack();
@@ -3502,7 +3608,7 @@ var Bitfab = class {
3502
3608
  */
3503
3609
  getLangGraphCallbackHandler(traceFunctionKey) {
3504
3610
  return new BitfabLangGraphCallbackHandler({
3505
- apiKey: this.apiKey,
3611
+ apiKey: this.resolveApiKey(),
3506
3612
  traceFunctionKey,
3507
3613
  serviceUrl: this.serviceUrl,
3508
3614
  getActiveSpanContext: () => {
@@ -3554,7 +3660,7 @@ var Bitfab = class {
3554
3660
  */
3555
3661
  getClaudeAgentHandler(traceFunctionKey) {
3556
3662
  return new BitfabClaudeAgentHandler({
3557
- apiKey: this.apiKey,
3663
+ apiKey: this.resolveApiKey(),
3558
3664
  traceFunctionKey,
3559
3665
  serviceUrl: this.serviceUrl,
3560
3666
  getActiveSpanContext: () => {
@@ -3726,9 +3832,8 @@ var Bitfab = class {
3726
3832
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3727
3833
  */
3728
3834
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3729
- if (!this.enabled) {
3730
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3731
- return fn2;
3835
+ if (!this.explicitlyEnabled) {
3836
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3732
3837
  }
3733
3838
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3734
3839
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3743,6 +3848,9 @@ var Bitfab = class {
3743
3848
  }
3744
3849
  })();
3745
3850
  const wrappedFn = function(...args) {
3851
+ if (!self.isTracingEnabled()) {
3852
+ return fn.apply(this, args);
3853
+ }
3746
3854
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3747
3855
  return asyncLocalStorageReady.then(
3748
3856
  () => wrappedFn.apply(this, args)
@@ -4003,7 +4111,7 @@ var Bitfab = class {
4003
4111
  return {
4004
4112
  traceId,
4005
4113
  addContext: (context) => {
4006
- if (!this.enabled) {
4114
+ if (!this.isTracingEnabled()) {
4007
4115
  return Promise.resolve();
4008
4116
  }
4009
4117
  if (typeof context !== "object" || context === null) {
@@ -4014,7 +4122,7 @@ var Bitfab = class {
4014
4122
  });
4015
4123
  },
4016
4124
  setMetadata: (metadata) => {
4017
- if (!this.enabled) {
4125
+ if (!this.isTracingEnabled()) {
4018
4126
  return Promise.resolve();
4019
4127
  }
4020
4128
  if (typeof metadata !== "object" || metadata === null) {
@@ -4023,7 +4131,7 @@ var Bitfab = class {
4023
4131
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4024
4132
  },
4025
4133
  setSessionId: (sessionId) => {
4026
- if (!this.enabled) {
4134
+ if (!this.isTracingEnabled()) {
4027
4135
  return Promise.resolve();
4028
4136
  }
4029
4137
  if (typeof sessionId !== "string" || sessionId.length === 0) {