@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.mjs CHANGED
@@ -2225,6 +2225,468 @@ function installTracingSuppressionHook() {
2225
2225
  }
2226
2226
  installTracingSuppressionHook();
2227
2227
 
2228
+ // src/conversation.ts
2229
+ import {
2230
+ isAIMessage,
2231
+ isBaseMessage,
2232
+ isChatMessage,
2233
+ isToolMessage
2234
+ } from "@langchain/core/messages";
2235
+
2236
+ // src/truncation.ts
2237
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
2238
+ var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
2239
+ var MAX_DEPTH = 12;
2240
+ var configuredMaxTextFieldChars;
2241
+ function setMaxTextFieldChars(limit) {
2242
+ if (limit === void 0) return;
2243
+ if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
2244
+ configuredMaxTextFieldChars = Math.floor(limit);
2245
+ } else {
2246
+ console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
2247
+ }
2248
+ }
2249
+ function otelEnvLimit() {
2250
+ if (typeof process === "undefined") return void 0;
2251
+ const raw = process.env?.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
2252
+ if (!raw) return void 0;
2253
+ const parsed = Number.parseInt(raw, 10);
2254
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
2255
+ }
2256
+ function eventMaxTextFieldChars() {
2257
+ return configuredMaxTextFieldChars ?? DEFAULT_MAX_TEXT_FIELD_CHARS2;
2258
+ }
2259
+ function spanAttributeLimit(limit) {
2260
+ const env = otelEnvLimit();
2261
+ return env !== void 0 && env < limit ? env : limit;
2262
+ }
2263
+ function effectiveMaxTextFieldChars() {
2264
+ return spanAttributeLimit(eventMaxTextFieldChars());
2265
+ }
2266
+ function truncateToLimit2(text, limit) {
2267
+ if (limit > TRUNCATION_MARKER2.length) {
2268
+ return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
2269
+ }
2270
+ return text.slice(0, limit);
2271
+ }
2272
+ function capText2(value, limit) {
2273
+ if (typeof value !== "string") return value;
2274
+ const max = limit ?? effectiveMaxTextFieldChars();
2275
+ if (value.length <= max) return value;
2276
+ return truncateToLimit2(value, max);
2277
+ }
2278
+ function boundedClone2(value, budget, depth) {
2279
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
2280
+ if (typeof value === "string") {
2281
+ if (value.length > budget.remaining) {
2282
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2283
+ budget.remaining = 0;
2284
+ return taken;
2285
+ }
2286
+ budget.remaining -= Math.max(value.length, 1);
2287
+ return value;
2288
+ }
2289
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
2290
+ budget.remaining -= 8;
2291
+ return value;
2292
+ }
2293
+ if (typeof value === "bigint") return boundedClone2(value.toString(), budget, depth);
2294
+ if (typeof value !== "object") {
2295
+ budget.remaining -= 1;
2296
+ return value;
2297
+ }
2298
+ if (value instanceof Uint8Array) {
2299
+ const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
2300
+ if (value.byteLength > maxBytes) {
2301
+ const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
2302
+ budget.remaining = 0;
2303
+ return taken;
2304
+ }
2305
+ const encoded = base64Encode(value);
2306
+ budget.remaining -= Math.max(encoded.length, 1);
2307
+ return encoded;
2308
+ }
2309
+ if (depth >= MAX_DEPTH) {
2310
+ budget.remaining -= 16;
2311
+ const name = value.constructor?.name ?? "object";
2312
+ return `<max depth: ${name}>`;
2313
+ }
2314
+ const toJSON = value.toJSON;
2315
+ if (typeof toJSON === "function") {
2316
+ budget.remaining -= 2;
2317
+ return boundedClone2(toJSON.call(value), budget, depth + 1);
2318
+ }
2319
+ if (Array.isArray(value)) {
2320
+ budget.remaining -= 2;
2321
+ const out2 = [];
2322
+ for (const item of value) {
2323
+ if (budget.remaining <= 0) {
2324
+ out2.push(TRUNCATION_MARKER2);
2325
+ break;
2326
+ }
2327
+ out2.push(boundedClone2(item, budget, depth + 1));
2328
+ }
2329
+ return out2;
2330
+ }
2331
+ budget.remaining -= 2;
2332
+ const out = {};
2333
+ for (const key in value) {
2334
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
2335
+ if (budget.remaining <= 0) {
2336
+ out["..."] = TRUNCATION_MARKER2;
2337
+ break;
2338
+ }
2339
+ let cappedKey = key;
2340
+ if (key.length > budget.remaining) {
2341
+ cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2342
+ budget.remaining = 0;
2343
+ out[cappedKey] = TRUNCATION_MARKER2;
2344
+ break;
2345
+ }
2346
+ budget.remaining -= Math.max(key.length, 1);
2347
+ out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
2348
+ }
2349
+ return out;
2350
+ }
2351
+ function boundedStringify(value, limit) {
2352
+ const max = limit ?? effectiveMaxTextFieldChars();
2353
+ try {
2354
+ if (typeof value === "string") {
2355
+ const text2 = JSON.stringify(capText2(value, max));
2356
+ return text2.length <= max ? text2 : truncateToLimit2(text2, max);
2357
+ }
2358
+ const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
2359
+ const text = JSON.stringify(pruned);
2360
+ if (text === void 0) return void 0;
2361
+ return text.length <= max ? text : truncateToLimit2(text, max);
2362
+ } catch {
2363
+ return void 0;
2364
+ }
2365
+ }
2366
+
2367
+ // src/conversation.ts
2368
+ var CAPTURE_TRUNCATED_ROLE = "raindrop";
2369
+ var MESSAGE_OVERHEAD = 32;
2370
+ var PART_OVERHEAD = 24;
2371
+ var MAX_DEPTH2 = 12;
2372
+ function isRecord(value) {
2373
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2374
+ }
2375
+ function stringField(record, key) {
2376
+ const value = record[key];
2377
+ return typeof value === "string" ? value : void 0;
2378
+ }
2379
+ function takeText(text, budget) {
2380
+ if (text.length > budget.remaining) {
2381
+ const kept = text.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2382
+ budget.remaining = 0;
2383
+ budget.truncated = true;
2384
+ return kept;
2385
+ }
2386
+ budget.remaining -= Math.max(text.length, 1);
2387
+ return text;
2388
+ }
2389
+ function reserve(text, budget) {
2390
+ if (text !== void 0) budget.remaining -= Math.max(text.length, 1);
2391
+ return text;
2392
+ }
2393
+ function takeValue(value, budget, depth) {
2394
+ if (typeof value === "string") return takeText(value, budget);
2395
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
2396
+ budget.remaining -= 8;
2397
+ return value;
2398
+ }
2399
+ if (typeof value !== "object") {
2400
+ budget.remaining -= 1;
2401
+ return value;
2402
+ }
2403
+ if (depth >= MAX_DEPTH2) {
2404
+ budget.remaining -= TRUNCATION_MARKER2.length;
2405
+ budget.truncated = true;
2406
+ return TRUNCATION_MARKER2;
2407
+ }
2408
+ if ("toJSON" in value && typeof value.toJSON === "function") {
2409
+ return takeValue(value.toJSON(), budget, depth + 1);
2410
+ }
2411
+ if (Array.isArray(value)) {
2412
+ budget.remaining -= 2;
2413
+ const out2 = [];
2414
+ for (const item of value) {
2415
+ if (budget.remaining <= 0) {
2416
+ budget.truncated = true;
2417
+ out2.push(TRUNCATION_MARKER2);
2418
+ break;
2419
+ }
2420
+ out2.push(takeValue(item, budget, depth + 1));
2421
+ }
2422
+ return out2;
2423
+ }
2424
+ budget.remaining -= 2;
2425
+ const out = {};
2426
+ if (!isRecord(value)) return out;
2427
+ for (const key in value) {
2428
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
2429
+ if (budget.remaining <= 0) {
2430
+ budget.truncated = true;
2431
+ out["..."] = TRUNCATION_MARKER2;
2432
+ break;
2433
+ }
2434
+ budget.remaining -= Math.max(key.length, 1);
2435
+ out[key] = takeValue(value[key], budget, depth + 1);
2436
+ }
2437
+ return out;
2438
+ }
2439
+ function messageType(message) {
2440
+ return typeof message._getType === "function" ? message._getType() : void 0;
2441
+ }
2442
+ function textPart(text, budget) {
2443
+ return { type: "text", text: takeText(text, budget) };
2444
+ }
2445
+ function joinedText(content, budget) {
2446
+ const texts = [];
2447
+ for (const block of content) {
2448
+ if (budget.remaining <= 0) {
2449
+ budget.truncated = true;
2450
+ texts.push(TRUNCATION_MARKER2);
2451
+ break;
2452
+ }
2453
+ budget.remaining -= 1;
2454
+ if (!isRecord(block)) continue;
2455
+ const text = stringField(block, "text");
2456
+ if (text !== void 0) texts.push(takeText(text, budget));
2457
+ }
2458
+ return texts.join("\n");
2459
+ }
2460
+ function contentPart(block, budget) {
2461
+ budget.remaining -= PART_OVERHEAD;
2462
+ if (!isRecord(block)) return takeValue(block, budget, 0);
2463
+ const type = stringField(block, "type");
2464
+ const text = stringField(block, "text");
2465
+ if (type === "text" && text !== void 0) return textPart(text, budget);
2466
+ if (type === "image_url") {
2467
+ const imageUrl = block["image_url"];
2468
+ const url = typeof imageUrl === "string" ? imageUrl : isRecord(imageUrl) ? stringField(imageUrl, "url") : void 0;
2469
+ if (url !== void 0) return { type: "image", image: takeText(url, budget) };
2470
+ }
2471
+ if (type === "thinking") {
2472
+ const thinking = stringField(block, "thinking");
2473
+ if (thinking !== void 0) return { type: "reasoning", text: takeText(thinking, budget) };
2474
+ }
2475
+ if (type === "image" || type === "file" || type === "audio") {
2476
+ const mediaType = stringField(block, "mime_type");
2477
+ const sourceType = stringField(block, "source_type");
2478
+ const source = block["source"];
2479
+ if (sourceType === "text" && text !== void 0) return textPart(text, budget);
2480
+ const data = sourceType === "url" ? stringField(block, "url") : sourceType === "base64" ? stringField(block, "data") : isRecord(source) ? stringField(source, "url") ?? stringField(source, "data") : void 0;
2481
+ const sourceMediaType = mediaType ?? (isRecord(source) ? stringField(source, "media_type") : void 0);
2482
+ if (data !== void 0) {
2483
+ const filename = stringField(block, "filename");
2484
+ return type === "image" ? { type: "image", image: takeText(data, budget), mediaType: reserve(sourceMediaType, budget) } : {
2485
+ type: "file",
2486
+ data: takeText(data, budget),
2487
+ mediaType: reserve(sourceMediaType, budget),
2488
+ filename: reserve(filename, budget)
2489
+ };
2490
+ }
2491
+ }
2492
+ return takeValue(block, budget, 0);
2493
+ }
2494
+ function contentParts(content, budget, skipToolUseIds) {
2495
+ const parts = [];
2496
+ for (const block of content) {
2497
+ if (budget.remaining <= 0) {
2498
+ budget.truncated = true;
2499
+ parts.push({ type: "text", text: TRUNCATION_MARKER2 });
2500
+ break;
2501
+ }
2502
+ if (isRecord(block) && (block["type"] === "tool_use" || block["type"] === "tool_call")) {
2503
+ const id = stringField(block, "id");
2504
+ if (id !== void 0 && skipToolUseIds.has(id)) continue;
2505
+ }
2506
+ parts.push(contentPart(block, budget));
2507
+ }
2508
+ return parts;
2509
+ }
2510
+ function assistantMessage(message, budget, toolNames) {
2511
+ const toolCalls = isAIMessage(message) ? message.tool_calls ?? [] : [];
2512
+ if (toolCalls.length === 0 && typeof message.content === "string") {
2513
+ return { role: "assistant", content: takeText(message.content, budget) };
2514
+ }
2515
+ const ids = /* @__PURE__ */ new Set();
2516
+ const callParts = [];
2517
+ for (const call of toolCalls) {
2518
+ if (budget.remaining <= 0) {
2519
+ budget.truncated = true;
2520
+ callParts.push({ type: "text", text: TRUNCATION_MARKER2 });
2521
+ break;
2522
+ }
2523
+ budget.remaining -= PART_OVERHEAD;
2524
+ if (call.id !== void 0) {
2525
+ ids.add(call.id);
2526
+ toolNames.set(call.id, call.name);
2527
+ }
2528
+ callParts.push({
2529
+ type: "tool-call",
2530
+ toolCallId: reserve(call.id, budget),
2531
+ toolName: reserve(call.name, budget),
2532
+ input: takeValue(call.args, budget, 0)
2533
+ });
2534
+ }
2535
+ const content = typeof message.content === "string" ? message.content.length > 0 ? [textPart(message.content, budget)] : [] : contentParts(message.content, budget, ids);
2536
+ content.push(...callParts);
2537
+ return { role: "assistant", content };
2538
+ }
2539
+ function toolResultOutput(message, budget) {
2540
+ const failed = isToolMessage(message) && message.status === "error";
2541
+ if (typeof message.content === "string") {
2542
+ return { type: failed ? "error-text" : "text", value: takeText(message.content, budget) };
2543
+ }
2544
+ return { type: "content", value: contentParts(message.content, budget, /* @__PURE__ */ new Set()) };
2545
+ }
2546
+ function toolMessage(message, budget, toolNames) {
2547
+ const toolCallId = isToolMessage(message) ? message.tool_call_id : void 0;
2548
+ const toolName = message.name ?? (toolCallId !== void 0 ? toolNames.get(toolCallId) : void 0);
2549
+ budget.remaining -= PART_OVERHEAD;
2550
+ return {
2551
+ role: "tool",
2552
+ content: [{
2553
+ type: "tool-result",
2554
+ toolCallId: reserve(toolCallId, budget),
2555
+ toolName: reserve(toolName, budget),
2556
+ output: toolResultOutput(message, budget)
2557
+ }]
2558
+ };
2559
+ }
2560
+ function modelMessage(message, budget, toolNames) {
2561
+ budget.remaining -= MESSAGE_OVERHEAD;
2562
+ switch (messageType(message)) {
2563
+ case "system":
2564
+ return {
2565
+ role: "system",
2566
+ content: typeof message.content === "string" ? takeText(message.content, budget) : joinedText(message.content, budget)
2567
+ };
2568
+ case "ai":
2569
+ return assistantMessage(message, budget, toolNames);
2570
+ case "tool":
2571
+ case "function":
2572
+ return toolMessage(message, budget, toolNames);
2573
+ case "generic": {
2574
+ const role = isChatMessage(message) ? message.role : void 0;
2575
+ if (role === "assistant" || role === "system") {
2576
+ return {
2577
+ role,
2578
+ content: typeof message.content === "string" ? takeText(message.content, budget) : joinedText(message.content, budget)
2579
+ };
2580
+ }
2581
+ return userMessage(message, budget);
2582
+ }
2583
+ default:
2584
+ return userMessage(message, budget);
2585
+ }
2586
+ }
2587
+ function userMessage(message, budget) {
2588
+ return {
2589
+ role: "user",
2590
+ content: typeof message.content === "string" ? takeText(message.content, budget) : contentParts(message.content, budget, /* @__PURE__ */ new Set())
2591
+ };
2592
+ }
2593
+ function truncationRecord(omittedMessages) {
2594
+ return { role: CAPTURE_TRUNCATED_ROLE, truncated: true, marker: TRUNCATION_MARKER2, omittedMessages };
2595
+ }
2596
+ function encodeWithinLimit(items, limit, overflow, convert) {
2597
+ let textBudget = limit;
2598
+ for (let attempt = 0; attempt < 8 && textBudget >= 64; attempt++) {
2599
+ const budget = { remaining: textBudget, truncated: false };
2600
+ const out = [];
2601
+ let omitted = 0;
2602
+ for (let i = 0; i < items.length; i++) {
2603
+ if (overflow === "omit" && budget.remaining <= 0) {
2604
+ omitted = items.length - i;
2605
+ break;
2606
+ }
2607
+ out.push(convert(items[i], budget));
2608
+ }
2609
+ const truncated = budget.truncated || omitted > 0;
2610
+ if (truncated && overflow === "omit") out.push(truncationRecord(omitted));
2611
+ const json2 = JSON.stringify(out);
2612
+ if (json2.length <= limit) return { json: json2, truncated };
2613
+ const excess = json2.length - limit + 128;
2614
+ textBudget = Math.max(textBudget - excess, Math.floor(textBudget / 2));
2615
+ }
2616
+ if (overflow === "keep") return void 0;
2617
+ const json = JSON.stringify([truncationRecord(items.length)]);
2618
+ return json.length <= limit ? { json, truncated: true } : void 0;
2619
+ }
2620
+ function captureConversation(messages, limit) {
2621
+ if (messages.length === 0) return void 0;
2622
+ try {
2623
+ const toolNames = /* @__PURE__ */ new Map();
2624
+ return encodeWithinLimit(
2625
+ messages,
2626
+ limit,
2627
+ "omit",
2628
+ (message, budget) => modelMessage(message, budget, toolNames)
2629
+ );
2630
+ } catch {
2631
+ return void 0;
2632
+ }
2633
+ }
2634
+ function capturePrompt(prompt, limit) {
2635
+ try {
2636
+ return encodeWithinLimit([prompt], limit, "omit", (text, budget) => {
2637
+ budget.remaining -= MESSAGE_OVERHEAD;
2638
+ return { role: "user", content: takeText(text, budget) };
2639
+ });
2640
+ } catch {
2641
+ return void 0;
2642
+ }
2643
+ }
2644
+ function captureToolCalls(message, limit) {
2645
+ if (!isRecord(message) || !Array.isArray(message["tool_calls"])) return void 0;
2646
+ const calls = [];
2647
+ let structural = 0;
2648
+ for (const call of message["tool_calls"]) {
2649
+ structural += PART_OVERHEAD;
2650
+ if (isRecord(call)) {
2651
+ structural += (stringField(call, "id")?.length ?? 0) + (stringField(call, "name")?.length ?? 0);
2652
+ calls.push(call);
2653
+ }
2654
+ if (structural > limit) return void 0;
2655
+ }
2656
+ if (calls.length === 0) return void 0;
2657
+ try {
2658
+ return encodeWithinLimit(calls, limit, "keep", (call, budget) => {
2659
+ budget.remaining -= PART_OVERHEAD;
2660
+ return {
2661
+ toolCallId: reserve(stringField(call, "id"), budget),
2662
+ toolName: reserve(stringField(call, "name"), budget),
2663
+ input: takeValue(call["args"], budget, 0)
2664
+ };
2665
+ });
2666
+ } catch {
2667
+ return void 0;
2668
+ }
2669
+ }
2670
+ function parseToolCallInput(input) {
2671
+ try {
2672
+ const parsed = JSON.parse(input);
2673
+ if (isRecord(parsed) && parsed["type"] === "tool_call" && "args" in parsed) {
2674
+ return { id: stringField(parsed, "id"), args: JSON.stringify(parsed["args"]) };
2675
+ }
2676
+ } catch {
2677
+ }
2678
+ return { args: input };
2679
+ }
2680
+ function unwrapToolOutput(output) {
2681
+ if (isBaseMessage(output)) {
2682
+ return {
2683
+ content: output.content,
2684
+ toolCallId: isToolMessage(output) ? output.tool_call_id : void 0
2685
+ };
2686
+ }
2687
+ return { content: output };
2688
+ }
2689
+
2228
2690
  // src/callback-handler.ts
