@raindrop-ai/deep-agents 0.1.2 → 0.1.3

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
@@ -2252,6 +2252,463 @@ function installTracingSuppressionHook() {
2252
2252
  }
2253
2253
  installTracingSuppressionHook();
2254
2254
 
2255
+ // src/conversation.ts
2256
+ var import_messages = require("@langchain/core/messages");
2257
+
2258
+ // src/truncation.ts
2259
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
2260
+ var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
2261
+ var MAX_DEPTH = 12;
2262
+ var configuredMaxTextFieldChars;
2263
+ function setMaxTextFieldChars(limit) {
2264
+ if (limit === void 0) return;
2265
+ if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
2266
+ configuredMaxTextFieldChars = Math.floor(limit);
2267
+ } else {
2268
+ console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
2269
+ }
2270
+ }
2271
+ function otelEnvLimit() {
2272
+ if (typeof process === "undefined") return void 0;
2273
+ const raw = process.env?.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
2274
+ if (!raw) return void 0;
2275
+ const parsed = Number.parseInt(raw, 10);
2276
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
2277
+ }
2278
+ function eventMaxTextFieldChars() {
2279
+ return configuredMaxTextFieldChars ?? DEFAULT_MAX_TEXT_FIELD_CHARS2;
2280
+ }
2281
+ function spanAttributeLimit(limit) {
2282
+ const env = otelEnvLimit();
2283
+ return env !== void 0 && env < limit ? env : limit;
2284
+ }
2285
+ function effectiveMaxTextFieldChars() {
2286
+ return spanAttributeLimit(eventMaxTextFieldChars());
2287
+ }
2288
+ function truncateToLimit2(text, limit) {
2289
+ if (limit > TRUNCATION_MARKER2.length) {
2290
+ return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
2291
+ }
2292
+ return text.slice(0, limit);
2293
+ }
2294
+ function capText2(value, limit) {
2295
+ if (typeof value !== "string") return value;
2296
+ const max = limit ?? effectiveMaxTextFieldChars();
2297
+ if (value.length <= max) return value;
2298
+ return truncateToLimit2(value, max);
2299
+ }
2300
+ function boundedClone2(value, budget, depth) {
2301
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
2302
+ if (typeof value === "string") {
2303
+ if (value.length > budget.remaining) {
2304
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2305
+ budget.remaining = 0;
2306
+ return taken;
2307
+ }
2308
+ budget.remaining -= Math.max(value.length, 1);
2309
+ return value;
2310
+ }
2311
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
2312
+ budget.remaining -= 8;
2313
+ return value;
2314
+ }
2315
+ if (typeof value === "bigint") return boundedClone2(value.toString(), budget, depth);
2316
+ if (typeof value !== "object") {
2317
+ budget.remaining -= 1;
2318
+ return value;
2319
+ }
2320
+ if (value instanceof Uint8Array) {
2321
+ const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
2322
+ if (value.byteLength > maxBytes) {
2323
+ const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
2324
+ budget.remaining = 0;
2325
+ return taken;
2326
+ }
2327
+ const encoded = base64Encode(value);
2328
+ budget.remaining -= Math.max(encoded.length, 1);
2329
+ return encoded;
2330
+ }
2331
+ if (depth >= MAX_DEPTH) {
2332
+ budget.remaining -= 16;
2333
+ const name = value.constructor?.name ?? "object";
2334
+ return `<max depth: ${name}>`;
2335
+ }
2336
+ const toJSON = value.toJSON;
2337
+ if (typeof toJSON === "function") {
2338
+ budget.remaining -= 2;
2339
+ return boundedClone2(toJSON.call(value), budget, depth + 1);
2340
+ }
2341
+ if (Array.isArray(value)) {
2342
+ budget.remaining -= 2;
2343
+ const out2 = [];
2344
+ for (const item of value) {
2345
+ if (budget.remaining <= 0) {
2346
+ out2.push(TRUNCATION_MARKER2);
2347
+ break;
2348
+ }
2349
+ out2.push(boundedClone2(item, budget, depth + 1));
2350
+ }
2351
+ return out2;
2352
+ }
2353
+ budget.remaining -= 2;
2354
+ const out = {};
2355
+ for (const key in value) {
2356
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
2357
+ if (budget.remaining <= 0) {
2358
+ out["..."] = TRUNCATION_MARKER2;
2359
+ break;
2360
+ }
2361
+ let cappedKey = key;
2362
+ if (key.length > budget.remaining) {
2363
+ cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2364
+ budget.remaining = 0;
2365
+ out[cappedKey] = TRUNCATION_MARKER2;
2366
+ break;
2367
+ }
2368
+ budget.remaining -= Math.max(key.length, 1);
2369
+ out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
2370
+ }
2371
+ return out;
2372
+ }
2373
+ function boundedStringify(value, limit) {
2374
+ const max = limit ?? effectiveMaxTextFieldChars();
2375
+ try {
2376
+ if (typeof value === "string") {
2377
+ const text2 = JSON.stringify(capText2(value, max));
2378
+ return text2.length <= max ? text2 : truncateToLimit2(text2, max);
2379
+ }
2380
+ const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
2381
+ const text = JSON.stringify(pruned);
2382
+ if (text === void 0) return void 0;
2383
+ return text.length <= max ? text : truncateToLimit2(text, max);
2384
+ } catch {
2385
+ return void 0;
2386
+ }
2387
+ }
2388
+
2389
+ // src/conversation.ts
2390
+ var CAPTURE_TRUNCATED_ROLE = "raindrop";
2391
+ var MESSAGE_OVERHEAD = 32;
2392
+ var PART_OVERHEAD = 24;
2393
+ var MAX_DEPTH2 = 12;
2394
+ function isRecord(value) {
2395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2396
+ }
2397
+ function stringField(record, key) {
2398
+ const value = record[key];
2399
+ return typeof value === "string" ? value : void 0;
2400
+ }
2401
+ function takeText(text, budget) {
2402
+ if (text.length > budget.remaining) {
2403
+ const kept = text.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2404
+ budget.remaining = 0;
2405
+ budget.truncated = true;
2406
+ return kept;
2407
+ }
2408
+ budget.remaining -= Math.max(text.length, 1);
2409
+ return text;
2410
+ }
2411
+ function reserve(text, budget) {
2412
+ if (text !== void 0) budget.remaining -= Math.max(text.length, 1);
2413
+ return text;
2414
+ }
2415
+ function takeValue(value, budget, depth) {
2416
+ if (typeof value === "string") return takeText(value, budget);
2417
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
2418
+ budget.remaining -= 8;
2419
+ return value;
2420
+ }
2421
+ if (typeof value !== "object") {
2422
+ budget.remaining -= 1;
2423
+ return value;
2424
+ }
2425
+ if (depth >= MAX_DEPTH2) {
2426
+ budget.remaining -= TRUNCATION_MARKER2.length;
2427
+ budget.truncated = true;
2428
+ return TRUNCATION_MARKER2;
2429
+ }
2430
+ if ("toJSON" in value && typeof value.toJSON === "function") {
2431
+ return takeValue(value.toJSON(), budget, depth + 1);
2432
+ }
2433
+ if (Array.isArray(value)) {
2434
+ budget.remaining -= 2;
2435
+ const out2 = [];
2436
+ for (const item of value) {
2437
+ if (budget.remaining <= 0) {
2438
+ budget.truncated = true;
2439
+ out2.push(TRUNCATION_MARKER2);
2440
+ break;
2441
+ }
2442
+ out2.push(takeValue(item, budget, depth + 1));
2443
+ }
2444
+ return out2;
2445
+ }
2446
+ budget.remaining -= 2;
2447
+ const out = {};
2448
+ if (!isRecord(value)) return out;
2449
+ for (const key in value) {
2450
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
2451
+ if (budget.remaining <= 0) {
2452
+ budget.truncated = true;
2453
+ out["..."] = TRUNCATION_MARKER2;
2454
+ break;
2455
+ }
2456
+ budget.remaining -= Math.max(key.length, 1);
2457
+ out[key] = takeValue(value[key], budget, depth + 1);
2458
+ }
2459
+ return out;
2460
+ }
2461
+ function messageType(message) {
2462
+ return typeof message._getType === "function" ? message._getType() : void 0;
2463
+ }
2464
+ function textPart(text, budget) {
2465
+ return { type: "text", text: takeText(text, budget) };
2466
+ }
2467
+ function joinedText(content, budget) {
2468
+ const texts = [];
2469
+ for (const block of content) {
2470
+ if (budget.remaining <= 0) {
2471
+ budget.truncated = true;
2472
+ texts.push(TRUNCATION_MARKER2);
2473
+ break;
2474
+ }
2475
+ budget.remaining -= 1;
2476
+ if (!isRecord(block)) continue;
2477
+ const text = stringField(block, "text");
2478
+ if (text !== void 0) texts.push(takeText(text, budget));
2479
+ }
2480
+ return texts.join("\n");
2481
+ }
2482
+ function contentPart(block, budget) {
2483
+ budget.remaining -= PART_OVERHEAD;
2484
+ if (!isRecord(block)) return takeValue(block, budget, 0);
2485
+ const type = stringField(block, "type");
2486
+ const text = stringField(block, "text");
2487
+ if (type === "text" && text !== void 0) return textPart(text, budget);
2488
+ if (type === "image_url") {
2489
+ const imageUrl = block["image_url"];
2490
+ const url = typeof imageUrl === "string" ? imageUrl : isRecord(imageUrl) ? stringField(imageUrl, "url") : void 0;
2491
+ if (url !== void 0) return { type: "image", image: takeText(url, budget) };
2492
+ }
2493
+ if (type === "thinking") {
2494
+ const thinking = stringField(block, "thinking");
2495
+ if (thinking !== void 0) return { type: "reasoning", text: takeText(thinking, budget) };
2496
+ }
2497
+ if (type === "image" || type === "file" || type === "audio") {
2498
+ const mediaType = stringField(block, "mime_type");
2499
+ const sourceType = stringField(block, "source_type");
2500
+ const source = block["source"];
2501
+ if (sourceType === "text" && text !== void 0) return textPart(text, budget);
2502
+ const data = sourceType === "url" ? stringField(block, "url") : sourceType === "base64" ? stringField(block, "data") : isRecord(source) ? stringField(source, "url") ?? stringField(source, "data") : void 0;
2503
+ const sourceMediaType = mediaType ?? (isRecord(source) ? stringField(source, "media_type") : void 0);
2504
+ if (data !== void 0) {
2505
+ const filename = stringField(block, "filename");
2506
+ return type === "image" ? { type: "image", image: takeText(data, budget), mediaType: reserve(sourceMediaType, budget) } : {
2507
+ type: "file",
2508
+ data: takeText(data, budget),
2509
+ mediaType: reserve(sourceMediaType, budget),
2510
+ filename: reserve(filename, budget)
2511
+ };
2512
+ }
2513
+ }
2514
+ return takeValue(block, budget, 0);
2515
+ }
2516
+ function contentParts(content, budget, skipToolUseIds) {
2517
+ const parts = [];
2518
+ for (const block of content) {
2519
+ if (budget.remaining <= 0) {
2520
+ budget.truncated = true;
2521
+ parts.push({ type: "text", text: TRUNCATION_MARKER2 });
2522
+ break;
2523
+ }
2524
+ if (isRecord(block) && (block["type"] === "tool_use" || block["type"] === "tool_call")) {
2525
+ const id = stringField(block, "id");
2526
+ if (id !== void 0 && skipToolUseIds.has(id)) continue;
2527
+ }
2528
+ parts.push(contentPart(block, budget));
2529
+ }
2530
+ return parts;
2531
+ }
2532
+ function assistantMessage(message, budget, toolNames) {
2533
+ const toolCalls = (0, import_messages.isAIMessage)(message) ? message.tool_calls ?? [] : [];
2534
+ if (toolCalls.length === 0 && typeof message.content === "string") {
2535
+ return { role: "assistant", content: takeText(message.content, budget) };
2536
+ }
2537
+ const ids = /* @__PURE__ */ new Set();
2538
+ const callParts = [];
2539
+ for (const call of toolCalls) {
2540
+ if (budget.remaining <= 0) {
2541
+ budget.truncated = true;
2542
+ callParts.push({ type: "text", text: TRUNCATION_MARKER2 });
2543
+ break;
2544
+ }
2545
+ budget.remaining -= PART_OVERHEAD;
2546
+ if (call.id !== void 0) {
2547
+ ids.add(call.id);
2548
+ toolNames.set(call.id, call.name);
2549
+ }
2550
+ callParts.push({
2551
+ type: "tool-call",
2552
+ toolCallId: reserve(call.id, budget),
2553
+ toolName: reserve(call.name, budget),
2554
+ input: takeValue(call.args, budget, 0)
2555
+ });
2556
+ }
2557
+ const content = typeof message.content === "string" ? message.content.length > 0 ? [textPart(message.content, budget)] : [] : contentParts(message.content, budget, ids);
2558
+ content.push(...callParts);
2559
+ return { role: "assistant", content };
2560
+ }
2561
+ function toolResultOutput(message, budget) {
2562
+ const failed = (0, import_messages.isToolMessage)(message) && message.status === "error";
2563
+ if (typeof message.content === "string") {
2564
+ return { type: failed ? "error-text" : "text", value: takeText(message.content, budget) };
2565
+ }
2566
+ return { type: "content", value: contentParts(message.content, budget, /* @__PURE__ */ new Set()) };
2567
+ }
2568
+ function toolMessage(message, budget, toolNames) {
2569
+ const toolCallId = (0, import_messages.isToolMessage)(message) ? message.tool_call_id : void 0;
2570
+ const toolName = message.name ?? (toolCallId !== void 0 ? toolNames.get(toolCallId) : void 0);
2571
+ budget.remaining -= PART_OVERHEAD;
2572
+ return {
2573
+ role: "tool",
2574
+ content: [{
2575
+ type: "tool-result",
2576
+ toolCallId: reserve(toolCallId, budget),
2577
+ toolName: reserve(toolName, budget),
2578
+ output: toolResultOutput(message, budget)
2579
+ }]
2580
+ };
2581
+ }
2582
+ function modelMessage(message, budget, toolNames) {
2583
+ budget.remaining -= MESSAGE_OVERHEAD;
2584
+ switch (messageType(message)) {
2585
+ case "system":
2586
+ return {
2587
+ role: "system",
2588
+ content: typeof message.content === "string" ? takeText(message.content, budget) : joinedText(message.content, budget)
2589
+ };
2590
+ case "ai":
2591
+ return assistantMessage(message, budget, toolNames);
2592
+ case "tool":
2593
+ case "function":
2594
+ return toolMessage(message, budget, toolNames);
2595
+ case "generic": {
2596
+ const role = (0, import_messages.isChatMessage)(message) ? message.role : void 0;
2597
+ if (role === "assistant" || role === "system") {
2598
+ return {
2599
+ role,
2600
+ content: typeof message.content === "string" ? takeText(message.content, budget) : joinedText(message.content, budget)
2601
+ };
2602
+ }
2603
+ return userMessage(message, budget);
2604
+ }
2605
+ default:
2606
+ return userMessage(message, budget);
2607
+ }
2608
+ }
2609
+ function userMessage(message, budget) {
2610
+ return {
2611
+ role: "user",
2612
+ content: typeof message.content === "string" ? takeText(message.content, budget) : contentParts(message.content, budget, /* @__PURE__ */ new Set())
2613
+ };
2614
+ }
2615
+ function truncationRecord(omittedMessages) {
2616
+ return { role: CAPTURE_TRUNCATED_ROLE, truncated: true, marker: TRUNCATION_MARKER2, omittedMessages };
2617
+ }
2618
+ function encodeWithinLimit(items, limit, overflow, convert) {
2619
+ let textBudget = limit;
2620
+ for (let attempt = 0; attempt < 8 && textBudget >= 64; attempt++) {
2621
+ const budget = { remaining: textBudget, truncated: false };
2622
+ const out = [];
2623
+ let omitted = 0;
2624
+ for (let i = 0; i < items.length; i++) {
2625
+ if (overflow === "omit" && budget.remaining <= 0) {
2626
+ omitted = items.length - i;
2627
+ break;
2628
+ }
2629
+ out.push(convert(items[i], budget));
2630
+ }
2631
+ const truncated = budget.truncated || omitted > 0;
2632
+ if (truncated && overflow === "omit") out.push(truncationRecord(omitted));
2633
+ const json2 = JSON.stringify(out);
2634
+ if (json2.length <= limit) return { json: json2, truncated };
2635
+ const excess = json2.length - limit + 128;
2636
+ textBudget = Math.max(textBudget - excess, Math.floor(textBudget / 2));
2637
+ }
2638
+ if (overflow === "keep") return void 0;
2639
+ const json = JSON.stringify([truncationRecord(items.length)]);
2640
+ return json.length <= limit ? { json, truncated: true } : void 0;
2641
+ }
2642
+ function captureConversation(messages, limit) {
2643
+ if (messages.length === 0) return void 0;
2644
+ try {
2645
+ const toolNames = /* @__PURE__ */ new Map();
2646
+ return encodeWithinLimit(
2647
+ messages,
2648
+ limit,
2649
+ "omit",
2650
+ (message, budget) => modelMessage(message, budget, toolNames)
2651
+ );
2652
+ } catch {
2653
+ return void 0;
2654
+ }
2655
+ }
2656
+ function capturePrompt(prompt, limit) {
2657
+ try {
2658
+ return encodeWithinLimit([prompt], limit, "omit", (text, budget) => {
2659
+ budget.remaining -= MESSAGE_OVERHEAD;
2660
+ return { role: "user", content: takeText(text, budget) };
2661
+ });
2662
+ } catch {
2663
+ return void 0;
2664
+ }
2665
+ }
2666
+ function captureToolCalls(message, limit) {
2667
+ if (!isRecord(message) || !Array.isArray(message["tool_calls"])) return void 0;
2668
+ const calls = [];
2669
+ let structural = 0;
2670
+ for (const call of message["tool_calls"]) {
2671
+ structural += PART_OVERHEAD;
2672
+ if (isRecord(call)) {
2673
+ structural += (stringField(call, "id")?.length ?? 0) + (stringField(call, "name")?.length ?? 0);
2674
+ calls.push(call);
2675
+ }
2676
+ if (structural > limit) return void 0;
2677
+ }
2678
+ if (calls.length === 0) return void 0;
2679
+ try {
2680
+ return encodeWithinLimit(calls, limit, "keep", (call, budget) => {
2681
+ budget.remaining -= PART_OVERHEAD;
2682
+ return {
2683
+ toolCallId: reserve(stringField(call, "id"), budget),
2684
+ toolName: reserve(stringField(call, "name"), budget),
2685
+ input: takeValue(call["args"], budget, 0)
2686
+ };
2687
+ });
2688
+ } catch {
2689
+ return void 0;
2690
+ }
2691
+ }
2692
+ function parseToolCallInput(input) {
2693
+ try {
2694
+ const parsed = JSON.parse(input);
2695
+ if (isRecord(parsed) && parsed["type"] === "tool_call" && "args" in parsed) {
2696
+ return { id: stringField(parsed, "id"), args: JSON.stringify(parsed["args"]) };
2697
+ }
2698
+ } catch {
2699
+ }
2700
+ return { args: input };
2701
+ }
2702
+ function unwrapToolOutput(output) {
2703
+ if ((0, import_messages.isBaseMessage)(output)) {
2704
+ return {
2705
+ content: output.content,
2706
+ toolCallId: (0, import_messages.isToolMessage)(output) ? output.tool_call_id : void 0
2707
+ };
2708
+ }
2709
+ return { content: output };
2710
+ }
2711
+
2255
2712
  // src/callback-handler.ts
