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/README.md CHANGED
@@ -31,6 +31,40 @@ const raindrop = new Raindrop({
31
31
 
32
32
  This sets the `X-Raindrop-Project-Id` header on every event and trace. Omit it (or pass `"default"`) to use your org's default **Production** project — the existing behavior. Single-project orgs need nothing new.
33
33
 
34
+ ## Multi-project tracing
35
+
36
+ You can run several `Raindrop` clients in one process, each pinned to a different project, and their **traces** are routed independently (manual events were already per-instance):
37
+
38
+ ```ts
39
+ const support = new Raindrop({ writeKey: process.env.SUPPORT_KEY!, projectId: "support-prod" });
40
+ const billing = new Raindrop({ writeKey: process.env.BILLING_KEY!, projectId: "billing-prod" });
41
+ ```
42
+
43
+ Each span is stamped with its owning client's project via the `raindrop.project_id` attribute (routed and org-validated at ingest). To route **auto-instrumented** spans (LLM/tool calls made by libraries you don't call directly) to a specific client, scope your code under that client's routing binding:
44
+
45
+ - `interaction = client.begin(...)` pushes a routing binding that lasts until `interaction.finish(...)`. Every auto-instrumented span started in the same async context is attributed to that client's project. An abandoned interaction (never finished, no longer referenced) releases its binding when it is garbage-collected, and `finish()` unbinds immediately even when called from a nested `withSpan`/`withTool`/`asCurrent` scope or a detached task.
46
+ - `client.asCurrent(fn)` runs `fn` (sync or async) under the client's binding for the callback's duration only — a scoped alternative to `begin()`/`finish()` when you just need routing, not an interaction.
47
+
48
+ ```ts
49
+ await support.asCurrent(async () => {
50
+ // auto-instrumented spans in here route to "support-prod"
51
+ await llm.chat(...);
52
+ });
53
+ ```
54
+
55
+ ### One tracing key per process
56
+
57
+ The underlying OpenTelemetry/Traceloop pipeline is a **per-process singleton**: the first tracing-enabled client claims it, and later clients share it. Using **two different write keys** (i.e. two orgs) in the same process is supported for manual events, but for tracing it flips the export guard to *positive attribution*: once a second key is seen, only spans provably bound to the pipeline owner's key are exported — spans from the other key **and** spans not bound to any client (no `begin()`/`asCurrent()`) are dropped, with a one-time warning. Single-key processes (the normal case) are unaffected and their spans stay byte-identical on the wire. Keep one tracing key per process; use separate processes for separate orgs.
58
+
59
+ ### Cloudflare Workers / runtimes without `AsyncLocalStorage.enterWith`
60
+
61
+ On runtimes that lack a usable `AsyncLocalStorage.enterWith` (notably Cloudflare Workers/`workerd`), `begin()` **cannot** scope auto-instrumented spans — it still returns an interaction for manual `withSpan`/`withTool`/events, but ambient routing is skipped (with a one-time warning) so it can't cross-contaminate concurrent requests. Use the run-based `asCurrent(fn)` (or `interaction.withSpan(...)`) to route auto-instrumented spans there; those are built on `AsyncLocalStorage.run`, which Workers support.
62
+
63
+ ### Behavior notes
64
+
65
+ - `withSpan`/`withTool` invoke your callback as `fn.apply(thisArg, args)` — the callback receives exactly the arguments you pass (an earlier version prepended a spurious leading `undefined`). If you had code compensating for that shifted argument, remove the workaround.
66
+ - With per-client interaction registries, `resumeInteraction(eventId)` returns only interactions that **this** client began; a second client resuming the same event id gets its own fresh interaction rather than the first client's.
67
+
34
68
  ## Payload size limits
35
69
 
36
70
  Text fields (ai input/output, tool span I/O, span content) are capped at