2229
2691
  function resolveEventId(source) {
2230
2692
  if (typeof source !== "function") return randomUUID();
@@ -2242,32 +2704,38 @@ function isInternalName(name) {
2242
2704
  if (name.startsWith("__") && name.endsWith("__")) return true;
2243
2705
  return false;
2244
2706
  }
2245
- var CHAIN_TEXT_MAX_LEN = 8192;
2246
- function extractTextFromContent(content) {
2707
+ function extractTextFromContent(content, limit) {
2247
2708
  if (content == null) return void 0;
2248
2709
  if (typeof content === "string") return content;
2249
2710
  if (Array.isArray(content)) {
2250
2711
  const parts = [];
2712
+ let length = 0;
2251
2713
  for (const part of content) {
2714
+ if (length > limit) break;
2715
+ length += 1;
2716
+ let text;
2252
2717
  if (typeof part === "string") {
2253
- parts.push(part);
2718
+ text = part;
2254
2719
  } else if (part && typeof part === "object") {
2255
2720
  const p = part;
2256
2721
  if (typeof p.text === "string") {
2257
- parts.push(p.text);
2722
+ text = p.text;
2258
2723
  } else if (p.type === "text" && typeof p.content === "string") {
2259
- parts.push(p.content);
2724
+ text = p.content;
2260
2725
  }
2261
2726
  }
2727
+ if (text === void 0) continue;
2728
+ parts.push(text.length > limit ? text.slice(0, limit + 1) : text);
2729
+ length += Math.max(text.length, 1);
2262
2730
  }
2263
2731
  if (parts.length > 0) return parts.join("\n");
2264
- return safeStringify(content, CHAIN_TEXT_MAX_LEN);
2732
+ return boundedStringify(content, limit);
2265
2733
  }
2266
2734
  if (typeof content === "object") {
2267
2735
  const c = content;
2268
2736
  if (typeof c.text === "string") return c.text;
2269
2737
  if (typeof c.content === "string") return c.content;
2270
- return safeStringify(content, CHAIN_TEXT_MAX_LEN);
2738
+ return boundedStringify(content, limit);
2271
2739
  }
2272
2740
  return String(content);
2273
2741
  }
@@ -2286,6 +2754,11 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2286
2754
  this.eventName = opts.eventName;
2287
2755
  this.eventIdSource = opts.eventId;
2288
2756
  this.traceChains = opts.traceChains ?? true;
2757
+ this.configuredSpanMaxChars = opts.maxTextFieldChars ?? eventMaxTextFieldChars();
2758
+ this.eventMaxChars = opts.eventMaxTextFieldChars ?? eventMaxTextFieldChars();
2759
+ }
2760
+ get spanMaxChars() {
2761
+ return spanAttributeLimit(this.configuredSpanMaxChars);
2289
2762
  }
2290
2763
  getEventId(runId, parentRunId) {
2291
2764
  const rootId = parentRunId ? this.rootRunIds.get(parentRunId) ?? parentRunId : runId;
@@ -2351,13 +2824,21 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2351
2824
  if (this.spans.has(runId)) return;
2352
2825
  const eventId = this.getEventId(runId, parentRunId);
2353
2826
  const modelId = getName(llm, runName);
2827
+ const prompt = prompts[0] ?? "";
2354
2828
  const span = this.traceShipper.startSpan({
2355
2829
  name: "llm",
2356
2830
  parent: this.getParent(parentRunId),
2357
2831
  eventId,
2832
+ // `ai.generateText` classifies the span as an LLM generation at ingest
2833
+ // (a bare `llm`/`chat` operationId maps to INTERNAL and is skipped by
2834
+ // the rich-interaction extractor). `ai.prompt.messages` carries the
2835
+ // per-step trajectory so replay can see the history each step ran on.
2836
+ // A batch is a list of independent prompts; the span describes the
2837
+ // first one, whose response `handleLLMEnd` reports (`generations[0][0]`).
2358
2838
  attributes: [
2359
2839
  attrString("ai.model.provider", modelId),
2360
- attrString("ai.operationId", "llm")
2840
+ attrString("ai.operationId", "ai.generateText"),
2841
+ ...promptAttributes(capturePrompt(prompt, this.spanMaxChars))
2361
2842
  ]
2362
2843
  });
2363
2844
  this.spans.set(runId, span);
@@ -2365,7 +2846,7 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2365
2846
  userId: this.userId,
2366
2847
  convoId: this.convoId,
2367
2848
  eventName: this.eventName,
2368
- input: truncate(prompts.join("\n"), CHAIN_TEXT_MAX_LEN),
2849
+ input: capText2(prompt, this.eventMaxChars),
2369
2850
  properties: buildProperties(tags, metadata),
2370
2851
  isPending: true
2371
2852
  });
