@raindrop-ai/langchain 0.0.6 → 0.0.8

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
@@ -46,12 +46,26 @@ await raindrop.flush();
46
46
  | `endpoint` | `string` | `https://api.raindrop.ai/v1/` | API endpoint |
47
47
  | `userId` | `string` | - | Associate all events with a user |
48
48
  | `convoId` | `string` | - | Group events into a conversation |
49
+ | `projectId` | `string` | - | Route events to a specific project (slug); omit for the default **Production** project |
49
50
  | `debug` | `boolean` | `false` | Enable verbose logging |
50
51
  | `traceChains` | `boolean` | `true` | Create spans for chain execution |
51
52
  | `traceRetrievers` | `boolean` | `true` | Create spans for retriever calls |
52
53
  | `filterLangGraphInternals` | `boolean` | `true` | Filter LangGraph-internal chain events and deduplicate LLM callbacks |
53
54
  | `maxTextFieldChars` | `number` | `1000000` | Per-field cap for event input/output and serialized span payloads, enforced before/during serialization (truncated values end with `...[truncated by raindrop]`; a stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored) |
54
55
 
56
+ ## Projects
57
+
58
+ If your org has multiple projects, route events to a specific one by passing its slug as `projectId`:
59
+
60
+ ```typescript
61
+ const raindrop = createRaindropLangChain({
62
+ writeKey: "your-write-key",
63
+ projectId: "support-prod",
64
+ });
65
+ ```
66
+
67
+ This sets the `X-Raindrop-Project-Id` header on every event. Omit it (or pass `"default"`) to use your org's default **Production** project — the existing behavior. Single-project orgs need nothing new.
68
+
55
69
  ## LangGraph Support
56
70
 
57
71
  Works with LangGraph out of the box. The handler automatically:
package/dist/index.d.mts CHANGED
@@ -365,6 +365,42 @@ declare class TraceShipper {
365
365
  shutdown(): Promise<void>;
366
366
  }
367
367
 
