@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.cjs CHANGED
@@ -636,7 +636,7 @@ __export(index_exports, {
636
636
  module.exports = __toCommonJS(index_exports);
637
637
 
638
638
  // src/version.generated.ts
639
- var __version__ = "0.27.1";
639
+ var __version__ = "0.28.0";
640
640
 
641
641
  // src/constants.ts
642
642
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -795,6 +795,13 @@ var HttpClient = class {
795
795
  this.serviceUrl = config.serviceUrl;
796
796
  this.timeout = config.timeout ?? 12e4;
797
797
  }
798
+ /**
799
+ * Resolve the API key at the moment it is needed (request time), invoking
800
+ * the function form if one was supplied. Never read at construction.
801
+ */
802
+ resolveApiKey() {
803
+ return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
804
+ }
798
805
  /**
799
806
  * Make an HTTP request to the Bitfab API. Defaults to POST; pass
800
807
  * `options.method` to use a different verb (e.g. "PATCH").
@@ -825,7 +832,7 @@ var HttpClient = class {
825
832
  method,
826
833
  headers: {
827
834
  "Content-Type": "application/json",
828
- Authorization: `Bearer ${this.apiKey}`
835
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
829
836
  },
830
837
  body,
831
838
  signal: controller.signal
@@ -986,7 +993,7 @@ var HttpClient = class {
986
993
  try {
987
994
  const response = await fetch(url, {
988
995
  method: "GET",
989
- headers: { Authorization: `Bearer ${this.apiKey}` },
996
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
990
997
  signal: controller.signal
991
998
  });
992
999
  if (!response.ok) {
@@ -1022,7 +1029,7 @@ var HttpClient = class {
1022
1029
  try {
1023
1030
  const response = await fetch(url, {
1024
1031
  method: "GET",
1025
- headers: { Authorization: `Bearer ${this.apiKey}` },
1032
+ headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
1026
1033
  signal: controller.signal
1027
1034
  });
1028
1035
  if (!response.ok) {
@@ -1996,6 +2003,7 @@ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1996
2003
  init_randomUuid();
1997
2004
  init_serialize();
1998
2005
  var LANGSMITH_HIDDEN_TAG = "langsmith:hidden";
2006
+ var CHAIN_RUN_TYPES = /* @__PURE__ */ new Set(["chain", "parser", "prompt"]);
1999
2007
  var LANGGRAPH_METADATA_KEYS = [
2000
2008
  "langgraph_step",
2001
2009
  "langgraph_node",
@@ -2006,6 +2014,18 @@ var LANGGRAPH_METADATA_KEYS = [
2006
2014
  function nowIso2() {
2007
2015
  return (/* @__PURE__ */ new Date()).toISOString();
2008
2016
  }
2017
+ function normalizeChainStartArgs(parentRunIdOrRunType, runTypeOrRunName, runNameOrParentRunId) {
2018
+ if (parentRunIdOrRunType && CHAIN_RUN_TYPES.has(parentRunIdOrRunType)) {
2019
+ return {
2020
+ parentRunId: runNameOrParentRunId,
2021
+ runName: runTypeOrRunName
2022
+ };
2023
+ }
2024
+ return {
2025
+ parentRunId: parentRunIdOrRunType,
2026
+ runName: runNameOrParentRunId
2027
+ };
2028
+ }
2009
2029
  function convertMessage(message) {
2010
2030
  if (typeof message !== "object" || message === null) {
2011
2031
  return { role: "unknown", content: String(message) };
@@ -2217,6 +2237,7 @@ var BitfabLangGraphCallbackHandler = class {
2217
2237
  const willHide = tags?.includes(LANGSMITH_HIDDEN_TAG) === true;
2218
2238
  let invocation;
2219
2239
  let effectiveParentId;
2240
+ let isRootInvocation = false;
2220
2241
  if (parentSpan) {
2221
2242
  const existing = this.invocations.get(parentSpan.rootRunId);
2222
2243
  if (existing) {
@@ -2247,6 +2268,7 @@ var BitfabLangGraphCallbackHandler = class {
2247
2268
  };
2248
2269
  this.invocations.set(runId, invocation);
2249
2270
  effectiveParentId = activeContext?.spanId ?? null;
2271
+ isRootInvocation = true;
2250
2272
  }
2251
2273
  const lgMetadata = extractLangGraphMetadata(metadata);
2252
2274
  const contexts = Object.keys(lgMetadata).length > 0 ? [lgMetadata] : [];
@@ -2269,6 +2291,9 @@ var BitfabLangGraphCallbackHandler = class {
2269
2291
  spanInfo.hidden = true;
2270
2292
  }
2271
2293
  this.runToSpan.set(runId, spanInfo);
2294
+ if (isRootInvocation) {
2295
+ this.sendTraceStart(spanInfo);
2296
+ }
2272
2297
  return spanInfo;
2273
2298
  }
2274
2299
  completeSpan(runId, output, error, extraContexts) {
@@ -2359,11 +2384,35 @@ var BitfabLangGraphCallbackHandler = class {
2359
2384
  } catch {
2360
2385
  }
2361
2386
  }
2387
+ sendTraceStart(rootSpan) {
2388
+ const traceData = {
2389
+ type: "sdk-function",
2390
+ source: "typescript-sdk-langgraph",
2391
+ traceFunctionKey: this.traceFunctionKey,
2392
+ externalTrace: {
2393
+ id: rootSpan.traceId,
2394
+ started_at: rootSpan.startedAt,
2395
+ workflow_name: this.traceFunctionKey
2396
+ },
2397
+ completed: false
2398
+ };
2399
+ const finalized = finalizeTracePayload(traceData);
2400
+ try {
2401
+ this.httpClient.sendExternalTrace(finalized);
2402
+ } catch {
2403
+ }
2404
+ }
2362
2405
  // ── chain callbacks (graph nodes) ─────────────────────────────
2363
- async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
2406
+ async handleChainStart(chain, inputs, runId, parentRunIdOrRunType, tags, metadata, runTypeOrRunName, runNameOrParentRunId) {
2364
2407
  try {
2365
- const idArr = chain.id;
2366
- const name = chain.name ?? idArr?.[idArr.length - 1] ?? "chain";
2408
+ const { parentRunId, runName } = normalizeChainStartArgs(
2409
+ parentRunIdOrRunType,
2410
+ runTypeOrRunName,
2411
+ runNameOrParentRunId
2412
+ );
2413
+ const serialized = chain ?? {};
2414
+ const idArr = serialized.id;
2415
+ const name = runName ?? serialized.name ?? idArr?.[idArr.length - 1] ?? "chain";
2367
2416
  this.startSpan(
2368
2417
  runId,
2369
2418
  parentRunId,
@@ -2398,11 +2447,12 @@ var BitfabLangGraphCallbackHandler = class {
2398
2447
  }
2399
2448
  }
2400
2449
  // ── LLM callbacks ─────────────────────────────────────────────
2401
- async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata) {
2450
+ async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, tags, metadata, runName) {
2402
2451
  try {
2403
- const model = extractModelName(llm, metadata);
2404
- const idArr = llm.id;
2405
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2452
+ const serialized = llm ?? {};
2453
+ const model = extractModelName(serialized, metadata);
2454
+ const idArr = serialized.id;
2455
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2406
2456
  const converted = messages.map((batch) => batch.map(convertMessage));
2407
2457
  const spanInfo = this.startSpan(
2408
2458
  runId,
@@ -2417,11 +2467,12 @@ var BitfabLangGraphCallbackHandler = class {
2417
2467
  } catch {
2418
2468
  }
2419
2469
  }
2420
- async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata) {
2470
+ async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, tags, metadata, runName) {
2421
2471
  try {
2422
- const model = extractModelName(llm, metadata);
2423
- const idArr = llm.id;
2424
- const name = model ?? idArr?.[idArr.length - 1] ?? "llm";
2472
+ const serialized = llm ?? {};
2473
+ const model = extractModelName(serialized, metadata);
2474
+ const idArr = serialized.id;
2475
+ const name = runName ?? model ?? idArr?.[idArr.length - 1] ?? "llm";
2425
2476
  const spanInfo = this.startSpan(
2426
2477
  runId,
2427
2478
  parentRunId,
@@ -2474,9 +2525,10 @@ var BitfabLangGraphCallbackHandler = class {
2474
2525
  async handleLLMNewToken() {
2475
2526
  }
2476
2527
  // ── tool callbacks ────────────────────────────────────────────
2477
- async handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
2528
+ async handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
2478
2529
  try {
2479
- const name = tool.name ?? "tool";
2530
+ const serialized = tool ?? {};
2531
+ const name = runName ?? serialized.name ?? "tool";
2480
2532
  this.startSpan(
2481
2533
  runId,
2482
2534
  parentRunId,
@@ -2506,9 +2558,10 @@ var BitfabLangGraphCallbackHandler = class {
2506
2558
  }
2507
2559
  }
2508
2560
  // ── retriever callbacks ───────────────────────────────────────
2509
- async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
2561
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, runName) {
2510
2562
  try {
2511
- const name = retriever.name ?? "retriever";
2563
+ const serialized = retriever ?? {};
2564
+ const name = runName ?? serialized.name ?? "retriever";
2512
2565
  this.startSpan(
2513
2566
  runId,
2514
2567
  parentRunId,
@@ -3306,6 +3359,12 @@ function getCurrentTrace() {
3306
3359
  }
3307
3360
  };
3308
3361
  }
3362
+ function readEnv(name) {
3363
+ if (typeof process !== "undefined" && process.env) {
3364
+ return process.env[name];
3365
+ }
3366
+ return void 0;
3367
+ }
3309
3368
  var Bitfab = class {
3310
3369
  /**
3311
3370
  * Initialize the Bitfab client.
@@ -3313,30 +3372,75 @@ var Bitfab = class {
3313
3372
  * @param config - Configuration options for the client
3314
3373
  */
3315
3374
  constructor(config) {
3316
- this.apiKey = config.apiKey;
3375
+ /** Gate the empty-key warning to fire at most once. */
3376
+ this.apiKeyWarned = false;
3377
+ this.apiKeyConfig = config.apiKey;
3317
3378
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
3318
3379
  this.timeout = config.timeout ?? 12e4;
3319
3380
  this.envVars = config.envVars ?? {};
3320
- const enabled = config.enabled ?? true;
3321
- if (enabled && (!config.apiKey || config.apiKey.trim() === "")) {
3322
- console.warn(
3323
- "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3324
- );
3325
- this.enabled = false;
3326
- } else {
3327
- this.enabled = enabled;
3328
- }
3381
+ this.explicitlyEnabled = config.enabled ?? true;
3382
+ this.strict = config.strict ?? false;
3329
3383
  this.bamlClient = config.bamlClient ?? null;
3330
3384
  if (config.dbSnapshot) {
3331
3385
  validateDbSnapshotConfig(config.dbSnapshot);
3332
3386
  }
3333
3387
  this.dbSnapshot = config.dbSnapshot;
3334
3388
  this.httpClient = new HttpClient({
3335
- apiKey: this.apiKey,
3389
+ apiKey: () => this.resolveApiKey(),
3336
3390
  serviceUrl: this.serviceUrl,
3337
3391
  timeout: this.timeout
3338
3392
  });
3339
3393
  }
3394
+ /**
3395
+ * Resolve the API key lazily, the first time a span actually needs it.
3396
+ *
3397
+ * The key is intentionally NOT read at construction. In ESM, a shim that
3398
+ * does `new Bitfab({ apiKey: process.env.BITFAB_API_KEY })` is hoisted and
3399
+ * evaluated before the importing script's body runs `dotenv.config()`, so
3400
+ * the key would be empty at construction even though it is set moments
3401
+ * later. Resolving here (at first `withSpan` call / first request) reads
3402
+ * the key after env loading has run.
3403
+ *
3404
+ * Resolution order: the configured value (string, or function called each
3405
+ * time it is still unresolved), then a fallback read of `BITFAB_API_KEY`
3406
+ * from the environment. Once a non-empty key is found it is cached, so an
3407
+ * early resolve that found nothing never poisons a later one.
3408
+ */
3409
+ resolveApiKey() {
3410
+ if (this.resolvedApiKey !== void 0) {
3411
+ return this.resolvedApiKey;
3412
+ }
3413
+ const fromConfig = typeof this.apiKeyConfig === "function" ? this.apiKeyConfig() : this.apiKeyConfig;
3414
+ const candidate = fromConfig && fromConfig.trim() !== "" ? fromConfig : readEnv("BITFAB_API_KEY");
3415
+ const key = candidate && candidate.trim() !== "" ? candidate : void 0;
3416
+ if (key) {
3417
+ this.resolvedApiKey = key;
3418
+ return key;
3419
+ }
3420
+ if (this.strict) {
3421
+ throw new BitfabError(
3422
+ "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`."
3423
+ );
3424
+ }
3425
+ if (this.explicitlyEnabled && !this.apiKeyWarned) {
3426
+ this.apiKeyWarned = true;
3427
+ console.warn(
3428
+ "Bitfab: apiKey is empty \u2014 tracing is disabled. Provide a valid API key to enable tracing."
3429
+ );
3430
+ }
3431
+ return void 0;
3432
+ }
3433
+ /**
3434
+ * Whether tracing should run, decided lazily at call time (not frozen at
3435
+ * construction). An explicit `enabled: false` short-circuits without ever
3436
+ * touching the key.
3437
+ */
3438
+ isTracingEnabled() {
3439
+ if (!this.explicitlyEnabled) {
3440
+ return false;
3441
+ }
3442
+ return this.resolveApiKey() !== void 0;
3443
+ }
3340
3444
  /**
3341
3445
  * Fetch the function with its current version and BAML prompt from the server.
3342
3446
  *
@@ -3429,7 +3533,9 @@ var Bitfab = class {
3429
3533
  */
3430
3534
  getOpenAiTracingProcessor() {
3431
3535
  return new BitfabOpenAITracingProcessor({
3432
- apiKey: this.apiKey,
3536
+ // Resolved at getter-call time (framework handlers are set up after env
3537
+ // loads); the withSpan path stays lazy via the constructor HttpClient thunk.
3538
+ apiKey: this.resolveApiKey(),
3433
3539
  serviceUrl: this.serviceUrl,
3434
3540
  getActiveSpanContext: () => {
3435
3541
  const stack = getSpanStack();
@@ -3488,7 +3594,7 @@ var Bitfab = class {
3488
3594
  */
3489
3595
  getLangGraphCallbackHandler(traceFunctionKey) {
3490
3596
  return new BitfabLangGraphCallbackHandler({
3491
- apiKey: this.apiKey,
3597
+ apiKey: this.resolveApiKey(),
3492
3598
  traceFunctionKey,
3493
3599
  serviceUrl: this.serviceUrl,
3494
3600
  getActiveSpanContext: () => {
@@ -3540,7 +3646,7 @@ var Bitfab = class {
3540
3646
  */
3541
3647
  getClaudeAgentHandler(traceFunctionKey) {
3542
3648
  return new BitfabClaudeAgentHandler({
3543
- apiKey: this.apiKey,
3649
+ apiKey: this.resolveApiKey(),
3544
3650
  traceFunctionKey,
3545
3651
  serviceUrl: this.serviceUrl,
3546
3652
  getActiveSpanContext: () => {
@@ -3712,9 +3818,8 @@ var Bitfab = class {
3712
3818
  * @returns A wrapped function with the same signature that creates spans for inputs and outputs
3713
3819
  */
3714
3820
  withSpan(traceFunctionKey, optionsOrFn, maybeFn) {
3715
- if (!this.enabled) {
3716
- const fn2 = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3717
- return fn2;
3821
+ if (!this.explicitlyEnabled) {
3822
+ return typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
3718
3823
  }
3719
3824
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
3720
3825
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
@@ -3729,6 +3834,9 @@ var Bitfab = class {
3729
3834
  }
3730
3835
  })();
3731
3836
  const wrappedFn = function(...args) {
3837
+ if (!self.isTracingEnabled()) {
3838
+ return fn.apply(this, args);
3839
+ }
3732
3840
  if (!asyncLocalStorage && !isAsyncStorageInitDone()) {
3733
3841
  return asyncLocalStorageReady.then(
3734
3842
  () => wrappedFn.apply(this, args)
@@ -3989,7 +4097,7 @@ var Bitfab = class {
3989
4097
  return {
3990
4098
  traceId,
3991
4099
  addContext: (context) => {
3992
- if (!this.enabled) {
4100
+ if (!this.isTracingEnabled()) {
3993
4101
  return Promise.resolve();
3994
4102
  }
3995
4103
  if (typeof context !== "object" || context === null) {
@@ -4000,7 +4108,7 @@ var Bitfab = class {
4000
4108
  });
4001
4109
  },
4002
4110
  setMetadata: (metadata) => {
4003
- if (!this.enabled) {
4111
+ if (!this.isTracingEnabled()) {
4004
4112
  return Promise.resolve();
4005
4113
  }
4006
4114
  if (typeof metadata !== "object" || metadata === null) {
@@ -4009,7 +4117,7 @@ var Bitfab = class {
4009
4117
  return this.httpClient.patchTrace(traceId, { mergeMetadata: metadata });
4010
4118
  },
4011
4119
  setSessionId: (sessionId) => {
4012
- if (!this.enabled) {
4120
+ if (!this.isTracingEnabled()) {
4013
4121
  return Promise.resolve();
4014
4122
  }
4015
4123
  if (typeof sessionId !== "string" || sessionId.length === 0) {