@@ -2377,24 +2858,31 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2377
2858
  if (this.spans.has(runId)) return;
2378
2859
  const eventId = this.getEventId(runId, parentRunId);
2379
2860
  const modelId = getName(llm, runName);
2861
+ const conversation = messages[0] ?? [];
2380
2862
  const span = this.traceShipper.startSpan({
2381
2863
  name: "llm",
2382
2864
  parent: this.getParent(parentRunId),
2383
2865
  eventId,
2866
+ // See handleLLMStart: the full history (historical tool calls and
2867
+ // their results included) goes on the span; the event `input` below
2868
+ // stays the latest human message. LangChain batches are a list of
2869
+ // independent conversations; the span describes the first one, the
2870
+ // same member `handleLLMEnd` reports the response of
2871
+ // (`generations[0][0]`), so history and response stay paired.
2384
2872
  attributes: [
2385
2873
  attrString("ai.model.provider", modelId),
2386
- attrString("ai.operationId", "chat")
2874
+ attrString("ai.operationId", "ai.generateText"),
2875
+ ...promptAttributes(captureConversation(conversation, this.spanMaxChars))
2387
2876
  ]
2388
2877
  });
2389
2878
  this.spans.set(runId, span);
2390
- const allMessages = messages.flat();
2391
- const userMessages = allMessages.filter((m) => {
2879
+ const userMessages = conversation.filter((m) => {
2392
2880
  const role = m._getType?.() ?? "unknown";
2393
2881
  return role === "human";
2394
2882
  });
2395
- const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : allMessages[allMessages.length - 1];
2396
- const extracted = lastMsg ? extractTextFromContent(lastMsg.content) : void 0;
2397
- const inputText = extracted !== void 0 ? truncate(extracted, CHAIN_TEXT_MAX_LEN) : void 0;
2883
+ const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : conversation[conversation.length - 1];
2884
+ const extracted = lastMsg ? extractTextFromContent(lastMsg.content, this.eventMaxChars) : void 0;
2885
+ const inputText = capText2(extracted, this.eventMaxChars);
2398
2886
  await this.eventShipper.patch(eventId, {
2399
2887
  userId: this.userId,
2400
2888
  convoId: this.convoId,
@@ -2417,7 +2905,7 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2417
2905
  await this.finalizeEventIfRoot(runId);
2418
2906
  return;
2419
2907
  }
2420
- const meta = extractLLMMetadata(output);
2908
+ const meta = extractLLMMetadata(output, this.spanMaxChars);
2421
2909
  const {
2422
2910
  model,
2423
2911
  promptTokens,
@@ -2425,11 +2913,17 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2425
2913
  cachedTokens,
2426
2914
  reasoningTokens,
2427
2915
  finishReason,
2428
- outputText
2916
+ outputText,
2917
+ toolCalls
2429
2918
  } = meta;
2430
2919
  this.traceShipper.endSpan(span, {
2431
2920
  attributes: [
2432
2921
  attrString("ai.response.model", model),
2922
+ attrString("ai.response.text", capText2(outputText, this.spanMaxChars)),
2923
+ // Tool calls requested by this step, with the ids the matching
2924
+ // `ai.toolCall` spans and the next step's history carry.
2925
+ attrString("ai.response.toolCalls", toolCalls?.json),
2926
+ attrString("ai.response.toolCalls.truncated", toolCalls?.truncated ? "true" : void 0),
2433
2927
  attrInt("ai.usage.prompt_tokens", promptTokens),
2434
2928
  attrInt("ai.usage.completion_tokens", completionTokens),
2435
2929
  attrInt("ai.usage.cached_tokens", cachedTokens),
@@ -2441,9 +2935,8 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2441
2935
  if (cachedTokens != null) properties["ai.usage.cached_tokens"] = cachedTokens;
2442
2936
  if (reasoningTokens != null) properties["ai.usage.thoughts_tokens"] = reasoningTokens;
2443
2937
  if (finishReason != null) properties["ai.finish_reason"] = finishReason;
2444
- const cappedOutput = outputText !== void 0 ? truncate(outputText, CHAIN_TEXT_MAX_LEN) : void 0;
2445
2938
  await this.finalizeEventIfRoot(runId, {
2446
- output: cappedOutput,
2939
+ output: capText2(outputText, this.eventMaxChars),
2447
2940
  model,
2448
2941
  usage: { promptTokens, completionTokens },
2449
2942
  properties: Object.keys(properties).length > 0 ? properties : void 0
@@ -2539,14 +3032,16 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2539
3032
  try {
2540
3033
  const eventId = this.getEventId(runId, parentRunId);
2541
3034
  const toolName = getName(tool, runName);
3035
+ const toolCall = parseToolCallInput(input);
2542
3036
  const span = this.traceShipper.startSpan({
2543
3037
  name: "tool",
2544
3038
  parent: this.getParent(parentRunId),
2545
3039
  eventId,
2546
3040
  operationId: "ai.toolCall",
2547
3041
  attributes: [
3042
+ attrString("ai.toolCall.id", toolCall.id),
2548
3043
  attrString("ai.toolCall.name", toolName),
2549
- attrString("tool.input", truncate(input, 4096))
3044
+ attrString("ai.toolCall.args", capText2(toolCall.args, this.spanMaxChars))
2550
3045
  ]
2551
3046
  });
2552
3047
  this.spans.set(runId, span);
@@ -2561,9 +3056,14 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2561
3056
  try {
2562
3057
  const span = this.spans.get(runId);
2563
3058
  if (span) {
2564
- const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
3059
+ const { content, toolCallId } = unwrapToolOutput(output);
3060
+ const result = typeof content === "string" ? capText2(content, this.spanMaxChars) : boundedStringify(content, this.spanMaxChars);
3061
+ const hasId = span.attributes.some((attr) => attr?.key === "ai.toolCall.id");
2565
3062
  this.traceShipper.endSpan(span, {
2566
- attributes: [attrString("tool.output", truncate(outputStr, 4096))]
3063
+ attributes: [
3064
+ attrString("ai.toolCall.id", hasId ? void 0 : toolCallId),
3065
+ attrString("ai.toolCall.result", result)
3066
+ ]
2567
3067
  });
2568
3068
  }
2569
3069
  await this.finalizeEventIfRoot(runId);
@@ -2599,10 +3099,10 @@ var RaindropDeepAgentsHandler = class extends BaseCallbackHandler {
2599
3099
  operationId: "ai.toolCall",
2600
3100
  attributes: [
2601
3101
  attrString("ai.toolCall.name", action.tool),
2602
- attrString("agent.tool_input", truncate(
2603
- typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput, 4096),
2604
- 4096
2605
- ))
3102
+ attrString(
3103
+ "agent.tool_input",
3104
+ typeof action.toolInput === "string" ? capText2(action.toolInput, this.spanMaxChars) : boundedStringify(action.toolInput, this.spanMaxChars)
3105
+ )
2606
3106
  ]
2607
3107
  });
2608
3108
  this.spans.set(`${runId}:agent_action`, span);
@@ -2635,75 +3135,13 @@ function buildProperties(tags, metadata) {
2635
3135
  if (metadata) Object.assign(props, metadata);
2636
3136
  return props;
2637
3137
  }
2638
- var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
2639
- var MAX_TEXT_FIELD_CHARS = 1e6;
2640
- var MAX_BOUNDED_DEPTH = 12;
2641
- function boundedClone2(value, budget, depth) {
2642
- if (budget.remaining <= 0) return TRUNCATION_MARKER2;
2643
- if (typeof value === "string") {
2644
- if (value.length > budget.remaining) {
2645
- const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
2646
- budget.remaining = 0;
2647
- return taken;
2648
- }
2649
- budget.remaining -= Math.max(value.length, 1);
2650
- return value;
2651
- }
2652
- if (value === null || typeof value === "number" || typeof value === "boolean") {
2653
- budget.remaining -= 8;
2654
- return value;
2655
- }
2656
- if (typeof value !== "object") {
2657
- budget.remaining -= 8;
2658
- return value;
2659
- }
2660
- if (depth >= MAX_BOUNDED_DEPTH) {
2661
- budget.remaining -= 16;
2662
- return `<max depth: ${TRUNCATION_MARKER2}>`;
2663
- }
2664
- const withToJson = value;
2665
- if (typeof withToJson.toJSON === "function") {
2666
- try {
2667
- return boundedClone2(withToJson.toJSON(), budget, depth + 1);
2668
- } catch {
2669
- }
2670
- }
2671
- if (Array.isArray(value)) {
2672
- const out2 = [];
2673
- for (const item of value) {
2674
- if (budget.remaining <= 0) {
2675
- out2.push(TRUNCATION_MARKER2);
2676
- break;
2677
- }
2678
- out2.push(boundedClone2(item, budget, depth + 1));
2679
- }
2680
- return out2;
2681
- }
2682
- const out = {};
2683
- for (const key of Object.keys(value)) {
2684
- if (budget.remaining <= 0) {
2685
- out["..."] = TRUNCATION_MARKER2;
2686
- break;
2687
- }
2688
- budget.remaining -= Math.max(key.length, 1);
2689
- out[key] = boundedClone2(value[key], budget, depth + 1);
2690
- }
2691
- return out;
2692
- }
2693
- function safeStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
2694
- try {
2695
- const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
2696
- return JSON.stringify(pruned) ?? "";
2697
- } catch {
2698
- const fallback = String(value);
2699
- return fallback.length > limit ? fallback.slice(0, limit) : fallback;
2700
- }
2701
- }
2702
- function truncate(str, maxLen) {
2703
- if (str.length <= maxLen) return str;
2704
- return str.slice(0, maxLen - 3) + "...";
3138
+ function promptAttributes(capture) {
3139
+ return [
3140
+ attrString("ai.prompt.messages", capture?.json),
3141
+ attrString("ai.prompt.messages.truncated", capture?.truncated ? "true" : void 0)
3142
+ ];
2705
3143
  }
2706
- function extractLLMMetadata(output) {
3144
+ function extractLLMMetadata(output, limit) {
2707
3145
  const gen = output.generations?.[0]?.[0];
2708
3146
  const message = gen?.message;
2709
3147
  const responseMeta = message?.response_metadata;
@@ -2729,7 +3167,7 @@ function extractLLMMetadata(output) {
2729
3167
  outputDetails?.reasoning ?? usage?.reasoning_tokens ?? usage?.thoughts_tokens
2730
3168
  );
2731
3169
  const finishReason = generationInfo?.finish_reason ?? responseMeta?.finish_reason ?? responseMeta?.stop_reason;
2732
- const outputText = gen?.text ?? (typeof message?.content === "string" ? message.content : void 0) ?? (message?.content ? extractTextFromContent(message.content) : void 0);
3170
+ const outputText = gen?.text ?? (typeof message?.content === "string" ? message.content : void 0) ?? (message?.content ? extractTextFromContent(message.content, limit) : void 0);
2733
3171
  return {
2734
3172
  model,
2735
3173
  promptTokens,
@@ -2737,7 +3175,8 @@ function extractLLMMetadata(output) {
2737
3175
  cachedTokens,
2738
3176
  reasoningTokens,
2739
3177
  finishReason,
2740
- outputText
3178
+ outputText,
3179
+ toolCalls: captureToolCalls(message, limit)
2741
3180
  };
2742
3181
  }
2743
3182
  function asNumber(val) {
@@ -2749,7 +3188,7 @@ function asNumber(val) {
2749
3188
  // package.json
2750
3189
  var package_default = {
2751
3190
  name: "@raindrop-ai/deep-agents",
2752
- version: "0.1.2",
3191
+ version: "0.1.3",
2753
3192
  description: "Raindrop integration for LangChain Deep Agents",
2754
3193
  main: "dist/index.js",
2755
3194
  module: "dist/index.mjs",
@@ -2822,6 +3261,8 @@ var libraryVersion = package_default.version;
2822
3261
  function createRaindropDeepAgents(opts) {
2823
3262
  const writeKey = opts.writeKey;
2824
3263
  const enabled = !!writeKey;
3264
+ setMaxTextFieldChars(opts.maxTextFieldChars);
3265
+ const maxChars = eventMaxTextFieldChars();
2825
3266
  if (!writeKey) {
2826
3267
  console.warn(
2827
3268
  "[raindrop-ai/deep-agents] writeKey not provided; telemetry shipping is disabled"
@@ -2837,7 +3278,8 @@ function createRaindropDeepAgents(opts) {
2837
3278
  sdkName: "deep-agents",
2838
3279
  libraryName,
2839
3280
  libraryVersion,
2840
- appGitContext
3281
+ appGitContext,
3282
+ maxTextFieldChars: maxChars
2841
3283
  });
2842
3284
  const traceShipper = new TraceShipper2({
2843
3285
  writeKey,
@@ -2848,7 +3290,8 @@ function createRaindropDeepAgents(opts) {
2848
3290
  sdkName: "deep-agents",
2849
3291
  serviceName: "raindrop.deep-agents",
2850
3292
  serviceVersion: libraryVersion,
2851
- appGitContext
3293
+ appGitContext,
3294
+ maxTextFieldChars: maxChars
2852
3295
  });
2853
3296
  const handler = new RaindropDeepAgentsHandler({
2854
3297
  eventShipper,
@@ -2857,7 +3300,9 @@ function createRaindropDeepAgents(opts) {
2857
3300
  convoId: opts.convoId,
2858
3301
  eventName: opts.eventName,
2859
3302
  eventId: opts.eventId,
2860
- traceChains: opts.traceChains
3303
+ traceChains: opts.traceChains,
3304
+ maxTextFieldChars: maxChars,
3305
+ eventMaxTextFieldChars: maxChars
2861
3306
  });
2862
3307
  return {
2863
3308
  handler,