@raindrop-ai/ai-sdk 0.0.29 → 0.0.30

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.
@@ -1714,13 +1714,76 @@ function readRaindropCallMetadataFromArgs(args) {
1714
1714
  return meta;
1715
1715
  }
1716
1716
 
1717
+ // src/internal/parent-tool-context.ts
1718
+ var SyncFallbackStorage2 = class {
1719
+ constructor() {
1720
+ this._stack = [];
1721
+ }
1722
+ getStore() {
1723
+ return this._stack[this._stack.length - 1];
1724
+ }
1725
+ run(store, callback) {
1726
+ this._stack.push(store);
1727
+ try {
1728
+ return callback();
1729
+ } finally {
1730
+ this._stack.pop();
1731
+ }
1732
+ }
1733
+ // `enterWith` is intentionally absent on the sync fallback — the
1734
+ // event-driven enter/clear pattern only makes sense when the runtime
1735
+ // can propagate state across awaits, which the sync stack cannot.
1736
+ };
1737
+ var _storage2 = null;
1738
+ function getStorage2() {
1739
+ if (_storage2) return _storage2;
1740
+ const Ctor = globalThis.RAINDROP_ASYNC_LOCAL_STORAGE;
1741
+ _storage2 = Ctor ? new Ctor() : new SyncFallbackStorage2();
1742
+ return _storage2;
1743
+ }
1744
+ function _resetParentToolContextStorage() {
1745
+ _storage2 = null;
1746
+ }
1747
+ function getCurrentParentToolContext() {
1748
+ return getStorage2().getStore();
1749
+ }
1750
+ function enterParentToolContext(ctx) {
1751
+ var _a;
1752
+ const storage = getStorage2();
1753
+ (_a = storage.enterWith) == null ? void 0 : _a.call(storage, ctx);
1754
+ }
1755
+ function clearParentToolContext() {
1756
+ var _a;
1757
+ const storage = getStorage2();
1758
+ (_a = storage.enterWith) == null ? void 0 : _a.call(storage, void 0);
1759
+ }
1760
+ function runWithParentToolContext(ctx, fn) {
1761
+ return getStorage2().run(ctx, fn);
1762
+ }
1763
+
1717
1764
  // src/internal/raindrop-telemetry-integration.ts