2256
2713
  function resolveEventId(source) {
2257
2714
  if (typeof source !== "function") return randomUUID();
@@ -2269,32 +2726,38 @@ function isInternalName(name) {
2269
2726
  if (name.startsWith("__") && name.endsWith("__")) return true;
2270
2727
  return false;
2271
2728
  }
2272
- var CHAIN_TEXT_MAX_LEN = 8192;
2273
- function extractTextFromContent(content) {
2729
+ function extractTextFromContent(content, limit) {
2274
2730
  if (content == null) return void 0;
2275
2731
  if (typeof content === "string") return content;
2276
2732
  if (Array.isArray(content)) {
2277
2733
  const parts = [];
2734
+ let length = 0;
2278
2735
  for (const part of content) {
2736
+ if (length > limit) break;
2737
+ length += 1;
2738
+ let text;
2279
2739
  if (typeof part === "string") {
2280
- parts.push(part);
2740
+ text = part;
2281
2741
  } else if (part && typeof part === "object") {
2282
2742
  const p = part;
2283
2743
  if (typeof p.text === "string") {
2284
- parts.push(p.text);
2744
+ text = p.text;
2285
2745
  } else if (p.type === "text" && typeof p.content === "string") {
2286
- parts.push(p.content);
2746
+ text = p.content;
2287
2747
  }
2288
2748
  }
2749
+ if (text === void 0) continue;
2750
+ parts.push(text.length > limit ? text.slice(0, limit + 1) : text);
2751
+ length += Math.max(text.length, 1);
2289
2752
  }
2290
2753
  if (parts.length > 0) return parts.join("\n");
2291
- return safeStringify(content, CHAIN_TEXT_MAX_LEN);
2754
+ return boundedStringify(content, limit);
2292
2755
  }
2293
2756
  if (typeof content === "object") {
2294
2757
  const c = content;
2295
2758
  if (typeof c.text === "string") return c.text;
2296
2759
  if (typeof c.content === "string") return c.content;
2297
- return safeStringify(content, CHAIN_TEXT_MAX_LEN);
2760
+ return boundedStringify(content, limit);
2298
2761
  }
2299
2762
  return String(content);
2300
2763
  }
@@ -2313,6 +2776,11 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2313
2776
  this.eventName = opts.eventName;
2314
2777
  this.eventIdSource = opts.eventId;
2315
2778
  this.traceChains = opts.traceChains ?? true;
2779
+ this.configuredSpanMaxChars = opts.maxTextFieldChars ?? eventMaxTextFieldChars();
2780
+ this.eventMaxChars = opts.eventMaxTextFieldChars ?? eventMaxTextFieldChars();
2781
+ }
2782
+ get spanMaxChars() {
2783
+ return spanAttributeLimit(this.configuredSpanMaxChars);
2316
2784
  }
2317
2785
  getEventId(runId, parentRunId) {
2318
2786
  const rootId = parentRunId ? this.rootRunIds.get(parentRunId) ?? parentRunId : runId;
@@ -2378,13 +2846,21 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2378
2846
  if (this.spans.has(runId)) return;
2379
2847
  const eventId = this.getEventId(runId, parentRunId);
2380
2848
  const modelId = getName(llm, runName);
2849
+ const prompt = prompts[0] ?? "";
2381
2850
  const span = this.traceShipper.startSpan({
2382
2851
  name: "llm",
2383
2852
  parent: this.getParent(parentRunId),
2384
2853
  eventId,
2854
+ // `ai.generateText` classifies the span as an LLM generation at ingest
2855
+ // (a bare `llm`/`chat` operationId maps to INTERNAL and is skipped by
2856
+ // the rich-interaction extractor). `ai.prompt.messages` carries the
2857
+ // per-step trajectory so replay can see the history each step ran on.
2858
+ // A batch is a list of independent prompts; the span describes the
2859
+ // first one, whose response `handleLLMEnd` reports (`generations[0][0]`).
2385
2860
  attributes: [
2386
2861
  attrString("ai.model.provider", modelId),
2387
- attrString("ai.operationId", "llm")
2862
+ attrString("ai.operationId", "ai.generateText"),
2863
+ ...promptAttributes(capturePrompt(prompt, this.spanMaxChars))
2388
2864
  ]
2389
2865
  });
2390
2866
  this.spans.set(runId, span);
@@ -2392,7 +2868,7 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2392
2868
  userId: this.userId,
2393
2869
  convoId: this.convoId,
2394
2870
  eventName: this.eventName,
2395
- input: truncate(prompts.join("\n"), CHAIN_TEXT_MAX_LEN),
2871
+ input: capText2(prompt, this.eventMaxChars),
2396
2872
  properties: buildProperties(tags, metadata),
2397
2873
  isPending: true
2398
2874
  });
