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.cjs CHANGED
@@ -645,7 +645,7 @@ __export(index_exports, {
645
645
  module.exports = __toCommonJS(index_exports);
646
646
 
647
647
  // src/version.generated.ts
648
- var __version__ = "0.27.1";
648
+ var __version__ = "0.28.0";
649
649
 
650
650
  // src/constants.ts
651
651
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -804,6 +804,13 @@ var HttpClient = class {
804
804
  this.serviceUrl = config.serviceUrl;
805
805
  this.timeout = config.timeout ?? 12e4;
806
806
  }
807
+ /**
808
+ * Resolve the API key at the moment it is needed (request time), invoking
809
+ * the function form if one was supplied. Never read at construction.
810
+ */
811
+ resolveApiKey() {
812
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
813
+ }
807
814
  /**
808
815
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
809
816
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -834,7 +841,7 @@ var HttpClient = class {
834
841
  method,
835
842
  headers: {
836
843
  "Content-Type": "application/json",
837
- Authorization: `Bearer ${this.apiKey}`
844
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
838
845
  },
839
846
  body,
840
847
  signal: controller.signal
@@ -995,7 +1002,7 @@ var HttpClient = class {
995
1002
  try {
996
1003
  const response = await fetch(url, {
997
1004
  method: "GET",
998
- headers: { Authorization: `Bearer ${this.apiKey}` },
1005
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
999
1006
  signal: controller.signal
1000
1007
  });
1001
1008
  if (!response.ok) {
@@ -1031,7 +1038,7 @@ var HttpClient = class {
1031
1038
  try {
1032
1039
  const response = await fetch(url, {
1033
1040
  method: "GET",
1034
- headers: { Authorization: `Bearer ${this.apiKey}` },
1041
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1035
1042
  signal: controller.signal
1036
1043
  });
1037
1044
  if (!response.ok) {
@@ -2005,6 +2012,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
2005
2012
  init_randomUuid();
2006
2013
  init_serialize();
2007
2014
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
2015
+ var CHAIN_RUN_TYPES = /* @__PURE__ */ new Set(["chain", "parser", "prompt"]);
2008
2016
  var LANGGRAPH_METADATA_KEYS = [
2009
2017
  "langgraph_step",
2010
2018
  "langgraph_node",
@@ -2015,6 +2023,18 @@ var LANGGRAPH_METADATA_KEYS = [
2015
2023
  function nowIso2() {
2016
2024
  return (/* @__PURE__ */ new Date()).toISOString();
2017
2025
  }
2026
+ function normalizeChainStartArgs(parentRunIdOrRunType, runTypeOrRunName, runNameOrParentRunId) {
2027
+ if (parentRunIdOrRunType && CHAIN_RUN_TYPES.has(parentRunIdOrRunType)) {
2028
+ return {
2029
+ parentRunId: runNameOrParentRunId,
2030
+ runName: runTypeOrRunName
2031
+ };
2032
+ }
2033
+ return {
2034
+ parentRunId: parentRunIdOrRunType,
2035
+ runName: runNameOrParentRunId
2036
+ };
2037
+ }
2018
2038
  function convertMessage(message) {
2019
2039
  if (typeof message !== "object" || message === null) {
2020
2040
  return { role: "unknown", content: String(message) };
@@ -2226,6 +2246,7 @@ var BitfabLangGraphCallbackHandler = class {
2226
2246
  const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
2227
2247
  let invocation;
2228
2248
  let effectiveParentId;
2249
+ let isRootInvocation = false;
2229
2250
  if (parentSpan) {
2230
2251
  const existing = this.invocations.get(parentSpan.rootRunId);
2231
2252
  if (existing) {
@@ -2256,6 +2277,7 @@ var BitfabLangGraphCallbackHandler = class {
2256
2277
  };
2257
2278
  this.invocations.set(runId, invocation);
2258
2279
  effectiveParentId = activeContext?.spanId ?? null;
2280
+ isRootInvocation = true;
2259
2281
  }
2260
2282
  const lgMetadata = extractLangGraphMetadata(metadata);
2261
2283
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
@@ -2278,6 +2300,9 @@ var BitfabLangGraphCallbackHandler = class {
2278
2300
  spanInfo.hidden = true;
2279
2301
  }
2280
2302
  this.runToSpan.set(runId, spanInfo);
2303
+ if (isRootInvocation) {
2304
+ this.sendTraceStart(spanInfo);
2305
+ }
2281
2306
  return spanInfo;
2282
2307
  }
2283
2308
  completeSpan(runId, output, error, extraContexts) {
@@ -2368,11 +2393,35 @@ var BitfabLangGraphCallbackHandler = class {
2368
2393
  } catch {
2369
2394
  }
2370
2395
  }
2396
+ sendTraceStart(rootSpan) {
2397
+ const traceData = {
2398
+ type: "sdk-function",
2399
+ source: "typescript-sdk-langgraph",
2400
+ traceFunctionKey: this.traceFunctionKey,
2401
+ externalTrace: {
2402
+ id: rootSpan.traceId,
2403
+ started_at: rootSpan.startedAt,
2404
+ workflow_name: this.traceFunctionKey
2405
+ },
2406
+ completed: false
2407
+ };
2408
+ const finalized = finalizeTracePayload(traceData);
2409
+ try {
2410
+ this.httpClient.sendExternalTrace(finalized);
2411
+ } catch {
2412
+ }
2413
+ }
2371
2414
  // ── chain callbacks (graph nodes) ─────────────────────────────
2372
- async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
2415
+ async handleChainStart(chain, inputs, runId, parentRunIdOrRunType, tags, metadata, runTypeOrRunName, runNameOrParentRunId) {
2373
2416
  try {
2374
- const idArr = chain.id;
2375
- const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
2417
+ const { parentRunId, runName } = normalizeChainStartArgs(
2418
+ parentRunIdOrRunType,
2419
+ runTypeOrRunName,
2420
+ runNameOrParentRunId
2421
+ );
2422
+ const serialized = chain ?? {};
2423
+ const idArr = serialized.id;
2424
+ const name = runName ?? serialized.name ?? idArr?.[idArr.length - 1] ?? "chain";
2376
2425
  this.startSpan(
2377
2426
  runId,
2378
2427
  parentRunId,
@@ -2407,11 +2456,12 @@ var BitfabLangGraphCallbackHandler = class {
2407
2456
  }
2408
2457
  }
2409
2458
  // ── LLM callbacks ─────────────────────────────────────────────
2410
- async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
2459
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata, runName) {
2411
2460
  try {
2412
- const model = extractModelName(llm, metadata);
2413
- const idArr = llm.id;
2414
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2461
+ const serialized = llm ?? {};
2462
+ const model = extractModelName(serialized, metadata);
2463
+ const idArr = serialized.id;
2464
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2415
2465
  const converted = messages.map((batch) => batch.map(convertMessage));
2416
2466
  const spanInfo = this.startSpan(
2417
2467
  runId,
@@ -2426,11 +2476,12 @@ var BitfabLangGraphCallbackHandler = class {
2426
2476
  } catch {
2427
2477
  }
2428
2478
  }
2429
- async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
2479
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata, runName) {
2430
2480
  try {
2431
- const model = extractModelName(llm, metadata);
2432
- const idArr = llm.id;
2433
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2481
+ const serialized = llm ?? {};
2482
+ const model = extractModelName(serialized, metadata);
2483
+ const idArr = serialized.id;
2484
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2434
2485
  const spanInfo = this.startSpan(
2435
2486
  runId,
2436
2487
  parentRunId,
@@ -2483,9 +2534,10 @@ var BitfabLangGraphCallbackHandler = class {
2483
2534
  async handleLLMNewToken() {
2484
2535
  }
2485
2536
  // ── tool callbacks ────────────────────────────────────────────
2486
- async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
2537
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
2487
2538
  try {
2488
- const name = tool.name ?? "tool";
2539
+ const serialized = tool ?? {};
2540
+ const name = runName ?? serialized.name ?? "tool";
2489
2541
  this.startSpan(
2490
2542
  runId,
2491
2543
  parentRunId,
@@ -2515,9 +2567,10 @@ var BitfabLangGraphCallbackHandler = class {
2515
2567
  }
2516
2568
  }
2517
2569
  // ── retriever callbacks ───────────────────────────────────────
2518
- async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
2570
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, runName) {
2519
2571
  try {
2520
- const name = retriever.name ?? "retriever";
2572
+ const serialized = retriever ?? {};
2573
+ const name = runName ?? serialized.name ?? "retriever";
2521
2574
  this.startSpan(
2522
2575
  runId,
2523
2576
  parentRunId,
@@ -3315,6 +3368,12 @@ function getCurrentTrace() {
3315
3368
  }
3316
3369
  };
3317
3370
  }
3371
+ function readEnv(name) {
3372
+ if (typeof process !== "undefined" && process.env) {
3373
+ return process.env[name];
3374
+ }
3375
+ return void 0;
3376
+ }
3318
3377
  var Bitfab = class {
3319
3378
  /**
3320
3379
  * Initialize the Bitfab client.
@@ -3322,30 +3381,75 @@ var Bitfab = class {
3322
3381
  * @param config - Configuration options for the client
3323
3382
  */
3324
3383
  constructor(config) {
3325
- this.apiKey = config.apiKey;
3384
+ /** Gate the empty-key warning to fire at most once. */
3385
+ this.apiKeyWarned = false;
3386
+ this.apiKeyConfig = config.apiKey;
3326
3387
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3327
3388
  this.timeout = config.timeout ?? 12e4;
3328
3389
  this.envVars = config.envVars ?? {};
3329
- const enabled = config.enabled ?? true;
3330
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3331
- console.warn(
3332
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3333
- );
3334
- this.enabled = false;
3335
- } else {
3336
- this.enabled = enabled;
3337
- }
3390
+ this.explicitlyEnabled = config.enabled ?? true;
3391
+ this.strict = config.strict ?? false;
3338
3392
  this.bamlClient = config.bamlClient ?? null;
3339
3393
  if (config.dbSnapshot) {
3340
3394
  validateDbSnapshotConfig(config.dbSnapshot);
3341
3395
  }
3342
3396
  this.dbSnapshot = config.dbSnapshot;
3343
3397
  this.httpClient = new HttpClient({
3344
- apiKey: this.apiKey,
3398
+ apiKey: () => this.resolveApiKey(),
3345
3399
  serviceUrl: this.serviceUrl,
3346
3400
  timeout: this.timeout
3347
3401
  });
3348
3402
  }
3403
+ /**
3404
+ * Resolve the API key lazily, the first time a span actually needs it.
3405
+ *
3406
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3407
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3408
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3409
+ * the key would be empty at construction even though it is set moments
3410
+ * later. Resolving here (at first `withSpan` call / first request) reads
3411
+ * the key after env loading has run.
3412
+ *
3413
+ * Resolution order: the configured value (string, or function called each
3414
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3415
+ * from the environment. Once a non-empty key is found it is cached, so an
3416
+ * early resolve that found nothing never poisons a later one.
3417
+ */
3418
+ resolveApiKey() {
3419
+ if (this.resolvedApiKey !== void 0) {
3420
+ return this.resolvedApiKey;
3421
+ }
3422
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3423
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3424
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3425
+ if (key) {
3426
+ this.resolvedApiKey = key;
3427
+ return key;
3428
+ }
3429
+ if (this.strict) {
3430
+ throw new BitfabError(
3431
+ "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`."
3432
+ );
3433
+ }
3434
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3435
+ this.apiKeyWarned = true;
3436
+ console.warn(
3437
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3438
+ );
3439
+ }
3440
+ return void 0;
3441
+ }
3442
+ /**
3443
+ * Whether tracing should run, decided lazily at call time (not frozen at
3444
+ * construction). An explicit `enabled: false` short-circuits without ever
3445
+ * touching the key.
3446
+ */
3447
+ isTracingEnabled() {
3448
+ if (!this.explicitlyEnabled) {
3449
+ return false;
3450
+ }
3451
+ return this.resolveApiKey() !== void 0;
3452
+ }
3349
3453
  /**
3350
3454
  * Fetch the function with its current version and BAML prompt from the server.
3351
3455
  *
@@ -3438,7 +3542,9 @@ var Bitfab = class {
3438
3542
  */
3439
3543
  getOpenAiTracingProcessor() {
3440
3544
  return new BitfabOpenAITracingProcessor({
3441
- apiKey: this.apiKey,
3545
+ // Resolved at getter-call time (framework handlers are set up after env
3546
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3547
+ apiKey: this.resolveApiKey(),
3442
3548
  serviceUrl: this.serviceUrl,
3443
3549
  getActiveSpanContext: () => {
3444
3550
  const stack = getSpanStack();
@@ -3497,7 +3603,7 @@ var Bitfab = class {
3497
3603
  */
3498
3604
  getLangGraphCallbackHandler(traceFunctionKey) {
3499
3605
  return new BitfabLangGraphCallbackHandler({
3500
- apiKey: this.apiKey,
3606
+ apiKey: this.resolveApiKey(),
3501
3607
  traceFunctionKey,
3502
3608
  serviceUrl: this.serviceUrl,
3503
3609
  getActiveSpanContext: () => {
@@ -3549,7 +3655,7 @@ var Bitfab = class {
3549
3655
  */
3550
3656
  getClaudeAgentHandler(traceFunctionKey) {
3551
3657
  return new BitfabClaudeAgentHandler({
3552
- apiKey: this.apiKey,
3658
+ apiKey: this.resolveApiKey(),
3553
3659
  traceFunctionKey,
3554
3660
  serviceUrl: this.serviceUrl,
3555
3661
  getActiveSpanContext: () => {
@@ -3721,9 +3827,8 @@ var Bitfab = class {
3721
3827
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3722
3828
  */
3723
3829
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3724
- if (!this.enabled) {
3725
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3726
- return fn2;
3830
+ if (!this.explicitlyEnabled) {
3831
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3727
3832
  }
3728
3833
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3729
3834
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3738,6 +3843,9 @@ var Bitfab = class {
3738
3843
  }
3739
3844
  })();
3740
3845
  const wrappedFn = function(...args) {
3846
+ if (!self.isTracingEnabled()) {
3847
+ return fn.apply(this, args);
3848
+ }
3741
3849
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3742
3850
  return asyncLocalStorageReady.then(
3743
3851
  () => wrappedFn.apply(this, args)
@@ -3998,7 +4106,7 @@ var Bitfab = class {
3998
4106
  return {
3999
4107
  traceId,
4000
4108
  addContext: (context) => {
4001
- if (!this.enabled) {
4109
+ if (!this.isTracingEnabled()) {
4002
4110
  return Promise.resolve();
4003
4111
  }
4004
4112
  if (typeof context !== "object" || context === null) {
@@ -4009,7 +4117,7 @@ var Bitfab = class {
4009
4117
  });
4010
4118
  },
4011
4119
  setMetadata: (metadata) => {
4012
- if (!this.enabled) {
4120
+ if (!this.isTracingEnabled()) {
4013
4121
  return Promise.resolve();
4014
4122
  }
4015
4123
  if (typeof metadata !== "object" || metadata === null) {
@@ -4018,7 +4126,7 @@ var Bitfab = class {
4018
4126
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4019
4127
  },
4020
4128
  setSessionId: (sessionId) => {
4021
- if (!this.enabled) {
4129
+ if (!this.isTracingEnabled()) {
4022
4130
  return Promise.resolve();
4023
4131
  }
4024
4132
  if (typeof sessionId !== "string" || sessionId.length === 0) {