1718
1765
  var RaindropTelemetryIntegration = class {
1719
1766
  constructor(opts) {
1720
1767
  this.callStates = /* @__PURE__ */ new Map();
1768
+ /**
1769
+ * Per-tool-call snapshot of the parent-tool ALS context taken right
1770
+ * before `enterParentToolContext` overwrites it in `toolExecutionStart`.
1771
+ * Kept at the integration level (rather than on `CallState`) so the
1772
+ * snapshot survives even when the parent generation has no tracked
1773
+ * `CallState` — e.g. the AI SDK dispatches a tool callback for a
1774
+ * `callId` we never registered via `onStart`. Without this, the
1775
+ * unconditional ALS enter in `toolExecutionStart` would leave
1776
+ * `toolExecutionEnd` no way to restore the prior context, so it would
1777
+ * clear and wipe whatever the outer scope had set.
1778
+ *
1779
+ * Keyed by `toolCallId` because that's the only identifier guaranteed
1780
+ * to round-trip between `toolExecutionStart` and `toolExecutionEnd`
1781
+ * (the `event.callId` can be the same for parallel sibling tools).
1782
+ */
1783
+ this.priorParentContexts = /* @__PURE__ */ new Map();
1721
1784
  // ── onStart ─────────────────────────────────────────────────────────────
1722
1785
  this.onStart = (event) => {
1723
- var _a, _b, _c, _d;
1786
+ var _a, _b, _c, _d, _e;
1724
1787
  if (event.isEnabled === false) return;
1725
1788
  const isEmbed = event.operationId === "ai.embed" || event.operationId === "ai.embedMany";
1726
1789
  const recordInputs = event.recordInputs !== false;
@@ -1739,6 +1802,47 @@ var RaindropTelemetryIntegration = class {
1739
1802
  event.operationId,
1740
1803
  functionId
1741
1804
  );
1805
+ const parentToolContext = !isEmbed && this.subagentWrapping ? getCurrentParentToolContext() : void 0;
1806
+ const metadataSubagentName = !isEmbed && this.subagentWrapping && parentToolContext === void 0 ? typeof (metadata == null ? void 0 : metadata["ash.subagent.name"]) === "string" ? metadata["ash.subagent.name"] : void 0 : void 0;
1807
+ const subagentName = (_e = parentToolContext == null ? void 0 : parentToolContext.toolName) != null ? _e : metadataSubagentName;
1808
+ let subagentToolCallSpan;
1809
+ let rootParentOverride;
1810
+ if (subagentName && this.sendTraces) {
1811
+ const parentAttrs = parentToolContext ? [
1812
+ attrString("raindrop.parent.callId", parentToolContext.parentCallId),
1813
+ attrString("raindrop.parent.toolCallId", parentToolContext.toolCallId),
1814
+ attrString("raindrop.parent.toolName", parentToolContext.toolName)
1815
+ ] : [];
1816
+ subagentToolCallSpan = this.traceShipper.startSpan({
1817
+ name: subagentName,
1818
+ parent: inheritedParent,
1819
+ eventId,
1820
+ operationId: "ai.toolCall",
1821
+ attributes: [
1822
+ attrString("operation.name", "ai.toolCall"),
1823
+ attrString("resource.name", subagentName),
1824
+ attrString("raindrop.span.kind", "tool_call"),
1825
+ attrString("ai.toolCall.name", subagentName),
1826
+ attrString("raindrop.subagent.name", subagentName),
1827
+ attrString("raindrop.agent.role", "subagent"),
1828
+ ...parentAttrs
1829
+ ]
1830
+ });
1831
+ const subagentParentRef = this.spanParentRef(subagentToolCallSpan);
1832
+ const markerSpan = this.traceShipper.startSpan({
1833
+ name: "agent.subagent",
1834
+ parent: subagentParentRef,
1835
+ eventId,
1836
+ operationId: "agent.subagent",
1837
+ attributes: [
1838
+ attrString("operation.name", "agent.subagent"),
1839
+ attrString("raindrop.span.kind", "llm_call"),
1840
+ attrString("raindrop.subagent.name", subagentName)
1841
+ ]
1842
+ });
1843
+ this.traceShipper.endSpan(markerSpan);
1844
+ rootParentOverride = subagentParentRef;
1845
+ }
1742
1846
  let rootSpan;
1743
1847
  if (this.sendTraces) {
1744
1848
  const promptAttrs = !isEmbed && recordInputs ? [
@@ -1759,7 +1863,7 @@ var RaindropTelemetryIntegration = class {
1759
1863
  ] : [attrString("ai.value", safeJsonWithUint8(event.value))] : [];
1760
1864
  rootSpan = this.traceShipper.startSpan({
1761
1865
  name: event.operationId,
1762
- parent: inheritedParent,
1866
+ parent: rootParentOverride != null ? rootParentOverride : inheritedParent,
1763
1867
  eventId,
1764
1868
  operationId: event.operationId,
1765
1869
  attributes: [
@@ -1787,18 +1891,21 @@ var RaindropTelemetryIntegration = class {
1787
1891
  operationId: event.operationId,
1788
1892
  eventId,
1789
1893
  rootSpan,
1790
- rootParent: rootSpan ? this.spanParentRef(rootSpan) : inheritedParent,
1894
+ rootParent: rootSpan ? this.spanParentRef(rootSpan) : rootParentOverride != null ? rootParentOverride : inheritedParent,
1791
1895
  stepSpan: void 0,
1792
1896
  stepParent: void 0,
1793
1897
  toolSpans: /* @__PURE__ */ new Map(),
1794
1898
  embedSpans: /* @__PURE__ */ new Map(),
1899
+ parentContextToolCallIds: /* @__PURE__ */ new Set(),
1795
1900
  recordInputs,
1796
1901
  recordOutputs,
1797
1902
  functionId,
1798
1903
  metadata,
1799
1904
  accumulatedText: "",
1800
1905
  inputText: isEmbed ? void 0 : this.extractInputText(event),
1801
- toolCallCount: 0
1906
+ toolCallCount: 0,
1907
+ subagentName,
1908
+ subagentToolCallSpan
1802
1909
  });
1803
1910
  };
1804
1911
  // ── onStepStart ─────────────────────────────────────────────────────────
@@ -2022,6 +2129,9 @@ var RaindropTelemetryIntegration = class {
2022
2129
  } else {
2023
2130
  this.finishGenerate(event, state);
2024
2131
  }
2132
+ if (state.subagentToolCallSpan) {
2133
+ this.traceShipper.endSpan(state.subagentToolCallSpan);
2134
+ }
2025
2135
  this.cleanup(event.callId);
2026
2136
  };
2027
2137
  // ── onError ─────────────────────────────────────────────────────────────
@@ -2039,6 +2149,10 @@ var RaindropTelemetryIntegration = class {
2039
2149
  this.traceShipper.endSpan(embedSpan, { error: actualError });
2040
2150
  }
2041
2151
  state.embedSpans.clear();
2152
+ for (const toolCallId of state.parentContextToolCallIds) {
2153
+ this.priorParentContexts.delete(toolCallId);
2154
+ }
2155
+ state.parentContextToolCallIds.clear();
2042
2156
  for (const toolSpan of state.toolSpans.values()) {
2043
2157
  this.traceShipper.endSpan(toolSpan, { error: actualError });
2044
2158
  }
@@ -2046,6 +2160,9 @@ var RaindropTelemetryIntegration = class {
2046
2160
  if (state.rootSpan) {
2047
2161
  this.traceShipper.endSpan(state.rootSpan, { error: actualError });
2048
2162
  }
2163
+ if (state.subagentToolCallSpan) {
2164
+ this.traceShipper.endSpan(state.subagentToolCallSpan, { error: actualError });
2165
+ }
2049
2166
  this.cleanup(event.callId);
2050
2167
  };
2051
2168
  // ── executeTool ─────────────────────────────────────────────────────────
@@ -2070,6 +2187,7 @@ var RaindropTelemetryIntegration = class {
2070
2187
  this.eventShipper = opts.eventShipper;
2071
2188
  this.sendTraces = opts.sendTraces !== false;
2072
2189
  this.sendEvents = opts.sendEvents !== false;
2190
+ this.subagentWrapping = opts.subagentWrapping !== false;
2073
2191
  this.debug = opts.debug === true;
2074
2192
  this.defaultContext = opts.context;
2075
2193
  }
@@ -2151,9 +2269,20 @@ var RaindropTelemetryIntegration = class {
2151
2269
  // (see https://github.com/vercel/ai/pull/14589). Event shape is identical.
2152
2270
  // Both names are exposed and forward to a single implementation.
2153
2271
  toolExecutionStart(event) {
2272
+ const { toolCall } = event;
2273
+ const priorParent = getCurrentParentToolContext();
2274
+ this.priorParentContexts.set(
2275
+ toolCall.toolCallId,
2276
+ priorParent != null ? priorParent : null
2277
+ );
2278
+ enterParentToolContext({
2279
+ parentCallId: event.callId,
2280
+ toolCallId: toolCall.toolCallId,
2281
+ toolName: toolCall.toolName
2282
+ });
2154
2283
  const state = this.getState(event.callId);
2284
+ state == null ? void 0 : state.parentContextToolCallIds.add(toolCall.toolCallId);
2155
2285
  if (!(state == null ? void 0 : state.stepParent)) return;
2156
- const { toolCall } = event;
2157
2286
  const { operationName, resourceName } = opName(
2158
2287
  "ai.toolCall",
2159
2288
  state.functionId
@@ -2177,6 +2306,17 @@ var RaindropTelemetryIntegration = class {
2177
2306
  this.emitLive(state, "tool_start", toolCall.toolName, { args: toolCall.input });
2178
2307
  }
2179
2308
  toolExecutionEnd(event) {
2309
+ var _a;
2310
+ const toolCallId = (_a = event.toolCall) == null ? void 0 : _a.toolCallId;
2311
+ if (toolCallId && this.priorParentContexts.has(toolCallId)) {
2312
+ const prior = this.priorParentContexts.get(toolCallId);
2313
+ this.priorParentContexts.delete(toolCallId);
2314
+ if (prior) {
2315
+ enterParentToolContext(prior);
2316
+ } else {
2317
+ clearParentToolContext();
2318
+ }
2319
+ }
2180
2320
  const state = this.getState(event.callId);
2181
2321
  if (!state) return;
2182
2322
  const toolSpan = state.toolSpans.get(event.toolCall.toolCallId);
@@ -2240,7 +2380,8 @@ var RaindropTelemetryIntegration = class {
2240
2380
  );
2241
2381
  this.traceShipper.endSpan(state.rootSpan, { attributes: outputAttrs });
2242
2382
  }
2243
- if (this.sendEvents) {
2383
+ const suppressSubagentEvent = state.subagentName !== void 0 && state.subagentToolCallSpan !== void 0;
2384
+ if (this.sendEvents && !suppressSubagentEvent) {
2244
2385
  const callMeta = this.extractRaindropMetadata(state.metadata);
2245
2386
  const userId = (_f = callMeta.userId) != null ? _f : (_e = this.defaultContext) == null ? void 0 : _e.userId;
2246
2387
  if (userId) {
@@ -4333,7 +4474,7 @@ function extractNestedTokens(usage, key) {
4333
4474
  // package.json
4334
4475
  var package_default = {
4335
4476
  name: "@raindrop-ai/ai-sdk",
4336
- version: "0.0.29"};
4477
+ version: "0.0.30"};
4337
4478
 
4338
4479
  // src/internal/version.ts
4339
4480
  var libraryName = package_default.name;
@@ -4424,12 +4565,16 @@ function createRaindropAISDK(opts) {
4424
4565
  const writeKey = opts.writeKey;
4425
4566
  const eventsRequested = ((_a = opts.events) == null ? void 0 : _a.enabled) !== false;
4426
4567
  const tracesRequested = ((_b = opts.traces) == null ? void 0 : _b.enabled) !== false;
4427
- const eventsEnabled = eventsRequested && !!writeKey;
4428
- const tracesEnabled = tracesRequested && !!writeKey;
4429
4568
  const envDebug = envDebugEnabled();
4430
- if (!writeKey && (eventsRequested || tracesRequested)) {
4569
+ const localWorkshopInput = opts.localWorkshopUrl === false ? null : opts.localWorkshopUrl;
4570
+ const resolvedLocalDebuggerUrl = resolveLocalDebuggerBaseUrl(localWorkshopInput);
4571
+ const localDebuggerUrl = localWorkshopInput === null ? null : resolvedLocalDebuggerUrl != null ? resolvedLocalDebuggerUrl : void 0;
4572
+ const hasDestination = !!writeKey || !!resolvedLocalDebuggerUrl;
4573
+ const eventsEnabled = eventsRequested && hasDestination;
4574
+ const tracesEnabled = tracesRequested && hasDestination;
4575
+ if (!hasDestination && (eventsRequested || tracesRequested)) {
4431
4576
  console.warn(
4432
- "[raindrop-ai/ai-sdk] writeKey not provided; telemetry shipping is disabled"
4577
+ "[raindrop-ai/ai-sdk] writeKey not provided and no local Workshop reachable; telemetry shipping is disabled"
4433
4578
  );
4434
4579
  }
4435
4580
  const eventShipper = new EventShipper2({
@@ -4437,9 +4582,9 @@ function createRaindropAISDK(opts) {
4437
4582
  endpoint: opts.endpoint,
4438
4583
  enabled: eventsEnabled,
4439
4584
  debug: ((_c = opts.events) == null ? void 0 : _c.debug) === true || envDebug,
4440
- partialFlushMs: (_d = opts.events) == null ? void 0 : _d.partialFlushMs
4585
+ partialFlushMs: (_d = opts.events) == null ? void 0 : _d.partialFlushMs,
4586
+ localDebuggerUrl
4441
4587
  });
4442
- const localDebuggerUrl = opts.localWorkshopUrl === false ? null : opts.localWorkshopUrl;
4443
4588
  const traceShipper = new TraceShipper2({
4444
4589
  writeKey,
4445
4590
  endpoint: opts.endpoint,
@@ -4461,12 +4606,30 @@ function createRaindropAISDK(opts) {
4461
4606
  traceShipper
4462
4607
  });
4463
4608
  },
4464
- createTelemetryIntegration(context) {
4609
+ createTelemetryIntegration(contextOrOptions) {
4610
+ const FLAT_CONTEXT_KEYS = [
4611
+ "userId",
4612
+ "eventId",
4613
+ "eventName",
4614
+ "convoId",
4615
+ "properties"
4616
+ ];
4617
+ let context;
4618
+ let subagentWrapping;
4619
+ if (contextOrOptions === void 0) ; else if ("context" in contextOrOptions) {
4620
+ context = contextOrOptions.context;
4621
+ subagentWrapping = contextOrOptions.subagentWrapping;
4622
+ } else if ("subagentWrapping" in contextOrOptions && !FLAT_CONTEXT_KEYS.some((k) => k in contextOrOptions)) {
4623
+ subagentWrapping = contextOrOptions.subagentWrapping;
4624
+ } else {
4625
+ context = contextOrOptions;
4626
+ }
4465
4627
  return new RaindropTelemetryIntegration({
4466
4628
  traceShipper,
4467
4629
  eventShipper,
4468
4630
  sendTraces: tracesEnabled,
4469
4631
  sendEvents: eventsEnabled,
4632
+ subagentWrapping,
4470
4633
  debug: envDebug,
4471
4634
  context
4472
4635
  });
@@ -4574,4 +4737,4 @@ function createRaindropAISDK(opts) {
4574
4737
  };
4575
4738
  }
4576
4739
 
4577
- export { DEFAULT_REDACT_ATTRIBUTE_KEYS, DEFAULT_SECRET_KEY_NAMES, REDACTED_PLACEHOLDER, RaindropTelemetryIntegration, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, createRaindropAISDK, currentSpan, defaultTransformSpan, eventMetadata, eventMetadataFromChatRequest, getContextManager, getCurrentRaindropCallMetadata, readRaindropCallMetadataFromArgs, redactJsonAttributeValue, redactSecretsInObject, runWithRaindropCallMetadata, withCurrent };
4740
+ export { DEFAULT_REDACT_ATTRIBUTE_KEYS, DEFAULT_SECRET_KEY_NAMES, REDACTED_PLACEHOLDER, RaindropTelemetryIntegration, _resetParentToolContextStorage, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, clearParentToolContext, createRaindropAISDK, currentSpan, defaultTransformSpan, enterParentToolContext, eventMetadata, eventMetadataFromChatRequest, getContextManager, getCurrentParentToolContext, getCurrentRaindropCallMetadata, readRaindropCallMetadataFromArgs, redactJsonAttributeValue, redactSecretsInObject, runWithParentToolContext, runWithRaindropCallMetadata, withCurrent };
@@ -391,6 +391,19 @@ type RaindropTelemetryIntegrationOptions = {
391
391
  eventShipper: EventShipper;
392
392
  sendTraces?: boolean;
393
393
  sendEvents?: boolean;
394
+ /**
395
+ * When `true` (default), generations started inside a tool's `execute`
396
+ * callback on the same async branch are wrapped in a synthetic `TOOL_CALL`
397
+ * + `agent.subagent` LLM marker so observability backends can render them
398
+ * as an agent block (and so the inner LLM's `track_partial` is suppressed).
399
+ *
400
+ * This is the right semantic for any framework that lowers sub-agents to
401
+ * AI SDK tools (Ash, Mastra, Claude Agent SDK, hand-rolled tool loops),
402
+ * not Ash-specific. Set to `false` if you have a tool whose `execute`
403
+ * happens to call `streamText`/`generateText` for unrelated reasons (e.g.
404
+ * RAG enrichment) and don't want it labelled as a sub-agent.
405
+ */
406
+ subagentWrapping?: boolean;
394
407
  debug?: boolean;
395
408
  context?: {
396
409
  userId?: string;
@@ -405,9 +418,26 @@ declare class RaindropTelemetryIntegration implements TelemetryIntegration {
405
418
  private readonly eventShipper;
406
419
  private readonly sendTraces;
407
420
  private readonly sendEvents;
421
+ private readonly subagentWrapping;
408
422
  private readonly debug;
409
423
  private readonly defaultContext;
410
424
  private readonly callStates;
425
+ /**
426
+ * Per-tool-call snapshot of the parent-tool ALS context taken right
427
+ * before `enterParentToolContext` overwrites it in `toolExecutionStart`.
428
+ * Kept at the integration level (rather than on `CallState`) so the
429
+ * snapshot survives even when the parent generation has no tracked
430
+ * `CallState` — e.g. the AI SDK dispatches a tool callback for a
431
+ * `callId` we never registered via `onStart`. Without this, the
432
+ * unconditional ALS enter in `toolExecutionStart` would leave
433
+ * `toolExecutionEnd` no way to restore the prior context, so it would
434
+ * clear and wipe whatever the outer scope had set.
435
+ *
436
+ * Keyed by `toolCallId` because that's the only identifier guaranteed
437
+ * to round-trip between `toolExecutionStart` and `toolExecutionEnd`
438
+ * (the `event.callId` can be the same for parallel sibling tools).
439
+ */
440
+ private readonly priorParentContexts;
411
441
  constructor(opts: RaindropTelemetryIntegrationOptions);
412
442
  private getState;
413
443
  private cleanup;
@@ -510,6 +540,92 @@ declare function readRaindropCallMetadataFromArgs(args: readonly unknown[]): Rai
510
540
 
511
541
  declare function _resetWarnedMissingUserId(): void;
512
542
 
543
+ /**
544
+ * Parent tool execution context propagation.
545
+ *
546
+ * Problem
547
+ * ───────
548
+ * When a tool's `execute` callback recursively calls `streamText` /
549
+ * `generateText` (which is exactly how Ash lowers sub-agents, but also a
550
+ * common pattern in hand-rolled agent loops, Mastra, Claude Agent SDK,
551
+ * etc.), the inner generation's telemetry events are dispatched with no
552
+ * connection back to the outer tool call. The AI SDK fires `onStart` for
553
+ * the inner generation with its own fresh `callId`; nothing on the event
554
+ * tells the integration "this generation was triggered from inside tool
555
+ * X's execute callback."
556
+ *
557
+ * Without that linkage, observability tools have to fall back to
558
+ * regex-matching the prompt to guess whether a call is a sub-agent
559
+ * dispatch — fragile, framework-specific, and easily spoofable by user
560
+ * input.
561
+ *
562
+ * Solution
563
+ * ────────
564
+ * We maintain an `AsyncLocalStorage` slot that holds the currently-active
565
+ * parent tool call. The integration's `onToolExecutionStart` enters the
566
+ * slot before the AI SDK awaits `tool.execute(...)`; the inner
567
+ * generation's `onStart` reads from the slot to recover parent linkage.
568
+ * `onToolExecutionEnd` clears the slot so subsequent code in the same
569
+ * async branch doesn't see stale state.
570
+ *
571
+ * Async-context scoping
572
+ * ─────────────────────
573
+ * `enterWith` modifies the *current* async context, which propagates
574
+ * downward into any `await` continuation and into child async resources
575
+ * (including the awaited `tool.execute` and anything it calls). Sibling
576
+ * branches are unaffected: when the AI SDK dispatches tool calls via
577
+ * `Promise.all(map(async (tc) => ...))`, each map callback runs in its
578
+ * own async branch, so `enterWith` in one branch is invisible to the
579
+ * others. This is exactly the behaviour we need for parallel sub-agent
580
+ * dispatch.
581
+ *
582
+ * On runtimes that don't expose `AsyncLocalStorage` (browser, Cloudflare
583
+ * Workers without `nodejs_compat`), we fall back to a synchronous stack
584
+ * via `runWithParentToolContext` — see the `SyncFallbackStorage` in
585
+ * `call-metadata.ts` for the established pattern. `enterWith` becomes a
586
+ * silent no-op on the sync fallback, so async tool execution on those
587
+ * runtimes won't have parent linkage and the inner generation will
588
+ * render as a top-level call instead of being grouped under its caller.
589
+ */
590
+ type ParentToolContext = {
591
+ /** The `callId` of the generation whose step kicked off this tool call. */
592
+ parentCallId: string;
593
+ /** The tool-call id from the parent generation's step. */
594
+ toolCallId: string;
595
+ /** The tool name the parent generation invoked. */
596
+ toolName: string;
597
+ };
598
+ /** Test helper — drop the storage so tests start from a fresh slot. */
599
+ declare function _resetParentToolContextStorage(): void;
600
+ /**
601
+ * Returns the active parent tool context, or `undefined` if no tool is
602
+ * currently executing in this async branch.
603
+ */
604
+ declare function getCurrentParentToolContext(): ParentToolContext | undefined;
605
+ /**
606
+ * Enter a parent tool context on the current async branch. Subsequent
607
+ * `await`-ed work (including the parent's `tool.execute` body) inherits
608
+ * the context until {@link clearParentToolContext} runs. No-op on
609
+ * runtimes without `enterWith` support (browser/edge sync fallback).
610
+ */
611
+ declare function enterParentToolContext(ctx: ParentToolContext): void;
612
+ /**
613
+ * Clear the parent tool context on the current async branch. Pairs with
614
+ * {@link enterParentToolContext}. No-op on runtimes without `enterWith`
615
+ * support.
616
+ *
617
+ * `enterWith(undefined)` is supported by Node's `AsyncLocalStorage` at
618
+ * runtime; the `as never` cast bypasses TypeScript's stricter typing on
619
+ * the shared global declaration (`store: T`).
620
+ */
621
+ declare function clearParentToolContext(): void;
622
+ /**
623
+ * Run `fn` with `ctx` bound as the parent tool context. The sync-fallback
624
+ * equivalent of `enter`/`clear` — useful in tests and any code path
625
+ * where you want strict block scoping.
626
+ */
627
+ declare function runWithParentToolContext<R>(ctx: ParentToolContext, fn: () => R): R;
628
+
513
629
  /**
514
630
  * Options for creating event metadata for call-time context override.
515
631
  */
@@ -860,8 +976,17 @@ type RaindropAISDKClient = {
860
976
  /**
861
977
  * Create a TelemetryIntegration instance for direct registration with AI SDK v7+.
862
978
  * Use with `registerTelemetryIntegration()` from the `ai` package.
979
+ *
980
+ * Accepts the same `context` it has always accepted, or a full options
981
+ * object exposing `{ context, subagentWrapping }`. Sub-agent wrapping
982
+ * defaults to `true`; pass `subagentWrapping: false` to opt out for
983
+ * consumers whose `tool.execute` happens to call `generateText` for
984
+ * non-agentic reasons (e.g. RAG enrichment).
863
985
  */
864
- createTelemetryIntegration(context?: RaindropTelemetryIntegrationOptions["context"]): RaindropTelemetryIntegration;
986
+ createTelemetryIntegration(contextOrOptions?: RaindropTelemetryIntegrationOptions["context"] | {
987
+ context?: RaindropTelemetryIntegrationOptions["context"];
988
+ subagentWrapping?: boolean;
989
+ }): RaindropTelemetryIntegration;
865
990
  events: {
866
991
  patch(eventId: string, patch: EventPatch): Promise<void>;
867
992
  addAttachments(eventId: string, attachments: Attachment[]): Promise<void>;
@@ -933,4 +1058,4 @@ type RaindropAISDKClient = {
933
1058
  };
934
1059
  declare function createRaindropAISDK(opts: RaindropAISDKOptions): RaindropAISDKClient;
935
1060
 
936
- export { type AISDKChatRequestLike as A, type BuildEventPatch as B, ContextManager as C, DEFAULT_REDACT_ATTRIBUTE_KEYS as D, type EndSpanArgs as E, defaultTransformSpan as F, eventMetadata as G, eventMetadataFromChatRequest as H, type IdentifyInput as I, getContextManager as J, getCurrentRaindropCallMetadata as K, readRaindropCallMetadataFromArgs as L, redactJsonAttributeValue as M, redactSecretsInObject as N, type OtlpAnyValue as O, runWithRaindropCallMetadata as P, withCurrent as Q, REDACTED_PLACEHOLDER as R, type SelfDiagnosticsOptions as S, type TraceSpan as T, type WrapAISDKOptions as W, _resetRaindropCallMetadataStorage as _, type AISDKChatRequestMessageLike as a, type AISDKMessage as b, type AgentCallMetadata as c, type AgentWithMetadata as d, type Attachment as e, type ContextSpan as f, type CreateSpanArgs as g, DEFAULT_SECRET_KEY_NAMES as h, type EventBuilder as i, type EventMetadataOptions as j, type OtlpSpan as k, type RaindropAISDKClient as l, type RaindropAISDKContext as m, type RaindropAISDKOptions as n, type RaindropCallMetadata as o, RaindropTelemetryIntegration as p, type RaindropTelemetryIntegrationOptions as q, type SelfDiagnosticsSignalDefinition as r, type SelfDiagnosticsSignalDefinitions as s, type StartSpanArgs as t, type TransformSpanHook as u, type WrappedAI as v, type WrappedAISDK as w, _resetWarnedMissingUserId as x, createRaindropAISDK as y, currentSpan as z };
1061
+ export { withCurrent as $, type AISDKChatRequestLike as A, type BuildEventPatch as B, ContextManager as C, DEFAULT_REDACT_ATTRIBUTE_KEYS as D, type EndSpanArgs as E, createRaindropAISDK as F, currentSpan as G, defaultTransformSpan as H, type IdentifyInput as I, enterParentToolContext as J, eventMetadata as K, eventMetadataFromChatRequest as L, getContextManager as M, getCurrentParentToolContext as N, type OtlpAnyValue as O, type ParentToolContext as P, getCurrentRaindropCallMetadata as Q, REDACTED_PLACEHOLDER as R, type SelfDiagnosticsOptions as S, type TraceSpan as T, readRaindropCallMetadataFromArgs as U, redactJsonAttributeValue as V, type WrapAISDKOptions as W, redactSecretsInObject as X, runWithParentToolContext as Y, runWithRaindropCallMetadata as Z, _resetParentToolContextStorage as _, type AISDKChatRequestMessageLike as a, type AISDKMessage as b, type AgentCallMetadata as c, type AgentWithMetadata as d, type Attachment as e, type ContextSpan as f, type CreateSpanArgs as g, DEFAULT_SECRET_KEY_NAMES as h, type EventBuilder as i, type EventMetadataOptions as j, type OtlpSpan as k, type RaindropAISDKClient as l, type RaindropAISDKContext as m, type RaindropAISDKOptions as n, type RaindropCallMetadata as o, RaindropTelemetryIntegration as p, type RaindropTelemetryIntegrationOptions as q, type SelfDiagnosticsSignalDefinition as r, type SelfDiagnosticsSignalDefinitions as s, type StartSpanArgs as t, type TransformSpanHook as u, type WrappedAI as v, type WrappedAISDK as w, _resetRaindropCallMetadataStorage as x, _resetWarnedMissingUserId as y, clearParentToolContext as z };
@@ -391,6 +391,19 @@ type RaindropTelemetryIntegrationOptions = {
391
391
  eventShipper: EventShipper;
392
392
  sendTraces?: boolean;
393
393
  sendEvents?: boolean;
394
+ /**
395
+ * When `true` (default), generations started inside a tool's `execute`
396
+ * callback on the same async branch are wrapped in a synthetic `TOOL_CALL`
397
+ * + `agent.subagent` LLM marker so observability backends can render them
398
+ * as an agent block (and so the inner LLM's `track_partial` is suppressed).
399
+ *
400
+ * This is the right semantic for any framework that lowers sub-agents to
401
+ * AI SDK tools (Ash, Mastra, Claude Agent SDK, hand-rolled tool loops),
402
+ * not Ash-specific. Set to `false` if you have a tool whose `execute`
403
+ * happens to call `streamText`/`generateText` for unrelated reasons (e.g.
404
+ * RAG enrichment) and don't want it labelled as a sub-agent.
405
+ */
406
+ subagentWrapping?: boolean;
394
407
  debug?: boolean;
395
408
  context?: {
396
409
  userId?: string;
@@ -405,9 +418,26 @@ declare class RaindropTelemetryIntegration implements TelemetryIntegration {
405
418
  private readonly eventShipper;
406
419
  private readonly sendTraces;
407
420
  private readonly sendEvents;
421
+ private readonly subagentWrapping;
408
422
  private readonly debug;
409
423
  private readonly defaultContext;
410
424
  private readonly callStates;
425
+ /**
426
+ * Per-tool-call snapshot of the parent-tool ALS context taken right
427
+ * before `enterParentToolContext` overwrites it in `toolExecutionStart`.
428
+ * Kept at the integration level (rather than on `CallState`) so the
429
+ * snapshot survives even when the parent generation has no tracked
430
+ * `CallState` — e.g. the AI SDK dispatches a tool callback for a
431
+ * `callId` we never registered via `onStart`. Without this, the
432
+ * unconditional ALS enter in `toolExecutionStart` would leave
433
+ * `toolExecutionEnd` no way to restore the prior context, so it would
434
+ * clear and wipe whatever the outer scope had set.
435
+ *
436
+ * Keyed by `toolCallId` because that's the only identifier guaranteed
437
+ * to round-trip between `toolExecutionStart` and `toolExecutionEnd`
438
+ * (the `event.callId` can be the same for parallel sibling tools).
439
+ */
440
+ private readonly priorParentContexts;
411
441
  constructor(opts: RaindropTelemetryIntegrationOptions);
412
442
  private getState;
413
443
  private cleanup;
@@ -510,6 +540,92 @@ declare function readRaindropCallMetadataFromArgs(args: readonly unknown[]): Rai
510
540
 
511
541
  declare function _resetWarnedMissingUserId(): void;
512
542
 
543
+ /**
544
+ * Parent tool execution context propagation.
545
+ *
546
+ * Problem
547
+ * ───────
548
+ * When a tool's `execute` callback recursively calls `streamText` /
549
+ * `generateText` (which is exactly how Ash lowers sub-agents, but also a
550
+ * common pattern in hand-rolled agent loops, Mastra, Claude Agent SDK,
551
+ * etc.), the inner generation's telemetry events are dispatched with no
552
+ * connection back to the outer tool call. The AI SDK fires `onStart` for
553
+ * the inner generation with its own fresh `callId`; nothing on the event
554
+ * tells the integration "this generation was triggered from inside tool
555
+ * X's execute callback."
556
+ *
557
+ * Without that linkage, observability tools have to fall back to
558
+ * regex-matching the prompt to guess whether a call is a sub-agent
559
+ * dispatch — fragile, framework-specific, and easily spoofable by user
560
+ * input.
561
+ *
562
+ * Solution
563
+ * ────────
564
+ * We maintain an `AsyncLocalStorage` slot that holds the currently-active
565
+ * parent tool call. The integration's `onToolExecutionStart` enters the
566
+ * slot before the AI SDK awaits `tool.execute(...)`; the inner
567
+ * generation's `onStart` reads from the slot to recover parent linkage.
568
+ * `onToolExecutionEnd` clears the slot so subsequent code in the same
569
+ * async branch doesn't see stale state.
570
+ *
571
+ * Async-context scoping
572
+ * ─────────────────────
573
+ * `enterWith` modifies the *current* async context, which propagates
574
+ * downward into any `await` continuation and into child async resources
575
+ * (including the awaited `tool.execute` and anything it calls). Sibling
576
+ * branches are unaffected: when the AI SDK dispatches tool calls via
577
+ * `Promise.all(map(async (tc) => ...))`, each map callback runs in its
578
+ * own async branch, so `enterWith` in one branch is invisible to the
579
+ * others. This is exactly the behaviour we need for parallel sub-agent
580
+ * dispatch.
581
+ *
582
+ * On runtimes that don't expose `AsyncLocalStorage` (browser, Cloudflare
583
+ * Workers without `nodejs_compat`), we fall back to a synchronous stack
584
+ * via `runWithParentToolContext` — see the `SyncFallbackStorage` in
585
+ * `call-metadata.ts` for the established pattern. `enterWith` becomes a
586
+ * silent no-op on the sync fallback, so async tool execution on those
587
+ * runtimes won't have parent linkage and the inner generation will
588
+ * render as a top-level call instead of being grouped under its caller.
589
+ */
590
+ type ParentToolContext = {
591
+ /** The `callId` of the generation whose step kicked off this tool call. */
592
+ parentCallId: string;
593
+ /** The tool-call id from the parent generation's step. */
594
+ toolCallId: string;
595
+ /** The tool name the parent generation invoked. */
596
+ toolName: string;
597
+ };
598
+ /** Test helper — drop the storage so tests start from a fresh slot. */
599
+ declare function _resetParentToolContextStorage(): void;
600
+ /**
601
+ * Returns the active parent tool context, or `undefined` if no tool is
602
+ * currently executing in this async branch.
603
+ */
604
+ declare function getCurrentParentToolContext(): ParentToolContext | undefined;
605
+ /**
606
+ * Enter a parent tool context on the current async branch. Subsequent
607
+ * `await`-ed work (including the parent's `tool.execute` body) inherits
608
+ * the context until {@link clearParentToolContext} runs. No-op on
609
+ * runtimes without `enterWith` support (browser/edge sync fallback).
610
+ */
611
+ declare function enterParentToolContext(ctx: ParentToolContext): void;
612
+ /**
613
+ * Clear the parent tool context on the current async branch. Pairs with
614
+ * {@link enterParentToolContext}. No-op on runtimes without `enterWith`
615
+ * support.
616
+ *
617
+ * `enterWith(undefined)` is supported by Node's `AsyncLocalStorage` at
618
+ * runtime; the `as never` cast bypasses TypeScript's stricter typing on
619
+ * the shared global declaration (`store: T`).
620
+ */
621
+ declare function clearParentToolContext(): void;
622
+ /**
623
+ * Run `fn` with `ctx` bound as the parent tool context. The sync-fallback
624
+ * equivalent of `enter`/`clear` — useful in tests and any code path
625
+ * where you want strict block scoping.
626
+ */
627
+ declare function runWithParentToolContext<R>(ctx: ParentToolContext, fn: () => R): R;
628
+
513
629
  /**
514
630
  * Options for creating event metadata for call-time context override.
515
631
  */
@@ -860,8 +976,17 @@ type RaindropAISDKClient = {
860
976
  /**
861
977
  * Create a TelemetryIntegration instance for direct registration with AI SDK v7+.
862
978
  * Use with `registerTelemetryIntegration()` from the `ai` package.
979
+ *
980
+ * Accepts the same `context` it has always accepted, or a full options
981
+ * object exposing `{ context, subagentWrapping }`. Sub-agent wrapping
982
+ * defaults to `true`; pass `subagentWrapping: false` to opt out for
983
+ * consumers whose `tool.execute` happens to call `generateText` for
984
+ * non-agentic reasons (e.g. RAG enrichment).
863
985
  */
864
- createTelemetryIntegration(context?: RaindropTelemetryIntegrationOptions["context"]): RaindropTelemetryIntegration;
986
+ createTelemetryIntegration(contextOrOptions?: RaindropTelemetryIntegrationOptions["context"] | {
987
+ context?: RaindropTelemetryIntegrationOptions["context"];
988
+ subagentWrapping?: boolean;
989
+ }): RaindropTelemetryIntegration;
865
990
  events: {
866
991
  patch(eventId: string, patch: EventPatch): Promise<void>;
867
992
  addAttachments(eventId: string, attachments: Attachment[]): Promise<void>;
@@ -933,4 +1058,4 @@ type RaindropAISDKClient = {
933
1058
  };
934
1059
  declare function createRaindropAISDK(opts: RaindropAISDKOptions): RaindropAISDKClient;
935
1060
 
936
- export { type AISDKChatRequestLike as A, type BuildEventPatch as B, ContextManager as C, DEFAULT_REDACT_ATTRIBUTE_KEYS as D, type EndSpanArgs as E, defaultTransformSpan as F, eventMetadata as G, eventMetadataFromChatRequest as H, type IdentifyInput as I, getContextManager as J, getCurrentRaindropCallMetadata as K, readRaindropCallMetadataFromArgs as L, redactJsonAttributeValue as M, redactSecretsInObject as N, type OtlpAnyValue as O, runWithRaindropCallMetadata as P, withCurrent as Q, REDACTED_PLACEHOLDER as R, type SelfDiagnosticsOptions as S, type TraceSpan as T, type WrapAISDKOptions as W, _resetRaindropCallMetadataStorage as _, type AISDKChatRequestMessageLike as a, type AISDKMessage as b, type AgentCallMetadata as c, type AgentWithMetadata as d, type Attachment as e, type ContextSpan as f, type CreateSpanArgs as g, DEFAULT_SECRET_KEY_NAMES as h, type EventBuilder as i, type EventMetadataOptions as j, type OtlpSpan as k, type RaindropAISDKClient as l, type RaindropAISDKContext as m, type RaindropAISDKOptions as n, type RaindropCallMetadata as o, RaindropTelemetryIntegration as p, type RaindropTelemetryIntegrationOptions as q, type SelfDiagnosticsSignalDefinition as r, type SelfDiagnosticsSignalDefinitions as s, type StartSpanArgs as t, type TransformSpanHook as u, type WrappedAI as v, type WrappedAISDK as w, _resetWarnedMissingUserId as x, createRaindropAISDK as y, currentSpan as z };
1061
+ export { withCurrent as $, type AISDKChatRequestLike as A, type BuildEventPatch as B, ContextManager as C, DEFAULT_REDACT_ATTRIBUTE_KEYS as D, type EndSpanArgs as E, createRaindropAISDK as F, currentSpan as G, defaultTransformSpan as H, type IdentifyInput as I, enterParentToolContext as J, eventMetadata as K, eventMetadataFromChatRequest as L, getContextManager as M, getCurrentParentToolContext as N, type OtlpAnyValue as O, type ParentToolContext as P, getCurrentRaindropCallMetadata as Q, REDACTED_PLACEHOLDER as R, type SelfDiagnosticsOptions as S, type TraceSpan as T, readRaindropCallMetadataFromArgs as U, redactJsonAttributeValue as V, type WrapAISDKOptions as W, redactSecretsInObject as X, runWithParentToolContext as Y, runWithRaindropCallMetadata as Z, _resetParentToolContextStorage as _, type AISDKChatRequestMessageLike as a, type AISDKMessage as b, type AgentCallMetadata as c, type AgentWithMetadata as d, type Attachment as e, type ContextSpan as f, type CreateSpanArgs as g, DEFAULT_SECRET_KEY_NAMES as h, type EventBuilder as i, type EventMetadataOptions as j, type OtlpSpan as k, type RaindropAISDKClient as l, type RaindropAISDKContext as m, type RaindropAISDKOptions as n, type RaindropCallMetadata as o, RaindropTelemetryIntegration as p, type RaindropTelemetryIntegrationOptions as q, type SelfDiagnosticsSignalDefinition as r, type SelfDiagnosticsSignalDefinitions as s, type StartSpanArgs as t, type TransformSpanHook as u, type WrappedAI as v, type WrappedAISDK as w, _resetRaindropCallMetadataStorage as x, _resetWarnedMissingUserId as y, clearParentToolContext as z };