raindrop-ai 0.0.80 → 0.0.81-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.
@@ -1,5 +1,97 @@
1
+ // package.json
2
+ var package_default = {
3
+ name: "raindrop-ai",
4
+ version: "0.0.81",
5
+ main: "dist/index.js",
6
+ module: "dist/index.mjs",
7
+ types: "dist/index.d.ts",
8
+ license: "MIT",
9
+ exports: {
10
+ ".": {
11
+ types: "./dist/index.d.ts",
12
+ import: "./dist/index.mjs",
13
+ require: "./dist/index.js"
14
+ },
15
+ "./tracing": {
16
+ types: "./dist/tracing/index.d.ts",
17
+ node: "./dist/tracing/index.js",
18
+ import: "./dist/tracing/index.mjs",
19
+ require: "./dist/tracing/index.js"
20
+ },
21
+ "./otel": {
22
+ types: "./dist/otel/index.d.ts",
23
+ node: "./dist/otel/index.js",
24
+ import: "./dist/otel/index.mjs",
25
+ require: "./dist/otel/index.js"
26
+ }
27
+ },
28
+ files: [
29
+ "dist/**"
30
+ ],
31
+ scripts: {
32
+ build: "tsup",
33
+ dev: "tsup --watch",
34
+ clean: "rm -rf .turbo && rm -rf dist",
35
+ try: "tsx ./src/example/index.ts",
36
+ test: "vitest run",
37
+ smoke: "tsx ./scripts/smoke.ts"
38
+ },
39
+ devDependencies: {
40
+ "@types/node": "^20.11.17",
41
+ msw: "^2.12.8",
42
+ tsup: "^8.4.0",
43
+ tsx: "^4.20.3",
44
+ typescript: "^5.3.3",
45
+ vitest: "^4.0.10"
46
+ },
47
+ dependencies: {
48
+ "@dawn-analytics/redact-pii": "0.1.2",
49
+ "@opentelemetry/api": "^1.9.0",
50
+ "@opentelemetry/resources": "^2.0.1",
51
+ "@traceloop/node-server-sdk": "0.19.0-otel-v1",
52
+ weakref: "^0.2.1",
53
+ zod: "^3.23.8"
54
+ },
55
+ publishConfig: {
56
+ access: "public",
57
+ provenance: false
58
+ },
59
+ tsup: {
60
+ entry: [
61
+ "src/index.ts",
62
+ "src/tracing/index.ts",
63
+ "src/otel/index.ts"
64
+ ],
65
+ format: [
66
+ "cjs",
67
+ "esm"
68
+ ],
69
+ external: [
70
+ "@traceloop/node-server-sdk",
71
+ "@traceloop/instrumentation-anthropic",
72
+ "@traceloop/instrumentation-openai",
73
+ "@traceloop/instrumentation-cohere",
74
+ "@traceloop/instrumentation-bedrock",
75
+ "@traceloop/instrumentation-vertexai",
76
+ "@traceloop/instrumentation-pinecone",
77
+ "@traceloop/instrumentation-chromadb",
78
+ "@traceloop/instrumentation-qdrant",
79
+ "@traceloop/instrumentation-together",
80
+ "@opentelemetry/instrumentation",
81
+ "@opentelemetry/sdk-trace-base"
82
+ ],
83
+ noExternal: [
84
+ "@dawn/schemas"
85
+ ],
86
+ dts: {
87
+ resolve: true
88
+ },
89
+ clean: true
90
+ }
91
+ };
92
+
1
93
  // src/tracing/tracer-core.ts
2
- import { context, trace as trace3 } from "@opentelemetry/api";
94
+ import { context as context2, trace as trace4 } from "@opentelemetry/api";
3
95
  import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic";
4
96
  import { BedrockInstrumentation } from "@traceloop/instrumentation-bedrock";
5
97
  import { ChromaDBInstrumentation } from "@traceloop/instrumentation-chromadb";