@@ -2404,24 +2880,31 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2404
2880
  if (this.spans.has(runId)) return;
2405
2881
  const eventId = this.getEventId(runId, parentRunId);
2406
2882
  const modelId = getName(llm, runName);
2883
+ const conversation = messages[0] ?? [];
2407
2884
  const span = this.traceShipper.startSpan({
2408
2885
  name: "llm",
2409
2886
  parent: this.getParent(parentRunId),
2410
2887
  eventId,
2888
+ // See handleLLMStart: the full history (historical tool calls and
2889
+ // their results included) goes on the span; the event `input` below
2890
+ // stays the latest human message. LangChain batches are a list of
2891
+ // independent conversations; the span describes the first one, the
2892
+ // same member `handleLLMEnd` reports the response of
2893
+ // (`generations[0][0]`), so history and response stay paired.
2411
2894
  attributes: [
2412
2895
  attrString("ai.model.provider", modelId),
2413
- attrString("ai.operationId", "chat")
2896
+ attrString("ai.operationId", "ai.generateText"),
2897
+ ...promptAttributes(captureConversation(conversation, this.spanMaxChars))
2414
2898
  ]
2415
2899
  });
2416
2900
  this.spans.set(runId, span);
2417
- const allMessages = messages.flat();
2418
- const userMessages = allMessages.filter((m) => {
2901
+ const userMessages = conversation.filter((m) => {
2419
2902
  const role = m._getType?.() ?? "unknown";
2420
2903
  return role === "human";
2421
2904
  });
2422
- const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : allMessages[allMessages.length - 1];
2423
- const extracted = lastMsg ? extractTextFromContent(lastMsg.content) : void 0;
2424
- const inputText = extracted !== void 0 ? truncate(extracted, CHAIN_TEXT_MAX_LEN) : void 0;
2905
+ const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : conversation[conversation.length - 1];
2906
+ const extracted = lastMsg ? extractTextFromContent(lastMsg.content, this.eventMaxChars) : void 0;
2907
+ const inputText = capText2(extracted, this.eventMaxChars);
2425
2908
  await this.eventShipper.patch(eventId, {
2426
2909
  userId: this.userId,
2427
2910
  convoId: this.convoId,
@@ -2444,7 +2927,7 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2444
2927
  await this.finalizeEventIfRoot(runId);
2445
2928
  return;
2446
2929
  }
2447
- const meta = extractLLMMetadata(output);
2930
+ const meta = extractLLMMetadata(output, this.spanMaxChars);
2448
2931
  const {
2449
2932
  model,
2450
2933
  promptTokens,
@@ -2452,11 +2935,17 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2452
2935
  cachedTokens,
2453
2936
  reasoningTokens,
2454
2937
  finishReason,
2455
- outputText
2938
+ outputText,
2939
+ toolCalls
2456
2940
  } = meta;
2457
2941
  this.traceShipper.endSpan(span, {
2458
2942
  attributes: [
2459
2943
  attrString("ai.response.model", model),
2944
+ attrString("ai.response.text", capText2(outputText, this.spanMaxChars)),
2945
+ // Tool calls requested by this step, with the ids the matching
2946
+ // `ai.toolCall` spans and the next step's history carry.
2947
+ attrString("ai.response.toolCalls", toolCalls?.json),
2948
+ attrString("ai.response.toolCalls.truncated", toolCalls?.truncated ? "true" : void 0),
2460
2949
  attrInt("ai.usage.prompt_tokens", promptTokens),
2461
2950
  attrInt("ai.usage.completion_tokens", completionTokens),
2462
2951
  attrInt("ai.usage.cached_tokens", cachedTokens),
@@ -2468,9 +2957,8 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2468
2957
  if (cachedTokens != null) properties["ai.usage.cached_tokens"] = cachedTokens;
2469
2958
  if (reasoningTokens != null) properties["ai.usage.thoughts_tokens"] = reasoningTokens;
2470
2959
  if (finishReason != null) properties["ai.finish_reason"] = finishReason;
2471
- const cappedOutput = outputText !== void 0 ? truncate(outputText, CHAIN_TEXT_MAX_LEN) : void 0;
2472
2960
  await this.finalizeEventIfRoot(runId, {
2473
- output: cappedOutput,
2961
+ output: capText2(outputText, this.eventMaxChars),
2474
2962
  model,
2475
2963
  usage: { promptTokens, completionTokens },
2476
2964
  properties: Object.keys(properties).length > 0 ? properties : void 0
@@ -2566,14 +3054,16 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2566
3054
  try {
2567
3055
  const eventId = this.getEventId(runId, parentRunId);
2568
3056
  const toolName = getName(tool, runName);
3057
+ const toolCall = parseToolCallInput(input);
2569
3058
  const span = this.traceShipper.startSpan({
2570
3059
  name: "tool",
2571
3060
  parent: this.getParent(parentRunId),
2572
3061
  eventId,
2573
3062
  operationId: "ai.toolCall",
2574
3063
  attributes: [
3064
+ attrString("ai.toolCall.id", toolCall.id),
2575
3065
  attrString("ai.toolCall.name", toolName),
2576
- attrString("tool.input", truncate(input, 4096))
3066
+ attrString("ai.toolCall.args", capText2(toolCall.args, this.spanMaxChars))
2577
3067
  ]
2578
3068
  });
2579
3069
  this.spans.set(runId, span);
@@ -2588,9 +3078,14 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2588
3078
  try {
2589
3079
  const span = this.spans.get(runId);
2590
3080
  if (span) {
2591
- const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
3081
+ const { content, toolCallId } = unwrapToolOutput(output);
3082
+ const result = typeof content === "string" ? capText2(content, this.spanMaxChars) : boundedStringify(content, this.spanMaxChars);
3083
+ const hasId = span.attributes.some((attr) => attr?.key === "ai.toolCall.id");
2592
3084
  this.traceShipper.endSpan(span, {
2593
- attributes: [attrString("tool.output", truncate(outputStr, 4096))]
3085
+ attributes: [
3086
+ attrString("ai.toolCall.id", hasId ? void 0 : toolCallId),
3087
+ attrString("ai.toolCall.result", result)
3088
+ ]
2594
3089
  });
2595
3090
  }
2596
3091
  await this.finalizeEventIfRoot(runId);
@@ -2626,10 +3121,10 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
2626
3121
  operationId: "ai.toolCall",
2627
3122
  attributes: [
2628
3123
  attrString("ai.toolCall.name", action.tool),
2629
- attrString("agent.tool_input", truncate(
2630
- typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput, 4096),
2631
- 4096
2632
- ))
3124
+ attrString(
3125
+ "agent.tool_input",
3126
+ typeof action.toolInput === "string" ? capText2(action.toolInput, this.spanMaxChars) : boundedStringify(action.toolInput, this.spanMaxChars)
3127
+ )
2633
3128
  ]
2634
3129
  });
2635
3130
  this.spans.set(`${runId}:agent_action`, span);
@@ -2662,75 +3157,13 @@ function buildProperties(tags, metadata) {
2662
3157
  if (metadata) Object.assign(props, metadata);
2663
3158
  return props;
2664
3159
  }
2665
- var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
2666
- var MAX_TEXT_FIELD_CHARS = 1e6;
2667
- var MAX_BOUNDED_DEPTH = 12;
2668
- function boundedClone2(value, budget, depth) {
2669
- if (budget.remaining <= 0) return TRUNCATION_MARKER2;
2670
- if (typeof value === "string") {
2671
- if (value.length > budget.remaining) {
2672
- const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2673
- budget.remaining = 0;
2674
- return taken;
2675
- }
2676
- budget.remaining -= Math.max(value.length, 1);
2677
- return value;
2678
- }
2679
- if (value === null || typeof value === "number" || typeof value === "boolean") {
2680
- budget.remaining -= 8;
2681
- return value;
2682
- }
2683
- if (typeof value !== "object") {
2684
- budget.remaining -= 8;
2685
- return value;
2686
- }
2687
- if (depth >= MAX_BOUNDED_DEPTH) {
2688
- budget.remaining -= 16;
2689
- return `<max depth: ${TRUNCATION_MARKER2}>`;
2690
- }
2691
- const withToJson = value;
2692
- if (typeof withToJson.toJSON === "function") {
2693
- try {
2694
- return boundedClone2(withToJson.toJSON(), budget, depth + 1);
2695
- } catch {
2696
- }
2697
- }
2698
- if (Array.isArray(value)) {
2699
- const out2 = [];
2700
- for (const item of value) {
2701
- if (budget.remaining <= 0) {
2702
- out2.push(TRUNCATION_MARKER2);
2703
- break;
2704
- }
2705
- out2.push(boundedClone2(item, budget, depth + 1));
2706
- }
2707
- return out2;
2708
- }
2709
- const out = {};
2710
- for (const key of Object.keys(value)) {
2711
- if (budget.remaining <= 0) {
2712
- out["..."] = TRUNCATION_MARKER2;
2713
- break;
2714
- }
2715
- budget.remaining -= Math.max(key.length, 1);
2716
- out[key] = boundedClone2(value[key], budget, depth + 1);
2717
- }
2718
- return out;
2719
- }
2720
- function safeStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
2721
- try {
2722
- const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
2723
- return JSON.stringify(pruned) ?? "";
2724
- } catch {
2725
- const fallback = String(value);
2726
- return fallback.length > limit ? fallback.slice(0, limit) : fallback;
2727
- }
2728
- }
2729
- function truncate(str, maxLen) {
2730
- if (str.length <= maxLen) return str;
2731
- return str.slice(0, maxLen - 3) + "...";
3160
+ function promptAttributes(capture) {
3161
+ return [
3162
+ attrString("ai.prompt.messages", capture?.json),
3163
+ attrString("ai.prompt.messages.truncated", capture?.truncated ? "true" : void 0)
3164
+ ];
2732
3165
  }
2733
- function extractLLMMetadata(output) {
3166
+ function extractLLMMetadata(output, limit) {
2734
3167
  const gen = output.generations?.[0]?.[0];
2735
3168
  const message = gen?.message;
2736
3169
  const responseMeta = message?.response_metadata;
@@ -2756,7 +3189,7 @@ function extractLLMMetadata(output) {
2756
3189
  outputDetails?.reasoning ?? usage?.reasoning_tokens ?? usage?.thoughts_tokens
2757
3190
  );
2758
3191
  const finishReason = generationInfo?.finish_reason ?? responseMeta?.finish_reason ?? responseMeta?.stop_reason;
2759
- const outputText = gen?.text ?? (typeof message?.content === "string" ? message.content : void 0) ?? (message?.content ? extractTextFromContent(message.content) : void 0);
3192
+ const outputText = gen?.text ?? (typeof message?.content === "string" ? message.content : void 0) ?? (message?.content ? extractTextFromContent(message.content, limit) : void 0);
2760
3193
  return {
2761
3194
  model,
2762
3195
  promptTokens,
@@ -2764,7 +3197,8 @@ function extractLLMMetadata(output) {
2764
3197
  cachedTokens,
2765
3198
  reasoningTokens,
2766
3199
  finishReason,
2767
- outputText
3200
+ outputText,
3201
+ toolCalls: captureToolCalls(message, limit)
2768
3202
  };
2769
3203
  }
2770
3204
  function asNumber(val) {
@@ -2776,7 +3210,7 @@ function asNumber(val) {
2776
3210
  // package.json
2777
3211
  var package_default = {
2778
3212
  name: "@raindrop-ai/deep-agents",
2779
- version: "0.1.2",
3213
+ version: "0.1.3",
2780
3214
  description: "Raindrop integration for LangChain Deep Agents",
2781
3215
  main: "dist/index.js",
2782
3216
  module: "dist/index.mjs",
@@ -2849,6 +3283,8 @@ var libraryVersion = package_default.version;
2849
3283
  function createRaindropDeepAgents(opts) {
2850
3284
  const writeKey = opts.writeKey;
2851
3285
  const enabled = !!writeKey;
3286
+ setMaxTextFieldChars(opts.maxTextFieldChars);
3287
+ const maxChars = eventMaxTextFieldChars();
2852
3288
  if (!writeKey) {
2853
3289
  console.warn(
2854
3290
  "[raindrop-ai/deep-agents] writeKey not provided; telemetry shipping is disabled"
@@ -2864,7 +3300,8 @@ function createRaindropDeepAgents(opts) {
2864
3300
  sdkName: "deep-agents",
2865
3301
  libraryName,
2866
3302
  libraryVersion,
2867
- appGitContext
3303
+ appGitContext,
3304
+ maxTextFieldChars: maxChars
2868
3305
  });
2869
3306
  const traceShipper = new TraceShipper2({
2870
3307
  writeKey,
@@ -2875,7 +3312,8 @@ function createRaindropDeepAgents(opts) {
2875
3312
  sdkName: "deep-agents",
2876
3313
  serviceName: "raindrop.deep-agents",
2877
3314
  serviceVersion: libraryVersion,
2878
- appGitContext
3315
+ appGitContext,
3316
+ maxTextFieldChars: maxChars
2879
3317
  });
2880
3318
  const handler = new RaindropDeepAgentsHandler({
2881
3319
  eventShipper,
@@ -2884,7 +3322,9 @@ function createRaindropDeepAgents(opts) {
2884
3322
  convoId: opts.convoId,
2885
3323
  eventName: opts.eventName,
2886
3324
  eventId: opts.eventId,
2887
- traceChains: opts.traceChains
3325
+ traceChains: opts.traceChains,
3326
+ maxTextFieldChars: maxChars,
3327
+ eventMaxTextFieldChars: maxChars
2888
3328
  });
2889
3329
  return {
2890
3330
  handler,