368
+ /**
369
+ * Run telemetry egress with OpenTelemetry tracing suppressed.
370
+ *
371
+ * Why this exists
372
+ * ---------------
373
+ * Raindrop integrations ship spans/events over HTTP with the global `fetch`
374
+ * (see {@link ../http.ts `postJson`}). When the host app also runs an OTel
375
+ * fetch/undici instrumentation — e.g. `@vercel/otel`'s `registerOTel`, which
376
+ * every Eve agent installs — that instrumentation wraps *our own* telemetry
377
+ * POSTs in a `fetch POST <endpoint>` span. Those spans are then handed to the
378
+ * very exporter that issued the request, so they get shipped right back to
379
+ * Raindrop and Workshop as standalone "runs" (and, because each export issues
380
+ * another fetch, they feed back on themselves). The result is a run list
381
+ * flooded with `fetch POST .../v1/traces`, `.../events/track_partial` and
382
+ * `.../live` entries that drown out the real agent turns — especially with
383
+ * sub-agents, where every sandbox runs its own instrumentation.
384
+ *
385
+ * The OTel-blessed fix is to mark the active context as "tracing suppressed"
386
+ * around the request; instrumentations check `isTracingSuppressed` and return
387
+ * a no-op span instead of recording one. We do this through a hook stashed on
388
+ * `globalThis` by the Node entrypoint ({@link ../index.node.ts}) so that:
389
+ * - `@opentelemetry/api` / `@opentelemetry/core` stay *optional* — core never
390
+ * hard-depends on them, and the hook is simply absent when they (and thus
391
+ * any instrumentation to suppress) are not installed; and
392
+ * - the browser bundle never pulls in `node:module`, mirroring how core
393
+ * injects `AsyncLocalStorage` via `RAINDROP_ASYNC_LOCAL_STORAGE`.
394
+ *
395
+ * When no hook is present the callback runs unchanged, so suppression is a
396
+ * best-effort no-op rather than a hard requirement.
397
+ */
398
+ /** Hook signature: run `fn` with OTel tracing suppressed, returning its value. */
399
+ type SuppressTracingHook = <T>(fn: () => T) => T;
400
+ declare global {
401
+ var RAINDROP_SUPPRESS_TRACING: SuppressTracingHook | undefined;
402
+ }
403
+
368
404
  type ParentSpanContext = {
369
405
  traceIdB64: string;
370
406
  spanIdB64: string;
package/dist/index.d.ts CHANGED
@@ -365,6 +365,42 @@ declare class TraceShipper {
365
365
  shutdown(): Promise<void>;
366
366
  }
367
367
 
368
+ /**
369
+ * Run telemetry egress with OpenTelemetry tracing suppressed.
370
+ *
371
+ * Why this exists
372
+ * ---------------
373
+ * Raindrop integrations ship spans/events over HTTP with the global `fetch`
374
+ * (see {@link ../http.ts `postJson`}). When the host app also runs an OTel
375
+ * fetch/undici instrumentation — e.g. `@vercel/otel`'s `registerOTel`, which
376
+ * every Eve agent installs — that instrumentation wraps *our own* telemetry
377
+ * POSTs in a `fetch POST <endpoint>` span. Those spans are then handed to the
378
+ * very exporter that issued the request, so they get shipped right back to
379
+ * Raindrop and Workshop as standalone "runs" (and, because each export issues
380
+ * another fetch, they feed back on themselves). The result is a run list
381
+ * flooded with `fetch POST .../v1/traces`, `.../events/track_partial` and
382
+ * `.../live` entries that drown out the real agent turns — especially with
383
+ * sub-agents, where every sandbox runs its own instrumentation.
384
+ *
385
+ * The OTel-blessed fix is to mark the active context as "tracing suppressed"
386
+ * around the request; instrumentations check `isTracingSuppressed` and return
387
+ * a no-op span instead of recording one. We do this through a hook stashed on
388
+ * `globalThis` by the Node entrypoint ({@link ../index.node.ts}) so that:
389
+ * - `@opentelemetry/api` / `@opentelemetry/core` stay *optional* — core never
390
+ * hard-depends on them, and the hook is simply absent when they (and thus
391
+ * any instrumentation to suppress) are not installed; and
392
+ * - the browser bundle never pulls in `node:module`, mirroring how core
393
+ * injects `AsyncLocalStorage` via `RAINDROP_ASYNC_LOCAL_STORAGE`.
394
+ *
395
+ * When no hook is present the callback runs unchanged, so suppression is a
396
+ * best-effort no-op rather than a hard requirement.
397
+ */
398
+ /** Hook signature: run `fn` with OTel tracing suppressed, returning its value. */
399
+ type SuppressTracingHook = <T>(fn: () => T) => T;
400
+ declare global {
401
+ var RAINDROP_SUPPRESS_TRACING: SuppressTracingHook | undefined;
402
+ }
403
+
368
404
  type ParentSpanContext = {
369
405
  traceIdB64: string;
370
406
  spanIdB64: string;
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ module.exports = __toCommonJS(index_exports);
28
28
  // src/callback-handler.ts
29
29
  var import_base = require("@langchain/core/callbacks/base");
30
30
 
31
- // ../core/dist/chunk-SK6EJEO7.js
31
+ // ../core/dist/chunk-WKRW55KX.js
32
32
  function getCrypto() {
33
33
  const c = globalThis.crypto;
34
34
  return c;
@@ -83,6 +83,20 @@ function base64Encode(bytes) {
83
83
  }
84
84
  return out;
85
85
  }
86
+ function runWithTracingSuppressed(fn) {
87
+ const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
88
+ if (typeof hook !== "function") return fn();
89
+ let started = false;
90
+ try {
91
+ return hook(() => {
92
+ started = true;
93
+ return fn();
94
+ });
95
+ } catch (err) {
96
+ if (started) throw err;
97
+ return fn();
98
+ }
99
+ }
86
100
  var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
87
101
  var MAX_RETRY_DELAY_MS = 3e4;
88
102
  function wait(ms) {
@@ -193,15 +207,17 @@ async function postJson(url, body, headers, opts) {
193
207
  const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
194
208
  await withRetry(
195
209
  async () => {
196
- const resp = await fetch(url, {
197
- method: "POST",
198
- headers: {
199
- "Content-Type": "application/json",
200
- ...headers
201
- },
202
- body: JSON.stringify(body),
203
- signal: AbortSignal.timeout(timeoutMs)
204
- });
210
+ const resp = await runWithTracingSuppressed(
211
+ () => fetch(url, {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ ...headers
216
+ },
217
+ body: JSON.stringify(body),
218
+ signal: AbortSignal.timeout(timeoutMs)
219
+ })
220
+ );
205
221
  if (!resp.ok) {
206
222
  const text = await resp.text().catch(() => "");
207
223
  const err = new Error(
@@ -1149,6 +1165,31 @@ var TraceShipper = class {
1149
1165
  // ../core/dist/index.node.js
1150
1166
  var import_async_hooks = require("async_hooks");
1151
1167
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
1168
+ var SUPPRESS_TRACING_KEY = /* @__PURE__ */ Symbol.for(
1169
+ "OpenTelemetry SDK Context Key SUPPRESS_TRACING"
1170
+ );
1171
+ function findOtelContextManager() {
1172
+ var _a;
1173
+ for (const sym of Object.getOwnPropertySymbols(globalThis)) {
1174
+ if (!((_a = sym.description) == null ? void 0 : _a.startsWith("opentelemetry.js.api."))) continue;
1175
+ const api = globalThis[sym];
1176
+ const cm = api == null ? void 0 : api.context;
1177
+ if (cm && typeof cm.with === "function" && typeof cm.active === "function") {
1178
+ return cm;
1179
+ }
1180
+ }
1181
+ return void 0;
1182
+ }
1183
+ function installTracingSuppressionHook() {
1184
+ if (typeof globalThis.RAINDROP_SUPPRESS_TRACING === "function") return;
1185
+ const hook = (fn) => {
1186
+ const cm = findOtelContextManager();
1187
+ if (!cm) return fn();
1188
+ return cm.with(cm.active().setValue(SUPPRESS_TRACING_KEY, true), fn);
1189
+ };
1190
+ globalThis.RAINDROP_SUPPRESS_TRACING = hook;
1191
+ }
1192
+ installTracingSuppressionHook();
1152
1193
 
1153
1194
  // src/truncation.ts
1154
1195
  var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
@@ -1675,7 +1716,7 @@ function extractLLMMetadata(output) {
1675
1716
  // package.json
1676
1717
  var package_default = {
1677
1718
  name: "@raindrop-ai/langchain",
1678
- version: "0.0.6",
1719
+ version: "0.0.8",
1679
1720
  description: "Raindrop integration for LangChain",
1680
1721
  main: "dist/index.js",
1681
1722
  module: "dist/index.mjs",
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/callback-handler.ts
2
2
  import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
3
3
 
4
- // ../core/dist/chunk-SK6EJEO7.js
4
+ // ../core/dist/chunk-WKRW55KX.js
5
5
  function getCrypto() {
6
6
  const c = globalThis.crypto;
7
7
  return c;
@@ -56,6 +56,20 @@ function base64Encode(bytes) {
56
56
  }
57
57
  return out;
58
58
  }
59
+ function runWithTracingSuppressed(fn) {
60
+ const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
61
+ if (typeof hook !== "function") return fn();
62
+ let started = false;
63
+ try {
64
+ return hook(() => {
65
+ started = true;
66
+ return fn();
67
+ });
68
+ } catch (err) {
69
+ if (started) throw err;
70
+ return fn();
71
+ }
72
+ }
59
73
  var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
60
74
  var MAX_RETRY_DELAY_MS = 3e4;
61
75
  function wait(ms) {
@@ -166,15 +180,17 @@ async function postJson(url, body, headers, opts) {
166
180
  const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
167
181
  await withRetry(
168
182
  async () => {
169
- const resp = await fetch(url, {
170
- method: "POST",
171
- headers: {
172
- "Content-Type": "application/json",
173
- ...headers
174
- },
175
- body: JSON.stringify(body),
176
- signal: AbortSignal.timeout(timeoutMs)
177
- });
183
+ const resp = await runWithTracingSuppressed(
184
+ () => fetch(url, {
185
+ method: "POST",
186
+ headers: {
187
+ "Content-Type": "application/json",
188
+ ...headers
189
+ },
190
+ body: JSON.stringify(body),
191
+ signal: AbortSignal.timeout(timeoutMs)
192
+ })
193
+ );
178
194
  if (!resp.ok) {
179
195
  const text = await resp.text().catch(() => "");
180
196
  const err = new Error(
@@ -1122,6 +1138,31 @@ var TraceShipper = class {
1122
1138
  // ../core/dist/index.node.js
1123
1139
  import { AsyncLocalStorage } from "async_hooks";
1124
1140
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
1141
+ var SUPPRESS_TRACING_KEY = /* @__PURE__ */ Symbol.for(
1142
+ "OpenTelemetry SDK Context Key SUPPRESS_TRACING"
1143
+ );
1144
+ function findOtelContextManager() {
1145
+ var _a;
1146
+ for (const sym of Object.getOwnPropertySymbols(globalThis)) {
1147
+ if (!((_a = sym.description) == null ? void 0 : _a.startsWith("opentelemetry.js.api."))) continue;
1148
+ const api = globalThis[sym];
1149
+ const cm = api == null ? void 0 : api.context;
1150
+ if (cm && typeof cm.with === "function" && typeof cm.active === "function") {
1151
+ return cm;
1152
+ }
1153
+ }
1154
+ return void 0;
1155
+ }
1156
+ function installTracingSuppressionHook() {
1157
+ if (typeof globalThis.RAINDROP_SUPPRESS_TRACING === "function") return;
1158
+ const hook = (fn) => {
1159
+ const cm = findOtelContextManager();
1160
+ if (!cm) return fn();
1161
+ return cm.with(cm.active().setValue(SUPPRESS_TRACING_KEY, true), fn);
1162
+ };
1163
+ globalThis.RAINDROP_SUPPRESS_TRACING = hook;
1164
+ }
1165
+ installTracingSuppressionHook();
1125
1166
 
1126
1167
  // src/truncation.ts
1127
1168
  var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
@@ -1648,7 +1689,7 @@ function extractLLMMetadata(output) {
1648
1689
  // package.json
1649
1690
  var package_default = {
1650
1691
  name: "@raindrop-ai/langchain",
1651
- version: "0.0.6",
1692
+ version: "0.0.8",
1652
1693
  description: "Raindrop integration for LangChain",
1653
1694
  main: "dist/index.js",
1654
1695
  module: "dist/index.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/langchain",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Raindrop integration for LangChain",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -31,7 +31,7 @@
31
31
  "tsup": "^8.4.0",
32
32
  "typescript": "^5.3.3",
33
33
  "vitest": "^2.1.9",
34
- "@raindrop-ai/core": "0.0.4"
34
+ "@raindrop-ai/core": "0.0.5"
35
35
  },
36
36
  "tsup": {
37
37
  "entry": [