@@ -15,18 +107,353 @@ import {
15
107
  import * as traceloop3 from "@traceloop/node-server-sdk";
16
108
  import { WeakValueMap } from "weakref";
17
109
 
110
+ // src/tracing/direct/http.ts
111
+ function wait(ms) {
112
+ return new Promise((resolve) => setTimeout(resolve, ms));
113
+ }
114
+ function formatEndpoint(endpoint) {
115
+ if (!endpoint) return void 0;
116
+ return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
117
+ }
118
+ function parseRetryAfter(headers) {
119
+ var _a;
120
+ const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
121
+ if (!value) return void 0;
122
+ const asNumber = Number(value);
123
+ if (!Number.isNaN(asNumber)) return asNumber * 1e3;
124
+ const asDate = new Date(value).getTime();
125
+ if (!Number.isNaN(asDate)) {
126
+ const delta = asDate - Date.now();
127
+ return delta > 0 ? delta : 0;
128
+ }
129
+ return void 0;
130
+ }
131
+ function getRetryDelayMs(attemptNumber, previousError) {
132
+ if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
133
+ const v = previousError.retryAfterMs;
134
+ if (typeof v === "number") return Math.max(0, v);
135
+ }
136
+ if (attemptNumber <= 1) return 0;
137
+ const base = 500;
138
+ const factor = Math.pow(2, attemptNumber - 2);
139
+ return base * factor;
140
+ }
141
+ async function withRetry(operation, opName, opts) {
142
+ let lastError = void 0;
143
+ for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
144
+ if (attemptNumber > 1) {
145
+ const delay = getRetryDelayMs(attemptNumber, lastError);
146
+ if (opts.debug) {
147
+ console.warn(
148
+ `[raindrop] ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
149
+ );
150
+ }
151
+ if (delay > 0) await wait(delay);
152
+ } else if (opts.debug) {
153
+ console.log(`[raindrop] ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
154
+ }
155
+ try {
156
+ return await operation();
157
+ } catch (err) {
158
+ lastError = err;
159
+ if (opts.debug) {
160
+ const msg = err instanceof Error ? err.message : String(err);
161
+ console.warn(
162
+ `[raindrop] ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
163
+ );
164
+ }
165
+ if (attemptNumber === opts.maxAttempts) break;
166
+ }
167
+ }
168
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
169
+ }
170
+ async function postJson(url, body, headers, opts) {
171
+ const opName = `POST ${url}`;
172
+ await withRetry(
173
+ async () => {
174
+ const resp = await fetch(url, {
175
+ method: "POST",
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ ...headers
179
+ },
180
+ body: JSON.stringify(body)
181
+ });
182
+ if (!resp.ok) {
183
+ const text = await resp.text().catch(() => "");
184
+ const err = new Error(
185
+ `HTTP ${resp.status} ${resp.statusText}${text ? `: ${text}` : ""}`
186
+ );
187
+ const retryAfterMs = parseRetryAfter(resp.headers);
188
+ if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
189
+ throw err;
190
+ }
191
+ },
192
+ opName,
193
+ opts
194
+ );
195
+ }
196
+
197
+ // src/tracing/direct/ids.ts
198
+ function getCrypto() {
199
+ const c = globalThis.crypto;
200
+ return c;
201
+ }
202
+ function randomBytes(length) {
203
+ const cryptoObj = getCrypto();
204
+ const out = new Uint8Array(length);
205
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
206
+ cryptoObj.getRandomValues(out);
207
+ return out;
208
+ }
209
+ for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
210
+ return out;
211
+ }
212
+ function base64Encode(bytes) {
213
+ const maybeBuffer = globalThis.Buffer;
214
+ if (maybeBuffer) {
215
+ return maybeBuffer.from(bytes).toString("base64");
216
+ }
217
+ let binary = "";
218
+ for (let i2 = 0; i2 < bytes.length; i2++) {
219
+ binary += String.fromCharCode(bytes[i2]);
220
+ }
221
+ const btoaFn = globalThis.btoa;
222
+ if (typeof btoaFn === "function") return btoaFn(binary);
223
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
224
+ let out = "";
225
+ let i = 0;
226
+ while (i < binary.length) {
227
+ const c1 = binary.charCodeAt(i++) & 255;
228
+ const c2 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
229
+ const c3 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
230
+ const e1 = c1 >> 2;
231
+ const e2 = (c1 & 3) << 4 | (Number.isNaN(c2) ? 0 : c2 >> 4);
232
+ const e3 = Number.isNaN(c2) ? 64 : (c2 & 15) << 2 | (Number.isNaN(c3) ? 0 : c3 >> 6);
233
+ const e4 = Number.isNaN(c3) ? 64 : c3 & 63;
234
+ out += alphabet.charAt(e1);
235
+ out += alphabet.charAt(e2);
236
+ out += e3 === 64 ? "=" : alphabet.charAt(e3);
237
+ out += e4 === 64 ? "=" : alphabet.charAt(e4);
238
+ }
239
+ return out;
240
+ }
241
+
242
+ // src/tracing/direct/otlp.ts
243
+ var libraryVersion = package_default.version;
244
+ var SERVICE_NAME = "raindrop-ai";
245
+ function attrString(key, value) {
246
+ if (value === void 0) return void 0;
247
+ return { key, value: { stringValue: value } };
248
+ }
249
+ function attrInt(key, value) {
250
+ if (value === void 0) return void 0;
251
+ if (!Number.isFinite(value)) return void 0;
252
+ return { key, value: { intValue: String(Math.trunc(value)) } };
253
+ }
254
+ function buildOtlpSpan(args) {
255
+ const attrs = args.attributes.filter((x) => x !== void 0);
256
+ const span = {
257
+ traceId: args.ids.traceIdB64,
258
+ spanId: args.ids.spanIdB64,
259
+ name: args.name,
260
+ startTimeUnixNano: args.startTimeUnixNano,
261
+ endTimeUnixNano: args.endTimeUnixNano
262
+ };
263
+ if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
264
+ if (attrs.length) span.attributes = attrs;
265
+ if (args.status) span.status = args.status;
266
+ return span;
267
+ }
268
+ function buildExportTraceServiceRequest(spans) {
269
+ return {
270
+ resourceSpans: [
271
+ {
272
+ resource: {
273
+ attributes: [{ key: "service.name", value: { stringValue: SERVICE_NAME } }]
274
+ },
275
+ scopeSpans: [
276
+ {
277
+ scope: { name: SERVICE_NAME, version: libraryVersion },
278
+ spans
279
+ }
280
+ ]
281
+ }
282
+ ]
283
+ };
284
+ }
285
+
286
+ // src/tracing/direct/context.ts
287
+ import { context, trace } from "@opentelemetry/api";
288
+ var INVALID_TRACE_ID = "00000000000000000000000000000000";
289
+ var INVALID_SPAN_ID = "0000000000000000";
290
+ function hexToBase64(hex) {
291
+ const bytes = new Uint8Array(hex.length / 2);
292
+ for (let i = 0; i < hex.length; i += 2) {
293
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
294
+ }
295
+ return base64Encode(bytes);
296
+ }
297
+ function getActiveTraceContext() {
298
+ const activeSpan = trace.getSpan(context.active());
299
+ if (!activeSpan) return {};
300
+ const spanContext = activeSpan.spanContext();
301
+ const traceIdHex = spanContext.traceId;
302
+ const spanIdHex = spanContext.spanId;
303
+ if (traceIdHex === INVALID_TRACE_ID) return {};
304
+ return {
305
+ traceIdB64: hexToBase64(traceIdHex),
306
+ parentSpanIdB64: spanIdHex !== INVALID_SPAN_ID ? hexToBase64(spanIdHex) : void 0
307
+ };
308
+ }
309
+
310
+ // src/tracing/direct/shipper.ts
311
+ var DirectTraceShipper = class {
312
+ constructor(opts) {
313
+ this.queue = [];
314
+ this.inFlight = /* @__PURE__ */ new Set();
315
+ var _a, _b, _c, _d, _e;
316
+ this.writeKey = opts.writeKey;
317
+ this.baseUrl = (_a = formatEndpoint(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
318
+ this.enabled = (_b = opts.enabled) != null ? _b : true;
319
+ this.debug = opts.debug;
320
+ this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
321
+ this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
322
+ this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
323
+ }
324
+ /**
325
+ * Build and enqueue an OTLP span for a trackTool call.
326
+ *
327
+ * Reads the active OTEL context if traceId/parentSpanId are not provided,
328
+ * so spans still appear as children in the trace tree.
329
+ */
330
+ sendToolSpan(params) {
331
+ if (!this.enabled) return;
332
+ let traceIdB64 = params.traceId;
333
+ let parentSpanIdB64 = params.parentSpanId;
334
+ if (!traceIdB64) {
335
+ const activeCtx = getActiveTraceContext();
336
+ traceIdB64 = activeCtx.traceIdB64;
337
+ parentSpanIdB64 = activeCtx.parentSpanIdB64;
338
+ }
339
+ const spanIdB64 = base64Encode(randomBytes(8));
340
+ if (!traceIdB64) {
341
+ traceIdB64 = base64Encode(randomBytes(16));
342
+ }
343
+ const startTimeUnixNano = String(params.startTimeMs * 1e6);
344
+ const endTimeUnixNano = String(params.endTimeMs * 1e6);
345
+ const attributes = [
346
+ // Required: passes hasAIOperation() filter in backend (parseSpan.ts)
347
+ attrString("traceloop.span.kind", "tool"),
348
+ // Parsed by backend for input/output payloads
349
+ attrString("traceloop.entity.input", params.input),
350
+ attrString("traceloop.entity.output", params.output),
351
+ // Duration tracking
352
+ attrInt("traceloop.entity.duration_ms", params.durationMs)
353
+ ];
354
+ for (const [key, value] of Object.entries(params.properties)) {
355
+ attributes.push(attrString(`traceloop.association.properties.${key}`, value));
356
+ }
357
+ let status;
358
+ if (params.error) {
359
+ status = { code: 2, message: params.error };
360
+ } else {
361
+ status = {
362
+ code: 1
363
+ /* STATUS_CODE_OK */
364
+ };
365
+ }
366
+ const span = buildOtlpSpan({
367
+ ids: {
368
+ traceIdB64,
369
+ spanIdB64,
370
+ parentSpanIdB64
371
+ },
372
+ name: params.name,
373
+ startTimeUnixNano,
374
+ endTimeUnixNano,
375
+ attributes,
376
+ status
377
+ });
378
+ this.enqueue(span);
379
+ if (this.debug) {
380
+ console.log(`[raindrop] direct shipper: enqueued span "${params.name}"`);
381
+ }
382
+ }
383
+ enqueue(span) {
384
+ if (!this.enabled) return;
385
+ if (this.queue.length >= this.maxQueueSize) {
386
+ this.queue.shift();
387
+ }
388
+ this.queue.push(span);
389
+ if (this.queue.length >= this.maxBatchSize) {
390
+ void this.flush().catch(() => {
391
+ });
392
+ return;
393
+ }
394
+ if (!this.timer) {
395
+ this.timer = setTimeout(() => {
396
+ this.timer = void 0;
397
+ void this.flush().catch(() => {
398
+ });
399
+ }, this.flushIntervalMs);
400
+ }
401
+ }
402
+ async flush() {
403
+ if (!this.enabled) return;
404
+ if (this.timer) {
405
+ clearTimeout(this.timer);
406
+ this.timer = void 0;
407
+ }
408
+ while (this.queue.length > 0) {
409
+ const batch = this.queue.splice(0, this.maxBatchSize);
410
+ const body = buildExportTraceServiceRequest(batch);
411
+ const url = `${this.baseUrl}traces`;
412
+ const p = postJson(
413
+ url,
414
+ body,
415
+ { Authorization: `Bearer ${this.writeKey}` },
416
+ { maxAttempts: 3, debug: this.debug }
417
+ );
418
+ this.inFlight.add(p);
419
+ try {
420
+ try {
421
+ await p;
422
+ if (this.debug) console.log(`[raindrop] direct: sent ${batch.length} spans`);
423
+ } catch (err) {
424
+ if (this.debug) {
425
+ const msg = err instanceof Error ? err.message : String(err);
426
+ console.warn(`[raindrop] direct: failed to send spans (dropping): ${msg}`);
427
+ }
428
+ }
429
+ } finally {
430
+ this.inFlight.delete(p);
431
+ }
432
+ }
433
+ }
434
+ async shutdown() {
435
+ if (this.timer) {
436
+ clearTimeout(this.timer);
437
+ this.timer = void 0;
438
+ }
439
+ await this.flush();
440
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
441
+ })));
442
+ }
443
+ };
444
+
18
445
  // src/tracing/liveInteraction.ts
19
- import { SpanStatusCode, trace } from "@opentelemetry/api";
446
+ import { SpanStatusCode, trace as trace2 } from "@opentelemetry/api";
20
447
  import * as traceloop from "@traceloop/node-server-sdk";
21
- function getPropertiesFromContext(context2) {
448
+ function getPropertiesFromContext(context3) {
22
449
  const properties = {
23
- ...context2.userId && { user_id: context2.userId },
24
- ...context2.convoId && { convo_id: context2.convoId },
25
- ...context2.eventId && { event_id: context2.eventId },
26
- ...context2.properties || {}
450
+ ...context3.userId && { user_id: context3.userId },
451
+ ...context3.convoId && { convo_id: context3.convoId },
452
+ ...context3.eventId && { event_id: context3.eventId },
453
+ ...context3.properties || {}
27
454
  };
28
- if (context2.attachments && context2.attachments.length > 0) {
29
- properties.attachments = JSON.stringify(context2.attachments);
455
+ if (context3.attachments && context3.attachments.length > 0) {
456
+ properties.attachments = JSON.stringify(context3.attachments);
30
457
  }
31
458
  return properties;
32
459
  }
@@ -62,11 +489,12 @@ var LiveToolSpan = class {
62
489
  }
63
490
  };
64
491
  var LiveInteraction = class {
65
- constructor(context2, traceId, analytics) {
66
- this.context = context2;
492
+ constructor(context3, traceId, analytics, directShipper) {
493
+ this.context = context3;
67
494
  this.analytics = analytics;
68
- this.tracer = trace.getTracer("traceloop.tracer");
495
+ this.tracer = trace2.getTracer("traceloop.tracer");
69
496
  this.traceId = traceId;
497
+ this.directShipper = directShipper;
70
498
  }
71
499
  async withSpan(params, fn, thisArg, ...args) {
72
500
  var _a;
@@ -101,10 +529,54 @@ var LiveInteraction = class {
101
529
  ...getPropertiesFromContext(this.context),
102
530
  ...params.properties || {}
103
531
  };
104
- const inputParams = params.inputParameters ? [params.inputParameters] : void 0;
105
532
  if ((_a = this.analytics) == null ? void 0 : _a.debugLogs) {
106
533
  console.log("[raindrop] using withTool in liveInteraction");
107
534
  }
535
+ if (this.directShipper) {
536
+ const { traceIdB64, parentSpanIdB64 } = getActiveTraceContext();
537
+ const startTimeMs = Date.now();
538
+ let result;
539
+ let error;
540
+ try {
541
+ result = await fn.apply(thisArg, args);
542
+ } catch (err) {
543
+ error = err instanceof Error ? err.message : String(err);
544
+ const endTimeMs2 = Date.now();
545
+ const durationMs2 = endTimeMs2 - startTimeMs;
546
+ const inputStr2 = params.inputParameters ? JSON.stringify(params.inputParameters) : void 0;
547
+ this.directShipper.sendToolSpan({
548
+ name: toolName,
549
+ input: inputStr2,
550
+ output: void 0,
551
+ durationMs: durationMs2,
552
+ startTimeMs,
553
+ endTimeMs: endTimeMs2,
554
+ error,
555
+ properties,
556
+ traceId: traceIdB64,
557
+ parentSpanId: parentSpanIdB64
558
+ });
559
+ throw err;
560
+ }
561
+ const endTimeMs = Date.now();
562
+ const durationMs = endTimeMs - startTimeMs;
563
+ const inputStr = params.inputParameters ? JSON.stringify(params.inputParameters) : void 0;
564
+ const outputStr = result !== void 0 ? typeof result === "string" ? result : JSON.stringify(result) : void 0;
565
+ this.directShipper.sendToolSpan({
566
+ name: toolName,
567
+ input: inputStr,
568
+ output: outputStr,
569
+ durationMs,
570
+ startTimeMs,
571
+ endTimeMs,
572
+ error: void 0,
573
+ properties,
574
+ traceId: traceIdB64,
575
+ parentSpanId: parentSpanIdB64
576
+ });
577
+ return result;
578
+ }
579
+ const inputParams = params.inputParameters ? [params.inputParameters] : void 0;
108
580
  return traceloop.withTool(
109
581
  {
110
582
  name: toolName,
@@ -210,10 +682,39 @@ var LiveInteraction = class {
210
682
  });
211
683
  }
212
684
  trackTool(params) {
213
- var _a;
685
+ var _a, _b;
214
686
  const { name, input, output, durationMs, startTime, error, properties = {} } = params;
215
687
  const startTimeMs = startTime instanceof Date ? startTime.getTime() : typeof startTime === "number" ? startTime : Date.now() - (durationMs != null ? durationMs : 0);
216
688
  const endTimeMs = startTimeMs + (durationMs != null ? durationMs : 0);
689
+ if (this.directShipper) {
690
+ const { traceIdB64, parentSpanIdB64 } = getActiveTraceContext();
691
+ const inputStr = input !== void 0 ? typeof input === "string" ? input : JSON.stringify(input) : void 0;
692
+ const outputStr = output !== void 0 ? typeof output === "string" ? output : JSON.stringify(output) : void 0;
693
+ this.directShipper.sendToolSpan({
694
+ name,
695
+ input: inputStr,
696
+ output: outputStr,
697
+ durationMs,
698
+ startTimeMs,
699
+ endTimeMs,
700
+ error: error ? error instanceof Error ? error.message : String(error) : void 0,
701
+ properties: {
702
+ ...getPropertiesFromContext(this.context),
703
+ ...properties
704
+ },
705
+ traceId: traceIdB64,
706
+ parentSpanId: parentSpanIdB64
707
+ });
708
+ if ((_a = this.analytics) == null ? void 0 : _a.debugLogs) {
709
+ console.log(`[raindrop] trackTool (direct): logged tool span "${name}"`, {
710
+ input,
711
+ output,
712
+ durationMs,
713
+ error: error ? String(error) : void 0
714
+ });
715
+ }
716
+ return;
717
+ }
217
718
  const span = this.tracer.startSpan(name, {
218
719
  startTime: startTimeMs
219
720
  });
@@ -240,7 +741,7 @@ var LiveInteraction = class {
240
741
  span.setStatus({ code: SpanStatusCode.OK });
241
742
  }
242
743
  span.end(endTimeMs);
243
- if ((_a = this.analytics) == null ? void 0 : _a.debugLogs) {
744
+ if ((_b = this.analytics) == null ? void 0 : _b.debugLogs) {
244
745
  console.log(`[raindrop] trackTool: logged tool span "${name}"`, {
245
746
  input,
246
747
  output,
@@ -252,12 +753,13 @@ var LiveInteraction = class {
252
753
  };
253
754
 
254
755
  // src/tracing/liveTracer.ts
255
- import { SpanStatusCode as SpanStatusCode2, trace as trace2 } from "@opentelemetry/api";
756
+ import { SpanStatusCode as SpanStatusCode2, trace as trace3 } from "@opentelemetry/api";
256
757
  import * as traceloop2 from "@traceloop/node-server-sdk";
257
758
  var LiveTracer = class {
258
- constructor(globalProperties) {
259
- this.tracer = trace2.getTracer("traceloop.tracer");
759
+ constructor(globalProperties, directShipper) {
760
+ this.tracer = trace3.getTracer("traceloop.tracer");
260
761
  this.globalProperties = globalProperties || {};
762
+ this.directShipper = directShipper;
261
763
  }
262
764
  async withSpan(params, fn, thisArg, ...args) {
263
765
  const taskName = typeof params === "string" ? params : params.name;
@@ -285,6 +787,27 @@ var LiveTracer = class {
285
787
  const { name, input, output, durationMs, startTime, error, properties = {} } = params;
286
788
  const startTimeMs = startTime instanceof Date ? startTime.getTime() : typeof startTime === "number" ? startTime : Date.now() - (durationMs != null ? durationMs : 0);
287
789
  const endTimeMs = startTimeMs + (durationMs != null ? durationMs : 0);
790
+ if (this.directShipper) {
791
+ const { traceIdB64, parentSpanIdB64 } = getActiveTraceContext();
792
+ const inputStr = input !== void 0 ? typeof input === "string" ? input : JSON.stringify(input) : void 0;
793
+ const outputStr = output !== void 0 ? typeof output === "string" ? output : JSON.stringify(output) : void 0;
794
+ this.directShipper.sendToolSpan({
795
+ name,
796
+ input: inputStr,
797
+ output: outputStr,
798
+ durationMs,
799
+ startTimeMs,
800
+ endTimeMs,
801
+ error: error ? error instanceof Error ? error.message : String(error) : void 0,
802
+ properties: {
803
+ ...this.globalProperties,
804
+ ...properties
805
+ },
806
+ traceId: traceIdB64,
807
+ parentSpanId: parentSpanIdB64
808
+ });
809
+ return;
810
+ }
288
811
  const span = this.tracer.startSpan(name, {
289
812
  startTime: startTimeMs
290
813
  });
@@ -390,14 +913,23 @@ var activeInteractions = new WeakValueMap();
390
913
  var activeInteractionsByEventId = new WeakValueMap();
391
914
  function getCurrentTraceId() {
392
915
  var _a;
393
- return (_a = trace3.getSpan(context.active())) == null ? void 0 : _a.spanContext().traceId;
916
+ return (_a = trace4.getSpan(context2.active())) == null ? void 0 : _a.spanContext().traceId;
394
917
  }
395
918
  var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
396
- const { logLevel, useExternalOtel, tracingEnabled, disableBatching, ...otherOptions } = options;
919
+ const { logLevel, useExternalOtel, tracingEnabled, disableBatching, bypassOtelForTools, directEndpoint, ...otherOptions } = options;
397
920
  if (isDebug) {
398
921
  const mode = useExternalOtel ? "(external OTEL mode)" : tracingEnabled === false ? "(tracing disabled)" : "";
399
922
  console.log("[raindrop] Initializing tracing", mode);
923
+ if (bypassOtelForTools) {
924
+ console.log("[raindrop] bypassOtelForTools enabled \u2014 tool spans ship directly via HTTP");
925
+ }
400
926
  }
927
+ const directShipper = bypassOtelForTools ? new DirectTraceShipper({
928
+ writeKey,
929
+ endpoint: directEndpoint != null ? directEndpoint : apiUrl,
930
+ debug: isDebug,
931
+ enabled: tracingEnabled !== false
932
+ }) : void 0;
401
933
  let traceloopInitialized = false;
402
934
  try {
403
935
  if (useExternalOtel) {
@@ -522,7 +1054,8 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
522
1054
  const interaction = new LiveInteraction(
523
1055
  traceContext,
524
1056
  traceId,
525
- analytics
1057
+ analytics,
1058
+ directShipper
526
1059
  );
527
1060
  if (traceId) {
528
1061
  activeInteractions.set(traceId, interaction);
@@ -542,16 +1075,17 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
542
1075
  return interactionByTraceId;
543
1076
  }
544
1077
  }
545
- const interaction = new LiveInteraction({ eventId }, traceId, analytics);
1078
+ const interaction = new LiveInteraction({ eventId }, traceId, analytics, directShipper);
546
1079
  activeInteractionsByEventId.set(eventId, interaction);
547
1080
  return interaction;
548
1081
  },
549
1082
  tracer(globalProperties) {
550
- return new LiveTracer(globalProperties);
1083
+ return new LiveTracer(globalProperties, directShipper);
551
1084
  },
552
1085
  async close() {
553
1086
  activeInteractions.clear();
554
1087
  activeInteractionsByEventId.clear();
1088
+ await (directShipper == null ? void 0 : directShipper.shutdown());
555
1089
  if (traceloopInitialized) {
556
1090
  try {
557
1091
  await traceloop3.forceFlush();
@@ -563,5 +1097,6 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
563
1097
  };
564
1098
 
565
1099
  export {
1100
+ package_default,
566
1101
  tracing
567
1102
  };
package/dist/index.d.mts CHANGED
@@ -620,6 +620,20 @@ interface AnalyticsConfig {
620
620
  * Defaults to true in development (NODE_ENV !== 'production').
621
621
  */
622
622
  disableBatching?: boolean;
623
+ /**
624
+ * When true, all `trackTool()` and `withTool()` calls bypass the OTEL
625
+ * exporter pipeline and ship spans directly to the Raindrop API via HTTP POST.
626
+ *
627
+ * Spans still read the active OTEL context to inherit traceId
628
+ * and parentSpanId — they still appear as children in the trace tree.
629
+ * Only the shipping mechanism changes.
630
+ *
631
+ * This is useful when OTEL setup is fragile (e.g., conflicting providers,
632
+ * broken exporters) but you still want tool spans to work reliably.
633
+ *
634
+ * @default false
635
+ */
636
+ bypassOtelForTools?: boolean;
623
637
  /**
624
638
  * Explicitly specify modules to instrument. Optional.
625
639
  *