raindrop-ai 0.2.1 → 0.2.2-otelv2

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
@@ -5771,7 +5771,13 @@ __export(index_exports, {
5771
5771
  });
5772
5772
  module.exports = __toCommonJS(index_exports);
5773
5773
 
5774
- // ../core/dist/chunk-Y5LUB7OE.js
5774
+ // src/lib/inject-als.ts
5775
+ var import_node_async_hooks = require("async_hooks");
5776
+ if (!globalThis.RAINDROP_ASYNC_LOCAL_STORAGE) {
5777
+ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_node_async_hooks.AsyncLocalStorage;
5778
+ }
5779
+
5780
+ // ../core/dist/chunk-ITAZONWL.js
5775
5781
  function normalizeFeatureFlags(input) {
5776
5782
  if (Array.isArray(input)) {
5777
5783
  return Object.fromEntries(input.map((name) => [name, "true"]));
@@ -6166,6 +6172,171 @@ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
6166
6172
  "ai.response.providerMetadata"
6167
6173
  ];
6168
6174
  var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
6175
+ function getAsyncLocalStorageCtor() {
6176
+ return globalThis.RAINDROP_ASYNC_LOCAL_STORAGE;
6177
+ }
6178
+ function getWeakRefCtor() {
6179
+ const ctor = globalThis.WeakRef;
6180
+ return typeof ctor === "function" ? ctor : void 0;
6181
+ }
6182
+ var MAX_BINDING_STACK = 128;
6183
+ var EMPTY_STACK = [];
6184
+ var RoutingContextStorage = class {
6185
+ constructor() {
6186
+ this._als = null;
6187
+ this._enterWithUsable = false;
6188
+ this._syncStack = EMPTY_STACK;
6189
+ this._adoptionChecked = false;
6190
+ this._warnedNoAmbientBind = false;
6191
+ }
6192
+ maybeAdoptAsyncLocalStorage() {
6193
+ if (this._adoptionChecked) return;
6194
+ this._adoptionChecked = true;
6195
+ const Ctor = getAsyncLocalStorageCtor();
6196
+ if (!Ctor) return;
6197
+ const als = new Ctor();
6198
+ try {
6199
+ als.run(EMPTY_STACK, () => {
6200
+ });
6201
+ } catch (e) {
6202
+ return;
6203
+ }
6204
+ this._als = als;
6205
+ if (typeof als.enterWith === "function") {
6206
+ try {
6207
+ als.enterWith(EMPTY_STACK);
6208
+ this._enterWithUsable = true;
6209
+ } catch (e) {
6210
+ this._enterWithUsable = false;
6211
+ }
6212
+ }
6213
+ }
6214
+ getStack() {
6215
+ var _a;
6216
+ this.maybeAdoptAsyncLocalStorage();
6217
+ if (this._als) return (_a = this._als.getStore()) != null ? _a : this._syncStack;
6218
+ return this._syncStack;
6219
+ }
6220
+ /**
6221
+ * Persist an ambient stack mutation. Returns false when an ALS is adopted but
6222
+ * `enterWith` is unusable (workerd): callers must NOT fall back to the
6223
+ * process-shared sync stack, since concurrent isolates would cross-stamp.
6224
+ */
6225
+ setAmbientStack(stack) {
6226
+ if (this._als) {
6227
+ if (!this._enterWithUsable) return false;
6228
+ this._als.enterWith(stack);
6229
+ return true;
6230
+ }
6231
+ this._syncStack = stack;
6232
+ return true;
6233
+ }
6234
+ push(binding) {
6235
+ this.maybeAdoptAsyncLocalStorage();
6236
+ if (this._als && !this._enterWithUsable) {
6237
+ this.warnNoAmbientBindOnce();
6238
+ return;
6239
+ }
6240
+ let stack = this.getStack();
6241
+ if (stack.length >= MAX_BINDING_STACK) {
6242
+ stack = stack.filter((b) => b.isLive());
6243
+ if (stack.length >= MAX_BINDING_STACK) {
6244
+ stack = stack.slice(stack.length - (MAX_BINDING_STACK - 1));
6245
+ }
6246
+ }
6247
+ this.setAmbientStack([...stack, binding]);
6248
+ }
6249
+ remove(binding) {
6250
+ binding.markDead();
6251
+ const stack = this.getStack();
6252
+ const pruned = stack.filter((b) => b !== binding && b.isLive());
6253
+ if (pruned.length !== stack.length) {
6254
+ this.setAmbientStack(pruned);
6255
+ }
6256
+ }
6257
+ runWith(binding, callback) {
6258
+ this.maybeAdoptAsyncLocalStorage();
6259
+ if (this._als) {
6260
+ return this._als.run([...this.getStack(), binding], callback);
6261
+ }
6262
+ const previous = this._syncStack;
6263
+ this._syncStack = [...previous, binding];
6264
+ try {
6265
+ return callback();
6266
+ } finally {
6267
+ this._syncStack = previous;
6268
+ }
6269
+ }
6270
+ current() {
6271
+ const stack = this.getStack();
6272
+ if (stack.length === 0) return void 0;
6273
+ const top = stack[stack.length - 1];
6274
+ if (top.isLive()) return top;
6275
+ const pruned = stack.filter((b) => b.isLive());
6276
+ this.setAmbientStack(pruned);
6277
+ return pruned.length > 0 ? pruned[pruned.length - 1] : void 0;
6278
+ }
6279
+ warnNoAmbientBindOnce() {
6280
+ if (this._warnedNoAmbientBind) return;
6281
+ this._warnedNoAmbientBind = true;
6282
+ console.warn(
6283
+ "[raindrop] This runtime's AsyncLocalStorage does not implement enterWith (e.g. Cloudflare Workers), so begin()/finish() cannot scope auto-instrumented spans to a project without cross-contaminating concurrent requests. Manual events and explicit span routing are unaffected; use asCurrent()/withSpan (run-based) to route auto-instrumented spans on this runtime."
6284
+ );
6285
+ }
6286
+ };
6287
+ var ROUTING_STORAGE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.routingContextStorage");
6288
+ function routingStorage() {
6289
+ const holder = globalThis;
6290
+ let storage = holder[ROUTING_STORAGE_KEY];
6291
+ if (!storage) {
6292
+ storage = new RoutingContextStorage();
6293
+ holder[ROUTING_STORAGE_KEY] = storage;
6294
+ }
6295
+ return storage;
6296
+ }
6297
+ function makeBoundContext(binding, owner) {
6298
+ const WeakRefCtor = owner !== void 0 ? getWeakRefCtor() : void 0;
6299
+ const ref = WeakRefCtor && owner !== void 0 ? new WeakRefCtor(owner) : void 0;
6300
+ let dead = false;
6301
+ return {
6302
+ projectId: binding.projectId,
6303
+ authHint: binding.authHint,
6304
+ isLive() {
6305
+ if (dead) return false;
6306
+ return ref === void 0 || ref.deref() !== void 0;
6307
+ },
6308
+ markDead() {
6309
+ dead = true;
6310
+ }
6311
+ };
6312
+ }
6313
+ function bindRoutingContext(binding, owner) {
6314
+ const bound = makeBoundContext(binding, owner);
6315
+ try {
6316
+ routingStorage().push(bound);
6317
+ } catch (e) {
6318
+ }
6319
+ return bound;
6320
+ }
6321
+ function unbindRoutingContext(bound) {
6322
+ if (!bound || !isBoundContext(bound)) return;
6323
+ try {
6324
+ routingStorage().remove(bound);
6325
+ } catch (e) {
6326
+ }
6327
+ }
6328
+ function withRoutingContext(binding, callback) {
6329
+ const bound = makeBoundContext(binding);
6330
+ return routingStorage().runWith(bound, callback);
6331
+ }
6332
+ function currentRoutingBinding() {
6333
+ const bound = routingStorage().current();
6334
+ if (!bound) return void 0;
6335
+ return { projectId: bound.projectId, authHint: bound.authHint };
6336
+ }
6337
+ function isBoundContext(value) {
6338
+ return typeof value.isLive === "function";
6339
+ }
6169
6340
 
6170
6341
  // ../core/dist/index.node.js
6171
6342
  var import_async_hooks = require("async_hooks");
@@ -10305,7 +10476,7 @@ var SignalEventSchema = external_exports.object({
10305
10476
  // package.json
10306
10477
  var package_default = {
10307
10478
  name: "raindrop-ai",
10308
- version: "0.2.1",
10479
+ version: "0.2.2",
10309
10480
  main: "dist/index.js",
10310
10481
  module: "dist/index.mjs",
10311
10482
  types: "dist/index.d.ts",
@@ -10371,6 +10542,7 @@ var package_default = {
10371
10542
  dependencies: {
10372
10543
  "@opentelemetry/api": "^1.9.0",
10373
10544
  "@opentelemetry/exporter-trace-otlp-http": "^0.57.2",
10545
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.57.2",
10374
10546
  "@traceloop/node-server-sdk": "0.19.0-otel-v1",
10375
10547
  weakref: "^0.2.1",
10376
10548
  zod: "^3.23.8"
@@ -10402,6 +10574,7 @@ var package_default = {
10402
10574
  "@traceloop/instrumentation-together",
10403
10575
  "@opentelemetry/instrumentation",
10404
10576
  "@opentelemetry/exporter-trace-otlp-http",
10577
+ "@opentelemetry/exporter-trace-otlp-proto",
10405
10578
  "@opentelemetry/sdk-trace-base"
10406
10579
  ],
10407
10580
  noExternal: [
@@ -10517,7 +10690,7 @@ var import_instrumentation_pinecone = require("@traceloop/instrumentation-pineco
10517
10690
  var import_instrumentation_qdrant = require("@traceloop/instrumentation-qdrant");
10518
10691
  var import_instrumentation_together = require("@traceloop/instrumentation-together");
10519
10692
  var import_instrumentation_vertexai = require("@traceloop/instrumentation-vertexai");
10520
- var traceloop3 = __toESM(require("@traceloop/node-server-sdk"));
10693
+ var traceloop4 = __toESM(require("@traceloop/node-server-sdk"));
10521
10694
  var import_weakref = require("weakref");
10522
10695
 
10523
10696
  // src/tracing/direct/http.ts
@@ -10741,6 +10914,190 @@ function getActiveTraceContext() {
10741
10914
  };
10742
10915
  }
10743
10916
 
10917
+ // src/tracing/multi-project.ts
10918
+ var import_node_crypto = require("crypto");
10919
+ var import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
10920
+ var PROJECT_ID_SPAN_ATTRIBUTE = "raindrop.project_id";
10921
+ var AUTH_HINT_SPAN_ATTRIBUTE = "raindrop.auth_hint";
10922
+ var EXPORT_SUCCESS = 0;
10923
+ var EXPORT_FAILED = 1;
10924
+ function authHintForKey(writeKey) {
10925
+ if (!writeKey) return void 0;
10926
+ return (0, import_node_crypto.createHash)("sha256").update(writeKey, "utf8").digest("hex").slice(0, 8);
10927
+ }
10928
+ var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.multiProjectState");
10929
+ function getState() {
10930
+ const holder = globalThis;
10931
+ let state = holder[STATE_KEY];
10932
+ if (!state) {
10933
+ state = {
10934
+ foreignClientSeen: false,
10935
+ seenClientHints: /* @__PURE__ */ new Set(),
10936
+ ownerAuthHint: void 0,
10937
+ ownerProjectId: void 0,
10938
+ pipelineClaimed: false,
10939
+ traceloopConfiguredWithoutClaim: false,
10940
+ multiKeyWarned: false,
10941
+ pendingMultiKeyWarning: false
10942
+ };
10943
+ holder[STATE_KEY] = state;
10944
+ }
10945
+ return state;
10946
+ }
10947
+ function emitMultiKeyWarningOnce() {
10948
+ const state = getState();
10949
+ if (state.multiKeyWarned) return;
10950
+ state.multiKeyWarned = true;
10951
+ state.pendingMultiKeyWarning = false;
10952
+ console.warn(
10953
+ "[raindrop] Multiple API keys detected in one process. The OTel export guard now requires positive attribution: spans not bound to any client (no begin()/asCurrent()) are dropped, and spans bound to a non-owner key are dropped. Manual events are unaffected."
10954
+ );
10955
+ }
10956
+ function registerClientKey(authHint, tracingEnabled = false) {
10957
+ if (!authHint) return;
10958
+ const state = getState();
10959
+ state.seenClientHints.add(authHint);
10960
+ if (state.seenClientHints.size > 1) {
10961
+ state.foreignClientSeen = true;
10962
+ if (tracingEnabled || state.pipelineClaimed) {
10963
+ emitMultiKeyWarningOnce();
10964
+ } else {
10965
+ state.pendingMultiKeyWarning = true;
10966
+ }
10967
+ }
10968
+ }
10969
+ function markForeignClient() {
10970
+ getState().foreignClientSeen = true;
10971
+ }
10972
+ function foreignClientSeen() {
10973
+ return getState().foreignClientSeen;
10974
+ }
10975
+ function stampSpan(span, projectId, authHint) {
10976
+ try {
10977
+ if (projectId) {
10978
+ span.setAttribute(PROJECT_ID_SPAN_ATTRIBUTE, projectId);
10979
+ }
10980
+ if (authHint && foreignClientSeen()) {
10981
+ span.setAttribute(AUTH_HINT_SPAN_ATTRIBUTE, authHint);
10982
+ }
10983
+ } catch (e) {
10984
+ }
10985
+ }
10986
+ var RaindropContextSpanProcessor = class {
10987
+ onStart(span) {
10988
+ try {
10989
+ const bound = currentRoutingBinding();
10990
+ if (!bound) return;
10991
+ stampSpan(span, bound.projectId, bound.authHint);
10992
+ } catch (e) {
10993
+ }
10994
+ }
10995
+ onEnd() {
10996
+ }
10997
+ async forceFlush() {
10998
+ }
10999
+ async shutdown() {
11000
+ }
11001
+ };
11002
+ var GuardedSpanExporter = class {
11003
+ constructor(inner, ownerAuthHint) {
11004
+ this.droppedTotal = 0;
11005
+ this.inner = inner;
11006
+ this.ownerAuthHint = ownerAuthHint;
11007
+ }
11008
+ partition(spans) {
11009
+ var _a;
11010
+ const allowed = [];
11011
+ let dropped = 0;
11012
+ const requirePositive = foreignClientSeen();
11013
+ for (const span of spans) {
11014
+ const attrs = (_a = span.attributes) != null ? _a : {};
11015
+ const hint = attrs[AUTH_HINT_SPAN_ATTRIBUTE];
11016
+ const keep = requirePositive ? hint === this.ownerAuthHint : hint === void 0 || hint === this.ownerAuthHint;
11017
+ if (keep) {
11018
+ allowed.push(span);
11019
+ } else {
11020
+ dropped += 1;
11021
+ }
11022
+ }
11023
+ return { allowed, dropped };
11024
+ }
11025
+ warnDropped(dropped) {
11026
+ this.droppedTotal += dropped;
11027
+ const total = this.droppedTotal;
11028
+ rateLimitedLog(
11029
+ "js-sdk.multi-project.dropped",
11030
+ () => console.warn(
11031
+ `[raindrop] Dropped ${dropped} span(s) produced under a different API key than the tracing pipeline's (total dropped: ${total}). One process has ONE OTel pipeline authenticating with the first tracing-enabled client's key; tracing for a different key/org in the same process is unsupported \u2014 run it in its own process. Manual events (track_ai/begin/finish/signals/identify) are unaffected.`
11032
+ )
11033
+ );
11034
+ }
11035
+ export(spans, resultCallback) {
11036
+ let allowed;
11037
+ let dropped;
11038
+ try {
11039
+ ({ allowed, dropped } = this.partition(spans));
11040
+ } catch (e) {
11041
+ if (foreignClientSeen()) {
11042
+ this.warnDropped(spans.length);
11043
+ resultCallback({ code: EXPORT_FAILED });
11044
+ return;
11045
+ }
11046
+ this.inner.export(spans, resultCallback);
11047
+ return;
11048
+ }
11049
+ if (dropped) {
11050
+ this.warnDropped(dropped);
11051
+ }
11052
+ if (allowed.length === 0) {
11053
+ resultCallback({ code: EXPORT_SUCCESS });
11054
+ return;
11055
+ }
11056
+ this.inner.export(allowed, resultCallback);
11057
+ }
11058
+ shutdown() {
11059
+ return this.inner.shutdown();
11060
+ }
11061
+ forceFlush() {
11062
+ return this.inner.forceFlush ? this.inner.forceFlush() : Promise.resolve();
11063
+ }
11064
+ };
11065
+ function buildGuardedExporter(baseUrl, headers, ownerAuthHint) {
11066
+ const inner = new import_exporter_trace_otlp_proto.OTLPTraceExporter({
11067
+ url: `${baseUrl}/v1/traces`,
11068
+ headers
11069
+ });
11070
+ return new GuardedSpanExporter(inner, ownerAuthHint);
11071
+ }
11072
+ function pipelineOwnerHint() {
11073
+ return getState().ownerAuthHint;
11074
+ }
11075
+ function claimPipeline(authHint, projectId) {
11076
+ const state = getState();
11077
+ if (state.pipelineClaimed) {
11078
+ return false;
11079
+ }
11080
+ state.pipelineClaimed = true;
11081
+ state.ownerAuthHint = authHint;
11082
+ state.ownerProjectId = projectId;
11083
+ if (state.foreignClientSeen && state.pendingMultiKeyWarning) {
11084
+ emitMultiKeyWarningOnce();
11085
+ }
11086
+ return true;
11087
+ }
11088
+ function releasePipelineClaim() {
11089
+ const state = getState();
11090
+ state.pipelineClaimed = false;
11091
+ state.ownerAuthHint = void 0;
11092
+ state.ownerProjectId = void 0;
11093
+ }
11094
+ function markTraceloopConfiguredWithoutClaim() {
11095
+ getState().traceloopConfiguredWithoutClaim = true;
11096
+ }
11097
+ function traceloopConfiguredWithoutClaim() {
11098
+ return getState().traceloopConfiguredWithoutClaim;
11099
+ }
11100
+
10744
11101
  // src/tracing/direct/shipper.ts
10745
11102
  function serializeAssociationValue(value) {
10746
11103
  return stringifyBounded(value);
@@ -10800,7 +11157,11 @@ var DirectTraceShipper = class {
10800
11157
  attrString2("traceloop.entity.input", params.input),
10801
11158
  attrString2("traceloop.entity.output", params.output),
10802
11159
  // Duration tracking
10803
- attrInt2("traceloop.entity.duration_ms", params.durationMs)
11160
+ attrInt2("traceloop.entity.duration_ms", params.durationMs),
11161
+ // Per-span project routing: the ingest boundary routes on this attribute
11162
+ // (org-validated) and falls back to the request's project header. Direct
11163
+ // spans ship on this instance's own key, so no cross-key auth hint.
11164
+ attrString2(PROJECT_ID_SPAN_ATTRIBUTE, this.projectId)
10804
11165
  ];
10805
11166
  for (const [key, value] of Object.entries(params.properties)) {
10806
11167
  if (value === void 0) {
@@ -11098,12 +11459,27 @@ var DirectToolSpan = class {
11098
11459
  }
11099
11460
  };
11100
11461
  var LiveInteraction = class {
11101
- constructor(context5, traceId, analytics, directShipper) {
11462
+ constructor(context5, traceId, analytics, directShipper, routing) {
11102
11463
  this.context = context5;
11103
11464
  this.analytics = analytics;
11104
11465
  this.tracer = import_api2.trace.getTracer("traceloop.tracer");
11105
11466
  this.traceId = traceId;
11106
11467
  this.directShipper = directShipper;
11468
+ this.routing = routing;
11469
+ }
11470
+ /**
11471
+ * Record the routing binding this interaction pushed onto the context stack
11472
+ * (via {@link bindRoutingContext} in `begin()`) so {@link finish} removes
11473
+ * exactly its own entry — non-LIFO safe, and leaving any still-open sibling's
11474
+ * binding intact.
11475
+ */
11476
+ setBoundContext(bound) {
11477
+ this.boundContext = bound;
11478
+ }
11479
+ /** Stamp a manually-created OTel span with this interaction's routing. */
11480
+ stampRouting(span) {
11481
+ if (!this.routing) return;
11482
+ stampSpan(span, this.routing.projectId, this.routing.authHint);
11107
11483
  }
11108
11484
  ensureTraceId(preferred) {
11109
11485
  if (preferred) {
@@ -11172,8 +11548,9 @@ var LiveInteraction = class {
11172
11548
  import_api2.context.active(),
11173
11549
  import_api2.trace.wrapSpanContext(spanContext)
11174
11550
  );
11551
+ const invoke = () => this.routing ? withRoutingContext(this.routing, () => fn.apply(thisArg, args)) : fn.apply(thisArg, args);
11175
11552
  try {
11176
- const result = await import_api2.context.with(wrappedContext, () => fn.apply(thisArg, args));
11553
+ const result = await import_api2.context.with(wrappedContext, invoke);
11177
11554
  const endTimeMs = Date.now();
11178
11555
  this.directShipper.sendSpan({
11179
11556
  name: taskName,
@@ -11208,11 +11585,17 @@ var LiveInteraction = class {
11208
11585
  }
11209
11586
  }
11210
11587
  const wrappedFn = (...fnArgs) => {
11211
- var _a2;
11212
- const activeTraceId = (_a2 = import_api2.trace.getSpan(import_api2.context.active())) == null ? void 0 : _a2.spanContext().traceId;
11588
+ const activeSpan = import_api2.trace.getSpan(import_api2.context.active());
11589
+ const activeTraceId = activeSpan == null ? void 0 : activeSpan.spanContext().traceId;
11213
11590
  if (activeTraceId) {
11214
11591
  this.traceId = activeTraceId;
11215
11592
  }
11593
+ if (activeSpan) {
11594
+ this.stampRouting(activeSpan);
11595
+ }
11596
+ if (this.routing) {
11597
+ return withRoutingContext(this.routing, () => fn.apply(thisArg, fnArgs));
11598
+ }
11216
11599
  return fn.apply(thisArg, fnArgs);
11217
11600
  };
11218
11601
  return traceloop.withTask(
@@ -11224,7 +11607,6 @@ var LiveInteraction = class {
11224
11607
  suppressTracing: params.suppressTracing
11225
11608
  },
11226
11609
  wrappedFn,
11227
- void 0,
11228
11610
  ...args
11229
11611
  );
11230
11612
  }
@@ -11261,10 +11643,11 @@ var LiveInteraction = class {
11261
11643
  import_api2.context.active(),
11262
11644
  import_api2.trace.wrapSpanContext(spanContext)
11263
11645
  );
11646
+ const invoke = () => this.routing ? withRoutingContext(this.routing, () => fn.apply(thisArg, args)) : fn.apply(thisArg, args);
11264
11647
  let result;
11265
11648
  let error;
11266
11649
  try {
11267
- result = await import_api2.context.with(wrappedContext, () => fn.apply(thisArg, args));
11650
+ result = await import_api2.context.with(wrappedContext, invoke);
11268
11651
  } catch (err) {
11269
11652
  error = err instanceof Error ? err.message : String(err);
11270
11653
  const endTimeMs2 = Date.now();
@@ -11332,6 +11715,9 @@ var LiveInteraction = class {
11332
11715
  const activeSpan = import_api2.trace.getSpan(import_api2.context.active());
11333
11716
  const ctx = activeSpan == null ? void 0 : activeSpan.spanContext();
11334
11717
  const liveSpanId = ctx && (0, import_api2.isValidSpanId)(ctx.spanId) ? ctx.spanId : void 0;
11718
+ if (activeSpan) {
11719
+ this.stampRouting(activeSpan);
11720
+ }
11335
11721
  emitLive({
11336
11722
  type: "tool_start",
11337
11723
  spanId: liveSpanId,
@@ -11340,7 +11726,7 @@ var LiveInteraction = class {
11340
11726
  metadata: params.inputParameters ? { args: params.inputParameters } : void 0
11341
11727
  });
11342
11728
  try {
11343
- const r = await fn.apply(thisArg, wrappedArgs);
11729
+ const r = this.routing ? await withRoutingContext(this.routing, () => fn.apply(thisArg, wrappedArgs)) : await fn.apply(thisArg, wrappedArgs);
11344
11730
  emitLive({
11345
11731
  type: "tool_result",
11346
11732
  spanId: liveSpanId,
@@ -11376,7 +11762,6 @@ var LiveInteraction = class {
11376
11762
  suppressTracing: params.suppressTracing
11377
11763
  },
11378
11764
  wrapped,
11379
- void 0,
11380
11765
  ...args
11381
11766
  );
11382
11767
  }
@@ -11387,6 +11772,7 @@ var LiveInteraction = class {
11387
11772
  setAssociationProperties(span, contextProperties);
11388
11773
  span.setAttribute("traceloop.span.kind", "task");
11389
11774
  setAssociationProperties(span, properties);
11775
+ this.stampRouting(span);
11390
11776
  return span;
11391
11777
  }
11392
11778
  startToolSpan(params) {
@@ -11428,6 +11814,7 @@ var LiveInteraction = class {
11428
11814
  setAssociationProperties(span, contextProperties);
11429
11815
  span.setAttribute("traceloop.span.kind", "tool");
11430
11816
  setAssociationProperties(span, properties);
11817
+ this.stampRouting(span);
11431
11818
  if (inputParameters !== void 0) {
11432
11819
  span.setAttribute("traceloop.entity.input", serializeSpanValue(inputParameters));
11433
11820
  }
@@ -11530,6 +11917,8 @@ var LiveInteraction = class {
11530
11917
  }
11531
11918
  async finish(resultEvent) {
11532
11919
  var _a, _b, _c, _d;
11920
+ unbindRoutingContext(this.boundContext);
11921
+ this.boundContext = void 0;
11533
11922
  if (!this.traceId) {
11534
11923
  this.resolveLiveTraceId();
11535
11924
  }
@@ -11617,6 +12006,7 @@ var LiveInteraction = class {
11617
12006
  setAssociationProperties(span, contextProperties);
11618
12007
  span.setAttribute("traceloop.span.kind", "tool");
11619
12008
  setAssociationProperties(span, properties);
12009
+ this.stampRouting(span);
11620
12010
  if (input !== void 0) {
11621
12011
  span.setAttribute("traceloop.entity.input", stringifyBounded(input));
11622
12012
  }
@@ -11662,10 +12052,16 @@ var LiveInteraction = class {
11662
12052
  var import_api3 = require("@opentelemetry/api");
11663
12053
  var traceloop2 = __toESM(require("@traceloop/node-server-sdk"));
11664
12054
  var LiveTracer = class {
11665
- constructor(globalProperties, directShipper) {
12055
+ constructor(globalProperties, directShipper, routing) {
11666
12056
  this.tracer = import_api3.trace.getTracer("traceloop.tracer");
11667
12057
  this.globalProperties = globalProperties || {};
11668
12058
  this.directShipper = directShipper;
12059
+ this.routing = routing;
12060
+ }
12061
+ /** Stamp a manually-created OTel span with this tracer's client routing. */
12062
+ stampRouting(span) {
12063
+ if (!this.routing) return;
12064
+ stampSpan(span, this.routing.projectId, this.routing.authHint);
11669
12065
  }
11670
12066
  emitLiveEvent(event) {
11671
12067
  var _a, _b;
@@ -11688,6 +12084,16 @@ var LiveTracer = class {
11688
12084
  ...params.properties || {}
11689
12085
  };
11690
12086
  const inputParameters = typeof params === "string" ? void 0 : params.inputParameters;
12087
+ const wrappedFn = (...fnArgs) => {
12088
+ const activeSpan = import_api3.trace.getSpan(import_api3.context.active());
12089
+ if (activeSpan) {
12090
+ this.stampRouting(activeSpan);
12091
+ }
12092
+ if (this.routing) {
12093
+ return withRoutingContext(this.routing, () => fn.apply(thisArg, fnArgs));
12094
+ }
12095
+ return fn.apply(thisArg, fnArgs);
12096
+ };
11691
12097
  return traceloop2.withTask(
11692
12098
  {
11693
12099
  name: taskName,
@@ -11696,8 +12102,7 @@ var LiveTracer = class {
11696
12102
  traceContent: params.traceContent,
11697
12103
  suppressTracing: params.suppressTracing
11698
12104
  },
11699
- fn,
11700
- thisArg,
12105
+ wrappedFn,
11701
12106
  ...args
11702
12107
  );
11703
12108
  }
@@ -11746,6 +12151,7 @@ var LiveTracer = class {
11746
12151
  const span = this.tracer.startSpan(name, {
11747
12152
  startTime: startTimeMs
11748
12153
  });
12154
+ this.stampRouting(span);
11749
12155
  Object.entries(this.globalProperties).forEach(([key, value]) => {
11750
12156
  span.setAttribute("traceloop.association.properties." + key, String(value));
11751
12157
  });
@@ -11790,6 +12196,59 @@ var LiveTracer = class {
11790
12196
  }
11791
12197
  };
11792
12198
 
12199
+ // src/tracing/exit-flush.ts
12200
+ var traceloop3 = __toESM(require("@traceloop/node-server-sdk"));
12201
+ var STATE_KEY2 = /* @__PURE__ */ Symbol.for("raindrop.tracing.exitFlushState");
12202
+ function getState2() {
12203
+ const holder = globalThis;
12204
+ let state = holder[STATE_KEY2];
12205
+ if (!state) {
12206
+ state = {
12207
+ clientFlushers: /* @__PURE__ */ new Set(),
12208
+ pipelineFlushNeeded: false,
12209
+ handlerRegistered: false
12210
+ };
12211
+ holder[STATE_KEY2] = state;
12212
+ }
12213
+ return state;
12214
+ }
12215
+ function ensureExitHandler(state) {
12216
+ if (state.handlerRegistered) return;
12217
+ if (typeof process === "undefined" || typeof process.once !== "function") return;
12218
+ state.handlerRegistered = true;
12219
+ process.once("beforeExit", () => {
12220
+ void flushAll();
12221
+ });
12222
+ }
12223
+ function registerClientFlusher(flush) {
12224
+ const state = getState2();
12225
+ state.clientFlushers.add(flush);
12226
+ ensureExitHandler(state);
12227
+ return () => {
12228
+ state.clientFlushers.delete(flush);
12229
+ };
12230
+ }
12231
+ function markPipelineForExitFlush() {
12232
+ const state = getState2();
12233
+ state.pipelineFlushNeeded = true;
12234
+ ensureExitHandler(state);
12235
+ }
12236
+ async function flushAll() {
12237
+ const state = getState2();
12238
+ const tasks = [];
12239
+ for (const flush of state.clientFlushers) {
12240
+ tasks.push(Promise.resolve().then(flush).catch(() => {
12241
+ }));
12242
+ }
12243
+ if (state.pipelineFlushNeeded) {
12244
+ tasks.push(
12245
+ Promise.resolve().then(() => traceloop3.forceFlush()).catch(() => {
12246
+ })
12247
+ );
12248
+ }
12249
+ await Promise.all(tasks);
12250
+ }
12251
+
11793
12252
  // src/tracing/tracer-core.ts
11794
12253
  if (localDebuggerEnabled()) {
11795
12254
  if (!process.env.OTEL_EXPORTER_OTLP_PROTOCOL) {
@@ -11898,6 +12357,29 @@ function resolveLocalDebuggerExporterBaseUrl(baseUrlOpt) {
11898
12357
  function createLocalDebuggerOtlpExporter(config) {
11899
12358
  return new import_exporter_trace_otlp_http.OTLPTraceExporter(config);
11900
12359
  }
12360
+ function parseTraceloopHeaders(value) {
12361
+ const headers = {};
12362
+ for (const entry of value.split(",")) {
12363
+ const keyPairPart = entry.split(";")[0];
12364
+ const sep = keyPairPart.indexOf("=");
12365
+ if (sep <= 0) continue;
12366
+ try {
12367
+ const key = decodeURIComponent(keyPairPart.slice(0, sep).trim());
12368
+ const val = decodeURIComponent(keyPairPart.slice(sep + 1).trim());
12369
+ if (key.length > 0 && val.length > 0) headers[key] = val;
12370
+ } catch (e) {
12371
+ }
12372
+ }
12373
+ return headers;
12374
+ }
12375
+ function traceloopDefaultHeaders(writeKey) {
12376
+ const env = process.env.TRACELOOP_HEADERS;
12377
+ if (env) {
12378
+ const parsed = parseTraceloopHeaders(env);
12379
+ if (Object.keys(parsed).length > 0) return parsed;
12380
+ }
12381
+ return { Authorization: `Bearer ${writeKey}` };
12382
+ }
11901
12383
  function createLocalDebuggerSpanProcessor(options) {
11902
12384
  const localDebuggerUrl = resolveLocalDebuggerExporterBaseUrl(options.localDebuggerUrlOpt);
11903
12385
  if (!localDebuggerUrl) {
@@ -11908,7 +12390,7 @@ function createLocalDebuggerSpanProcessor(options) {
11908
12390
  headers: {}
11909
12391
  });
11910
12392
  try {
11911
- return traceloop3.createSpanProcessor({
12393
+ return traceloop4.createSpanProcessor({
11912
12394
  apiKey: options.writeKey,
11913
12395
  baseUrl: localDebuggerUrl,
11914
12396
  disableBatch: options.disableBatching,
@@ -11924,12 +12406,6 @@ function createLocalDebuggerSpanProcessor(options) {
11924
12406
  return void 0;
11925
12407
  }
11926
12408
  }
11927
- function mergeWithBestEffortLocalDebuggerProcessor(processor, localDebuggerProcessor) {
11928
- if (!localDebuggerProcessor) {
11929
- return processor;
11930
- }
11931
- return new CompositeSpanProcessor(processor ? [processor] : [], [localDebuggerProcessor]);
11932
- }
11933
12409
  var warnedSpanNormalizeFailure = false;
11934
12410
  var INVALID_OTEL_SPAN_ID = "0000000000000000";
11935
12411
  function normalizeSpanForV1Exporter(span) {
@@ -12229,13 +12705,12 @@ function createManualInstrumentations(instrumentModules) {
12229
12705
  }
12230
12706
  return instrumentations;
12231
12707
  }
12232
- var activeInteractions = new import_weakref.WeakValueMap();
12233
- var activeInteractionsByEventId = new import_weakref.WeakValueMap();
12234
12708
  function getCurrentTraceId() {
12235
12709
  var _a;
12236
12710
  return (_a = import_api4.trace.getSpan(import_api4.context.active())) == null ? void 0 : _a.spanContext().traceId;
12237
12711
  }
12238
12712
  var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12713
+ var _a;
12239
12714
  process.env.TRACELOOP_TELEMETRY = "false";
12240
12715
  const {
12241
12716
  logLevel,
@@ -12250,12 +12725,15 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12250
12725
  ...otherOptions
12251
12726
  } = options;
12252
12727
  const cloudEnabledResolved = cloudEnabled != null ? cloudEnabled : true;
12728
+ const authHint = authHintForKey(writeKey);
12729
+ const clientTracingExports = !useExternalOtel && cloudEnabledResolved && tracingEnabled !== false;
12730
+ registerClientKey(authHint, clientTracingExports);
12253
12731
  const withProjectIdHeaders = (existing) => {
12254
12732
  if (!projectId) {
12255
12733
  return existing;
12256
12734
  }
12257
12735
  return {
12258
- Authorization: `Bearer ${writeKey}`,
12736
+ ...traceloopDefaultHeaders(writeKey),
12259
12737
  ...existing != null ? existing : {},
12260
12738
  ...projectIdHeaders(projectId)
12261
12739
  };
@@ -12277,9 +12755,12 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12277
12755
  projectId
12278
12756
  }) : void 0;
12279
12757
  let traceloopInitialized = false;
12758
+ let claimedPipeline = false;
12759
+ const activeInteractions = new import_weakref.WeakValueMap();
12760
+ const activeInteractionsByEventId = new import_weakref.WeakValueMap();
12280
12761
  try {
12281
12762
  if (useExternalOtel) {
12282
- traceloop3.initialize({
12763
+ traceloop4.initialize({
12283
12764
  baseUrl: apiUrl,
12284
12765
  apiKey: writeKey,
12285
12766
  logLevel,
@@ -12291,6 +12772,7 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12291
12772
  // DO NOT create NodeSDK - customer has their own
12292
12773
  });
12293
12774
  traceloopInitialized = true;
12775
+ markTraceloopConfiguredWithoutClaim();
12294
12776
  if (isDebug) {
12295
12777
  console.log(
12296
12778
  "[raindrop] External OTEL mode active. Add raindrop.createSpanProcessor() to your NodeSDK's spanProcessors. Register instrumentations (AnthropicInstrumentation, etc.) on your NodeSDK."
@@ -12307,12 +12789,12 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12307
12789
  const localDebuggerProcessor = createLocalDebuggerSpanProcessor({
12308
12790
  writeKey,
12309
12791
  disableBatching,
12310
- allowedInstrumentationLibraries: traceloop3.ALL_INSTRUMENTATION_LIBRARIES,
12792
+ allowedInstrumentationLibraries: traceloop4.ALL_INSTRUMENTATION_LIBRARIES,
12311
12793
  localDebuggerUrlOpt: localDebuggerUrl
12312
12794
  });
12313
12795
  if (!cloudEnabledResolved) {
12314
12796
  if (localDebuggerProcessor) {
12315
- traceloop3.initialize({
12797
+ traceloop4.initialize({
12316
12798
  baseUrl: apiUrl,
12317
12799
  apiKey: writeKey,
12318
12800
  ...otherOptions,
@@ -12323,8 +12805,9 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12323
12805
  processor: localDebuggerProcessor
12324
12806
  });
12325
12807
  traceloopInitialized = true;
12808
+ markTraceloopConfiguredWithoutClaim();
12326
12809
  } else {
12327
- traceloop3.initialize({
12810
+ traceloop4.initialize({
12328
12811
  baseUrl: apiUrl,
12329
12812
  apiKey: writeKey,
12330
12813
  logLevel,
@@ -12332,33 +12815,91 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12332
12815
  tracingEnabled: false
12333
12816
  });
12334
12817
  traceloopInitialized = true;
12818
+ markTraceloopConfiguredWithoutClaim();
12335
12819
  }
12820
+ } else if (tracingEnabled === false) {
12821
+ traceloopInitialized = false;
12336
12822
  } else {
12337
- const processor = mergeWithBestEffortLocalDebuggerProcessor(
12338
- otherOptions.processor,
12339
- localDebuggerProcessor
12340
- );
12341
- const headers = withProjectIdHeaders(otherOptions.headers);
12342
- traceloop3.initialize({
12343
- baseUrl: apiUrl,
12344
- apiKey: writeKey,
12345
- ...otherOptions,
12346
- logLevel,
12347
- silenceInitializationMessage: true,
12348
- tracingEnabled: tracingEnabled !== false,
12349
- disableBatch: disableBatching,
12350
- ...processor ? { processor } : {},
12351
- ...headers ? { headers } : {}
12352
- });
12353
- traceloopInitialized = true;
12823
+ const claimed = claimPipeline(authHint, projectId);
12824
+ claimedPipeline = claimed;
12825
+ const ownerHint = pipelineOwnerHint();
12826
+ if (!claimed && authHint !== ownerHint) {
12827
+ markForeignClient();
12828
+ console.warn(
12829
+ "[raindrop] The OTel tracing pipeline is already initialized with a DIFFERENT api key. One process supports one tracing key/org: auto-instrumented spans from this client will be dropped at export, and spans not bound to any client (no begin()/asCurrent()) will be dropped as unattributable (manual events are unaffected and route normally). Run this client in its own process for tracing."
12830
+ );
12831
+ }
12832
+ if (claimed && traceloopConfiguredWithoutClaim()) {
12833
+ releasePipelineClaim();
12834
+ claimedPipeline = false;
12835
+ console.warn(
12836
+ "[raindrop] Cannot initialize the cloud tracing pipeline: OpenTelemetry was already configured in this process by an earlier Raindrop client (external-OTEL mode, or a local-only/Workshop client with no write key). Traceloop's tracer provider is fixed once per process, so this client's auto-instrumented spans will NOT be exported. Construct the cloud tracing-enabled client FIRST, or run it in its own process. Manual events (track_ai/begin/finish/signals/identify) are unaffected."
12837
+ );
12838
+ } else if (claimed) {
12839
+ const exporterHeaders = (_a = withProjectIdHeaders(otherOptions.headers)) != null ? _a : traceloopDefaultHeaders(writeKey);
12840
+ let guardedExporter;
12841
+ try {
12842
+ const callerExporter = otherOptions.exporter;
12843
+ guardedExporter = callerExporter ? new GuardedSpanExporter(callerExporter, ownerHint) : buildGuardedExporter(apiUrl, exporterHeaders, ownerHint);
12844
+ } catch (guardError) {
12845
+ releasePipelineClaim();
12846
+ claimedPipeline = false;
12847
+ console.warn(
12848
+ `[raindrop] could not install the guarded span exporter (${guardError instanceof Error ? guardError.message : guardError}); disabling tracing rather than running an unguarded pipeline. Manual events are unaffected.`
12849
+ );
12850
+ }
12851
+ if (guardedExporter) {
12852
+ const contextProcessor = new RaindropContextSpanProcessor();
12853
+ const callerProcessor = otherOptions.processor;
12854
+ const processor = new CompositeSpanProcessor(
12855
+ callerProcessor ? [contextProcessor, callerProcessor] : [contextProcessor],
12856
+ localDebuggerProcessor ? [localDebuggerProcessor] : []
12857
+ );
12858
+ try {
12859
+ traceloop4.initialize({
12860
+ baseUrl: apiUrl,
12861
+ apiKey: writeKey,
12862
+ ...otherOptions,
12863
+ logLevel,
12864
+ silenceInitializationMessage: true,
12865
+ // This branch only runs when tracing is enabled (the disabled
12866
+ // case is handled above), so tracing is always on here.
12867
+ tracingEnabled: true,
12868
+ disableBatch: disableBatching,
12869
+ processor,
12870
+ exporter: guardedExporter
12871
+ });
12872
+ traceloopInitialized = true;
12873
+ } catch (initError) {
12874
+ releasePipelineClaim();
12875
+ claimedPipeline = false;
12876
+ throw initError;
12877
+ }
12878
+ }
12879
+ } else {
12880
+ traceloopInitialized = true;
12881
+ }
12354
12882
  }
12355
12883
  }
12356
12884
  } catch (error) {
12885
+ if (claimedPipeline && !traceloopInitialized) {
12886
+ releasePipelineClaim();
12887
+ claimedPipeline = false;
12888
+ }
12357
12889
  console.warn(
12358
12890
  "[raindrop] Failed to initialize traceloop. Spans will not be sent.",
12359
12891
  error instanceof Error ? error.message : error
12360
12892
  );
12361
12893
  }
12894
+ if (traceloopInitialized) {
12895
+ markPipelineForExitFlush();
12896
+ }
12897
+ const deregisterExitFlusher = directShipper ? registerClientFlusher(async () => {
12898
+ try {
12899
+ await directShipper.flush();
12900
+ } catch (e) {
12901
+ }
12902
+ }) : void 0;
12362
12903
  return {
12363
12904
  /**
12364
12905
  * Returns instrumentation instances for your NodeSDK.
@@ -12446,7 +12987,7 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12446
12987
  );
12447
12988
  }
12448
12989
  const headers = withProjectIdHeaders(processorOptions == null ? void 0 : processorOptions.headers);
12449
- const productionProcessor = traceloop3.createSpanProcessor({
12990
+ const productionProcessor = traceloop4.createSpanProcessor({
12450
12991
  baseUrl: apiUrl,
12451
12992
  apiKey: writeKey,
12452
12993
  disableBatch: disableBatching,
@@ -12459,12 +13000,16 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12459
13000
  allowedInstrumentationLibraries: processorOptions == null ? void 0 : processorOptions.allowedInstrumentationLibraries,
12460
13001
  localDebuggerUrlOpt: localDebuggerUrl
12461
13002
  });
13003
+ const contextProcessor = new RaindropContextSpanProcessor();
12462
13004
  return withV2SpanCompat(
12463
- mergeWithBestEffortLocalDebuggerProcessor(productionProcessor, localDebuggerProcessor)
13005
+ new CompositeSpanProcessor(
13006
+ [contextProcessor, productionProcessor],
13007
+ localDebuggerProcessor ? [localDebuggerProcessor] : []
13008
+ )
12464
13009
  );
12465
13010
  },
12466
13011
  begin(traceContext) {
12467
- var _a;
13012
+ var _a2;
12468
13013
  if (!traceContext.eventId) {
12469
13014
  traceContext.eventId = crypto.randomUUID();
12470
13015
  }
@@ -12473,19 +13018,27 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12473
13018
  traceContext,
12474
13019
  traceId,
12475
13020
  analytics,
12476
- directShipper
13021
+ directShipper,
13022
+ { projectId, authHint }
12477
13023
  );
12478
- analytics._trackAiPartial({
12479
- ...traceContext,
12480
- properties: {
12481
- ...(_a = traceContext.properties) != null ? _a : {},
12482
- ...interaction.getTraceId() ? { trace_id: interaction.getTraceId() } : {}
13024
+ const bound = bindRoutingContext({ projectId, authHint }, interaction);
13025
+ interaction.setBoundContext(bound);
13026
+ try {
13027
+ analytics._trackAiPartial({
13028
+ ...traceContext,
13029
+ properties: {
13030
+ ...(_a2 = traceContext.properties) != null ? _a2 : {},
13031
+ ...interaction.getTraceId() ? { trace_id: interaction.getTraceId() } : {}
13032
+ }
13033
+ });
13034
+ if (interaction.getTraceId()) {
13035
+ activeInteractions.set(interaction.getTraceId(), interaction);
12483
13036
  }
12484
- });
12485
- if (interaction.getTraceId()) {
12486
- activeInteractions.set(interaction.getTraceId(), interaction);
13037
+ activeInteractionsByEventId.set(traceContext.eventId, interaction);
13038
+ } catch (error) {
13039
+ unbindRoutingContext(bound);
13040
+ throw error;
12487
13041
  }
12488
- activeInteractionsByEventId.set(traceContext.eventId, interaction);
12489
13042
  return interaction;
12490
13043
  },
12491
13044
  getActiveInteraction(eventId) {
@@ -12500,12 +13053,15 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12500
13053
  return interactionByTraceId;
12501
13054
  }
12502
13055
  }
12503
- const interaction = new LiveInteraction({ eventId }, traceId, analytics, directShipper);
13056
+ const interaction = new LiveInteraction({ eventId }, traceId, analytics, directShipper, {
13057
+ projectId,
13058
+ authHint
13059
+ });
12504
13060
  activeInteractionsByEventId.set(eventId, interaction);
12505
13061
  return interaction;
12506
13062
  },
12507
13063
  tracer(globalProperties) {
12508
- return new LiveTracer(globalProperties, directShipper);
13064
+ return new LiveTracer(globalProperties, directShipper, { projectId, authHint });
12509
13065
  },
12510
13066
  /**
12511
13067
  * Best-effort drain of in-flight OTel spans and direct shipper batches.
@@ -12520,18 +13076,19 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12520
13076
  }
12521
13077
  if (traceloopInitialized) {
12522
13078
  try {
12523
- await traceloop3.forceFlush();
13079
+ await traceloop4.forceFlush();
12524
13080
  } catch (e) {
12525
13081
  }
12526
13082
  }
12527
13083
  },
12528
13084
  async close() {
13085
+ deregisterExitFlusher == null ? void 0 : deregisterExitFlusher();
12529
13086
  activeInteractions.clear();
12530
13087
  activeInteractionsByEventId.clear();
12531
13088
  await (directShipper == null ? void 0 : directShipper.shutdown());
12532
13089
  if (traceloopInitialized) {
12533
13090
  try {
12534
- await traceloop3.forceFlush();
13091
+ await traceloop4.forceFlush();
12535
13092
  } catch (e) {
12536
13093
  }
12537
13094
  }
@@ -12743,6 +13300,7 @@ var Raindrop = class {
12743
13300
  setDefaultMaxTextFieldChars(config.maxTextFieldChars);
12744
13301
  this.maxTextFieldChars = resolveMaxTextFieldChars(config.maxTextFieldChars);
12745
13302
  this.writeKey = (_a = config.writeKey) != null ? _a : "";
13303
+ this.authHint = authHintForKey(this.writeKey);
12746
13304
  this.localDebuggerUrlOpt = config.localWorkshopUrl === false ? null : config.localWorkshopUrl;
12747
13305
  this.awaitSpanFlushOpt = config.awaitSpanFlush;
12748
13306
  this.apiUrl = (_b = this.formatEndpoint(config.endpoint)) != null ? _b : `https://api.raindrop.ai/v1/`;
@@ -12805,6 +13363,14 @@ var Raindrop = class {
12805
13363
  /**
12806
13364
  * Begins a new interaction.
12807
13365
  *
13366
+ * The interaction's routing binding lives until `finish()` (which unbinds it
13367
+ * by identity, so it is removed even if `finish()` runs inside a nested
13368
+ * `withSpan`/`withTool`/`asCurrent` scope or a detached task). One exception:
13369
+ * calling `begin()` INSIDE an `asCurrent(fn)` scope pushes the binding within
13370
+ * that scope, so it dies when the scope exits (matching the Python SDK's
13371
+ * token-reset semantics) — start such interactions outside `asCurrent`, or
13372
+ * keep their work inside the same scope.
13373
+ *
12808
13374
  * @param traceContext - The trace context for the interaction.
12809
13375
  * @returns The interaction object.
12810
13376
  */
@@ -12822,6 +13388,22 @@ var Raindrop = class {
12822
13388
  tracer(globalProperties) {
12823
13389
  return this._tracing.tracer(globalProperties);
12824
13390
  }
13391
+ /**
13392
+ * Scope all spans created inside `fn` (and its awaited continuations) to this
13393
+ * client's project/key, without an interaction. Use it around auto-
13394
+ * instrumented calls (an LLM SDK call, a framework invocation) that aren't
13395
+ * wrapped by `begin()`/`finish()` so their spans route to this client's
13396
+ * project in a multi-client process. The binding lasts exactly the duration
13397
+ * of `fn` — it is removed when `fn` returns or throws.
13398
+ *
13399
+ * @example
13400
+ * ```ts
13401
+ * const answer = await raindrop.asCurrent(() => llm.chat({ ... }));
13402
+ * ```
13403
+ */
13404
+ asCurrent(fn) {
13405
+ return withRoutingContext({ projectId: this.projectId, authHint: this.authHint }, fn);
13406
+ }
12825
13407
  createSpanProcessor(processorOptions) {
12826
13408
  return this._tracing.createSpanProcessor(processorOptions);
12827
13409
  }