@raindrop-ai/ai-sdk 0.2.1 → 0.3.0

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.
@@ -4,7 +4,7 @@ var async_hooks = require('async_hooks');
4
4
 
5
5
  // src/index.node.ts
6
6
 
7
- // ../core/dist/chunk-QNWZIOOY.js
7
+ // ../core/dist/chunk-YUIXRQWN.js
8
8
  function normalizeFeatureFlags(input) {
9
9
  if (Array.isArray(input)) {
10
10
  return Object.fromEntries(input.map((name) => [name, "true"]));
@@ -67,6 +67,311 @@ function base64Encode(bytes) {
67
67
  }
68
68
  return out;
69
69
  }
70
+ function base64ToHex(value) {
71
+ if (!value) return "";
72
+ const maybeBuffer = globalThis.Buffer;
73
+ if (maybeBuffer) {
74
+ return maybeBuffer.from(value, "base64").toString("hex");
75
+ }
76
+ const atobFn = globalThis.atob;
77
+ if (typeof atobFn !== "function") return "";
78
+ try {
79
+ const binary = atobFn(value);
80
+ let hex = "";
81
+ for (let i = 0; i < binary.length; i++) {
82
+ hex += (binary.charCodeAt(i) & 255).toString(16).padStart(2, "0");
83
+ }
84
+ return hex;
85
+ } catch (e) {
86
+ return "";
87
+ }
88
+ }
89
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
90
+ mode: "raindrop.handoff.mode",
91
+ childEventId: "raindrop.handoff.childEventId",
92
+ parentEventId: "raindrop.handoff.parentEventId",
93
+ parentSpanId: "raindrop.handoff.parentSpanId",
94
+ name: "raindrop.handoff.name",
95
+ terminal: "raindrop.handoff.terminal",
96
+ agentRole: "raindrop.agent.role"
97
+ };
98
+ var HANDOFF_ATTRIBUTE_PREFIXES = [
99
+ "ai.telemetry.metadata.",
100
+ "ai.settings.context.",
101
+ ""
102
+ ];
103
+ var AI_SDK_METADATA_PREFIX = "ai.telemetry.metadata.";
104
+ var DETACHED_MODE = "detached";
105
+ var SUBAGENT_AGENT_ROLE = "subagent";
106
+ var HANDOFF_TERMINAL_CANCELLED = "cancelled";
107
+ var TOOL_EVENTS_SUFFIX = "raindrop.toolEvents";
108
+ var TOOL_EVENTS_ALLOW = "allow";
109
+ var SUBAGENT_ABORT_OPERATION_ID = "ai.subagent.failed";
110
+ var SUBAGENT_ABORT_SPAN_NAME = SUBAGENT_ABORT_OPERATION_ID;
111
+ var DEFAULT_DISPATCH_SPAN_NAME = "launch_subagent";
112
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
113
+ function defined(entries) {
114
+ const out = {};
115
+ for (const [key, value] of Object.entries(entries)) {
116
+ if (typeof value === "string" && value.trim().length > 0) out[key] = value;
117
+ }
118
+ return out;
119
+ }
120
+ function detachedDispatchMetadata(dispatch) {
121
+ return defined({
122
+ [HANDOFF_ATTRIBUTE_SUFFIXES.mode]: DETACHED_MODE,
123
+ [HANDOFF_ATTRIBUTE_SUFFIXES.childEventId]: dispatch.childEventId,
124
+ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: dispatch.name
125
+ });
126
+ }
127
+ function detachedChildMetadata(link) {
128
+ return defined({
129
+ [HANDOFF_ATTRIBUTE_SUFFIXES.parentEventId]: link.parentEventId,
130
+ [HANDOFF_ATTRIBUTE_SUFFIXES.parentSpanId]: link.parentSpanId,
131
+ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: link.name,
132
+ [HANDOFF_ATTRIBUTE_SUFFIXES.agentRole]: SUBAGENT_AGENT_ROLE
133
+ });
134
+ }
135
+ function cancelledTerminalMetadata() {
136
+ return { [HANDOFF_ATTRIBUTE_SUFFIXES.terminal]: HANDOFF_TERMINAL_CANCELLED };
137
+ }
138
+ function cancelledTerminalAttributes() {
139
+ const bare = cancelledTerminalMetadata();
140
+ return { ...bare, ...withMetadataPrefix(bare) };
141
+ }
142
+ function toolEventsAllowMetadata() {
143
+ return { [TOOL_EVENTS_SUFFIX]: TOOL_EVENTS_ALLOW };
144
+ }
145
+ function withMetadataPrefix(metadata) {
146
+ const out = {};
147
+ for (const [key, value] of Object.entries(metadata)) {
148
+ out[key.startsWith(AI_SDK_METADATA_PREFIX) ? key : `${AI_SDK_METADATA_PREFIX}${key}`] = value;
149
+ }
150
+ return out;
151
+ }
152
+ function readHandoffField(metadata, suffix) {
153
+ for (const prefix of HANDOFF_ATTRIBUTE_PREFIXES) {
154
+ const value = metadata[`${prefix}${suffix}`];
155
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
156
+ }
157
+ return void 0;
158
+ }
159
+ function readDetachedChildLink(metadata) {
160
+ if (!metadata) return void 0;
161
+ const parentEventId = readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.parentEventId);
162
+ if (!parentEventId) return void 0;
163
+ return {
164
+ parentEventId,
165
+ parentSpanId: readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.parentSpanId),
166
+ name: readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.name)
167
+ };
168
+ }
169
+ var BAGGAGE_KEYS = {
170
+ eventId: "raindrop-event-id",
171
+ childEventId: "raindrop-child-event-id",
172
+ name: "raindrop-handoff-name",
173
+ convoId: "raindrop-convo-id",
174
+ userId: "raindrop-user-id",
175
+ // The dispatch span and its trace, carried as fields as well as in
176
+ // `traceparent`. Not redundant: OTel's HTTP instrumentations rewrite
177
+ // `traceparent` from whatever span is current as the request goes out, so by
178
+ // the time the child reads it the ids name the HTTP client span and its trace
179
+ // instead of the dispatch. The child would then point its reverse reference at
180
+ // a span the launcher's UI does not know as a dispatch. A field states which
181
+ // span it means rather than meaning "whoever wrote this header last".
182
+ dispatchSpanId: "raindrop-dispatch-span-id",
183
+ traceId: "raindrop-trace-id"
184
+ };
185
+ var TRACEPARENT_HEADER = "traceparent";
186
+ var BAGGAGE_HEADER = "baggage";
187
+ var W3C_TRACE_ID = /^[0-9a-f]{32}$/;
188
+ var W3C_SPAN_ID = /^[0-9a-f]{16}$/;
189
+ function isW3CTraceId(value) {
190
+ return value !== void 0 && W3C_TRACE_ID.test(value);
191
+ }
192
+ function isW3CSpanId(value) {
193
+ return value !== void 0 && W3C_SPAN_ID.test(value);
194
+ }
195
+ var warnedNonConformantIds = false;
196
+ function warnNonConformantIdsOnce(traceId, spanId) {
197
+ if (warnedNonConformantIds) return;
198
+ warnedNonConformantIds = true;
199
+ console.warn(
200
+ `[raindrop] hand-off carrier ids are not W3C-shaped (traceId="${traceId}", spanId="${spanId}"). The child still reports as its own event, but its reverse reference names a span id the backend does not store, so the link back to the launcher will not resolve. A LangSmith carrier reads this way \u2014 its run ids are UUIDs; propagate Raindrop's own carrier (toHeaders) to link.`
201
+ );
202
+ }
203
+ var HANDOFF_HEADER = "x-raindrop-handoff";
204
+ var LANGSMITH_TRACE_HEADER = "langsmith-trace";
205
+ var LANGSMITH_METADATA_BAGGAGE_KEY = "langsmith-metadata";
206
+ function encodeBaggage(entries) {
207
+ return Object.entries(entries).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(",");
208
+ }
209
+ function decodeBaggage(header) {
210
+ if (!header) return {};
211
+ const parsed = {};
212
+ for (const part of header.split(",")) {
213
+ const [rawKey, ...rest] = part.split("=");
214
+ if (!rawKey || rest.length === 0) continue;
215
+ const key = rawKey.trim();
216
+ let value;
217
+ try {
218
+ value = decodeURIComponent(rest.join("=").trim());
219
+ } catch (e) {
220
+ value = rest.join("=").trim();
221
+ }
222
+ if (key in parsed && parsed[key] !== value) throw new ConflictingCarrierError(key);
223
+ parsed[key] = value;
224
+ }
225
+ return parsed;
226
+ }
227
+ function carrierBaggage(carrier) {
228
+ return {
229
+ [BAGGAGE_KEYS.eventId]: carrier.eventId,
230
+ [BAGGAGE_KEYS.childEventId]: carrier.childEventId,
231
+ [BAGGAGE_KEYS.name]: carrier.name,
232
+ [BAGGAGE_KEYS.convoId]: carrier.convoId,
233
+ [BAGGAGE_KEYS.userId]: carrier.userId,
234
+ [BAGGAGE_KEYS.dispatchSpanId]: carrier.spanId,
235
+ [BAGGAGE_KEYS.traceId]: carrier.traceId
236
+ };
237
+ }
238
+ function toHeaders(carrier) {
239
+ const fields = encodeBaggage(carrierBaggage(carrier));
240
+ const headers = {
241
+ [BAGGAGE_HEADER]: fields,
242
+ [HANDOFF_HEADER]: fields
243
+ };
244
+ if (isW3CTraceId(carrier.traceId) && isW3CSpanId(carrier.spanId)) {
245
+ headers[TRACEPARENT_HEADER] = `00-${carrier.traceId}-${carrier.spanId}-01`;
246
+ }
247
+ return headers;
248
+ }
249
+ function langsmithStamp() {
250
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "000000");
251
+ }
252
+ function toLangSmithHeaders(carrier) {
253
+ const stamp = langsmithStamp();
254
+ const dottedOrder = `${stamp}Z${carrier.traceId}.${stamp}Z${carrier.spanId}`;
255
+ return {
256
+ [LANGSMITH_TRACE_HEADER]: dottedOrder,
257
+ [BAGGAGE_HEADER]: encodeBaggage({
258
+ [LANGSMITH_METADATA_BAGGAGE_KEY]: JSON.stringify(carrierBaggage(carrier))
259
+ }),
260
+ // Carried here too: a LangSmith-shaped carrier is rewritten in transit
261
+ // exactly like the native one, and an injected `traceparent` is preferred
262
+ // over the dotted order on read.
263
+ [HANDOFF_HEADER]: encodeBaggage(carrierBaggage(carrier))
264
+ };
265
+ }
266
+ function isHeaderLookup(headers) {
267
+ return "get" in headers && typeof headers.get === "function";
268
+ }
269
+ var ConflictingCarrierError = class extends Error {
270
+ };
271
+ function readHeader(headers, name) {
272
+ var _a, _b, _c;
273
+ if (isHeaderLookup(headers)) return (_a = headers.get(name)) != null ? _a : void 0;
274
+ const value = (_c = headers[name]) != null ? _c : (_b = Object.entries(headers).find(([key]) => key.toLowerCase() === name)) == null ? void 0 : _b[1];
275
+ if (!Array.isArray(value)) return value;
276
+ const distinct = new Set(value);
277
+ if (distinct.size > 1) throw new ConflictingCarrierError(name);
278
+ return value[0];
279
+ }
280
+ function readTraceHeader(headers, name) {
281
+ const value = readHeader(headers, name);
282
+ if (!(value == null ? void 0 : value.includes(","))) return value;
283
+ const copies = value.split(",").map((copy) => copy.trim());
284
+ if (new Set(copies).size > 1) throw new ConflictingCarrierError(name);
285
+ return copies[0];
286
+ }
287
+ function fromHeaders(headers) {
288
+ var _a, _b;
289
+ let baggage;
290
+ let handoff;
291
+ let traceparent;
292
+ let langsmithTrace;
293
+ try {
294
+ baggage = decodeBaggage(readHeader(headers, BAGGAGE_HEADER));
295
+ handoff = decodeBaggage(readHeader(headers, HANDOFF_HEADER));
296
+ traceparent = readTraceHeader(headers, TRACEPARENT_HEADER);
297
+ langsmithTrace = readTraceHeader(headers, LANGSMITH_TRACE_HEADER);
298
+ } catch (error) {
299
+ if (error instanceof ConflictingCarrierError) return null;
300
+ throw error;
301
+ }
302
+ const langsmithMetadata = baggage[LANGSMITH_METADATA_BAGGAGE_KEY];
303
+ const nested = langsmithMetadata ? safeParseStringRecord(langsmithMetadata) : {};
304
+ for (const [key, value] of Object.entries(nested)) {
305
+ if (key in baggage && baggage[key] !== value) return null;
306
+ }
307
+ const fields = {
308
+ ...baggage,
309
+ ...nested,
310
+ // Last, so it wins: the standard headers may have been rewritten in transit
311
+ // by a propagator, this one is only ever written by us.
312
+ ...handoff
313
+ };
314
+ let traceId;
315
+ let spanId;
316
+ if (traceparent) {
317
+ const [, parsedTraceId, parsedSpanId] = traceparent.split("-");
318
+ if (isW3CTraceId(parsedTraceId) && isW3CSpanId(parsedSpanId)) {
319
+ traceId = parsedTraceId;
320
+ spanId = parsedSpanId;
321
+ }
322
+ }
323
+ if (!traceId && !spanId && langsmithTrace) {
324
+ const segments = langsmithTrace.split(".");
325
+ traceId = (_a = segments[0]) == null ? void 0 : _a.split("Z").pop();
326
+ spanId = (_b = segments[segments.length - 1]) == null ? void 0 : _b.split("Z").pop();
327
+ }
328
+ traceId = fields[BAGGAGE_KEYS.traceId] || traceId;
329
+ spanId = fields[BAGGAGE_KEYS.dispatchSpanId] || spanId;
330
+ const eventId = fields[BAGGAGE_KEYS.eventId];
331
+ if (!traceId || !spanId || !eventId) return null;
332
+ if (!isW3CTraceId(traceId) || !isW3CSpanId(spanId)) {
333
+ warnNonConformantIdsOnce(traceId, spanId);
334
+ }
335
+ const carrier = { traceId, spanId, eventId };
336
+ const childEventId = fields[BAGGAGE_KEYS.childEventId];
337
+ if (childEventId) carrier.childEventId = childEventId;
338
+ const name = fields[BAGGAGE_KEYS.name];
339
+ if (name) carrier.name = name;
340
+ const convoId = fields[BAGGAGE_KEYS.convoId];
341
+ if (convoId) carrier.convoId = convoId;
342
+ const userId = fields[BAGGAGE_KEYS.userId];
343
+ if (userId) carrier.userId = userId;
344
+ return carrier;
345
+ }
346
+ var UNRESOLVED_CARRIER_WARNING = `[raindrop] resume: headers were supplied but no hand-off carrier was found on them (searched ${HANDOFF_HEADER}, ${TRACEPARENT_HEADER}, ${BAGGAGE_HEADER}). This run reports as an UNLINKED event, and the launcher that dispatched it will stay on "queued" forever because nothing ever references its child. Passing headers means a parent handed off, so no carrier on them is almost certainly a defect: a gateway or proxy stripping ${HANDOFF_HEADER}, a middleware overwriting ${BAGGAGE_HEADER}, a request forwarded without its headers, or a hand-built carrier whose field names do not match. Send every header the launcher's dispatch returned. Logged once per process; pass no headers for a directly-invoked sub-agent to silence it.`;
347
+ var UNRESOLVED_CARRIER_WARNED_KEY = /* @__PURE__ */ Symbol.for("raindrop.handoff.warnedUnresolvedCarrier");
348
+ function warnedHolder() {
349
+ return globalThis;
350
+ }
351
+ function resetUnresolvedCarrierWarning() {
352
+ warnedHolder()[UNRESOLVED_CARRIER_WARNED_KEY] = false;
353
+ }
354
+ function warnUnresolvedCarrier(headers, carrier) {
355
+ if (carrier) return;
356
+ if (!headers) return;
357
+ const holder = warnedHolder();
358
+ if (holder[UNRESOLVED_CARRIER_WARNED_KEY]) return;
359
+ holder[UNRESOLVED_CARRIER_WARNED_KEY] = true;
360
+ console.warn(UNRESOLVED_CARRIER_WARNING);
361
+ }
362
+ function safeParseStringRecord(raw) {
363
+ try {
364
+ const parsed = JSON.parse(raw);
365
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
366
+ const result = {};
367
+ for (const [key, value] of Object.entries(parsed)) {
368
+ if (typeof value === "string") result[key] = value;
369
+ }
370
+ return result;
371
+ } catch (e) {
372
+ return {};
373
+ }
374
+ }
70
375
  function runWithTracingSuppressed(fn) {
71
376
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
72
377
  if (typeof hook !== "function") return fn();
@@ -1580,7 +1885,7 @@ installTracingSuppressionHook();
1580
1885
  // package.json
1581
1886
  var package_default = {
1582
1887
  name: "@raindrop-ai/ai-sdk",
1583
- version: "0.2.1"};
1888
+ version: "0.3.0"};
1584
1889
 
1585
1890
  // src/internal/version.ts
1586
1891
  var libraryName = package_default.name;
@@ -2468,6 +2773,353 @@ function runWithParentToolContext(ctx, fn) {
2468
2773
  return getStorage2().run(ctx, fn);
2469
2774
  }
2470
2775
 
2776
+ // src/internal/subagents.ts
2777
+ var SUBAGENT_CANCELLED_OUTPUT = "Cancelled before producing a result.";
2778
+ var SUBAGENT_ABORTED_OUTPUT = "Aborted before producing a result.";
2779
+ var SUBAGENT_FAILED_OPERATION_ID = SUBAGENT_ABORT_OPERATION_ID;
2780
+ var MAX_SETTLED_EVENT_IDS = 1024;
2781
+ function isTraceCarrier(value) {
2782
+ const candidate = value;
2783
+ return typeof candidate.traceId === "string" && typeof candidate.spanId === "string" && typeof candidate.eventId === "string";
2784
+ }
2785
+ function attrsFrom(metadata) {
2786
+ return Object.entries(withMetadataPrefix(metadata)).map(([key, value]) => attrString(key, value));
2787
+ }
2788
+ function attrsExact(attributes) {
2789
+ return Object.entries(attributes).map(([key, value]) => attrString(key, value));
2790
+ }
2791
+ function handoffSpanAttrs(link) {
2792
+ if (!link) return [];
2793
+ return attrsFrom(detachedChildMetadata(link));
2794
+ }
2795
+ function subagentNameAttrs(name, link) {
2796
+ if (!name || (link == null ? void 0 : link.name)) return [];
2797
+ return attrsFrom({ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: name });
2798
+ }
2799
+ function cancelledSpanAttrs() {
2800
+ return attrsExact(cancelledTerminalAttributes());
2801
+ }
2802
+ function describeAbortReason(reason) {
2803
+ if (reason instanceof Error) return `Aborted: ${reason.message}`;
2804
+ if (typeof reason === "string" && reason.trim().length > 0) return reason;
2805
+ if (reason === void 0 || reason === null) return SUBAGENT_ABORTED_OUTPUT;
2806
+ const serialized = boundedStringify(reason);
2807
+ return serialized ? `Aborted: ${serialized}` : SUBAGENT_ABORTED_OUTPUT;
2808
+ }
2809
+ function stampOpenSpan(span, metadata) {
2810
+ if (span.endTimeUnixNano) return;
2811
+ span.attributes.push(...attrsFrom(metadata));
2812
+ }
2813
+ function stampOpenSpanExact(span, attributes) {
2814
+ if (span.endTimeUnixNano) return;
2815
+ span.attributes.push(...attrsExact(attributes));
2816
+ }
2817
+ var SubagentApi = class {
2818
+ constructor(opts) {
2819
+ var _a;
2820
+ this.opts = opts;
2821
+ this.settledEventIds = (_a = opts.settledEventIds) != null ? _a : /* @__PURE__ */ new Set();
2822
+ }
2823
+ /** True when this child's outcome has already been reported. */
2824
+ wasSettledExplicitly(eventId) {
2825
+ return this.settledEventIds.has(eventId);
2826
+ }
2827
+ /**
2828
+ * Record an outcome reported somewhere other than `cancel()` / `fail()`, so
2829
+ * first-outcome-wins holds across every path that can state one.
2830
+ *
2831
+ * An aborted generation is such a path: an AbortSignal cancellation IS the
2832
+ * run's outcome, and the `fail(err)` that a catch block around the aborted
2833
+ * call naturally reports would otherwise error the child's spans — which is
2834
+ * the only thing separating `failed` from `cancelled`.
2835
+ */
2836
+ noteSettled(eventId) {
2837
+ this.remember(eventId);
2838
+ }
2839
+ remember(eventId) {
2840
+ if (this.settledEventIds.has(eventId)) return;
2841
+ if (this.settledEventIds.size >= MAX_SETTLED_EVENT_IDS) {
2842
+ const oldest = this.settledEventIds.values().next();
2843
+ if (!oldest.done) this.settledEventIds.delete(oldest.value);
2844
+ }
2845
+ this.settledEventIds.add(eventId);
2846
+ }
2847
+ /**
2848
+ * Record a detached sub-agent on this turn: allocate the child's event id,
2849
+ * stamp the dispatch, and return the headers to send with the job.
2850
+ *
2851
+ * This dispatches nothing — you do, moments later, with the returned headers.
2852
+ * Nothing here waits for the child either, or claims to know how it is doing.
2853
+ * Readers derive that from the child's own event.
2854
+ */
2855
+ subagent(options = {}) {
2856
+ var _a;
2857
+ const childEventId = (_a = options.childEventId) != null ? _a : randomUUID();
2858
+ const name = options.name;
2859
+ try {
2860
+ return this.recordLaunch(childEventId, name, options);
2861
+ } catch (err) {
2862
+ if (this.opts.debug) {
2863
+ console.warn(
2864
+ `[raindrop-ai/ai-sdk] sub-agent dispatch not recorded: ${err instanceof Error ? err.message : err}`
2865
+ );
2866
+ }
2867
+ return { childEventId, name, headers: {}, carrier: null };
2868
+ }
2869
+ }
2870
+ recordLaunch(childEventId, name, options) {
2871
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
2872
+ const active = (_a = this.opts.spanSource) == null ? void 0 : _a.activeDispatch();
2873
+ const inherited = getContextManager().getParentSpanIds();
2874
+ const eventId = (_e = (_c = (_b = options.eventId) != null ? _b : active == null ? void 0 : active.eventId) != null ? _c : inherited == null ? void 0 : inherited.eventId) != null ? _e : (_d = this.opts.defaultContext) == null ? void 0 : _d.eventId;
2875
+ const userId = (_h = (_f = options.userId) != null ? _f : active == null ? void 0 : active.userId) != null ? _h : (_g = this.opts.defaultContext) == null ? void 0 : _g.userId;
2876
+ const convoId = (_k = (_i = options.convoId) != null ? _i : active == null ? void 0 : active.convoId) != null ? _k : (_j = this.opts.defaultContext) == null ? void 0 : _j.convoId;
2877
+ if (!eventId) {
2878
+ return { childEventId, name, headers: {}, carrier: null };
2879
+ }
2880
+ const dispatch = detachedDispatchMetadata({ childEventId, name });
2881
+ const toolEvents = toolEventsAllowMetadata();
2882
+ for (const span of (_l = active == null ? void 0 : active.turnSpans) != null ? _l : []) {
2883
+ stampOpenSpan(span, toolEvents);
2884
+ }
2885
+ let dispatchIds;
2886
+ if (active == null ? void 0 : active.dispatchSpan) {
2887
+ stampOpenSpan(active.dispatchSpan, dispatch);
2888
+ dispatchIds = active.dispatchSpan.ids;
2889
+ } else if (this.opts.sendTraces) {
2890
+ dispatchIds = this.synthesizeDispatchSpan({
2891
+ childEventId,
2892
+ name,
2893
+ toolName: options.toolName,
2894
+ eventId,
2895
+ userId,
2896
+ convoId,
2897
+ input: options.input,
2898
+ // No active turn means no telemetry setting to honor: `input` is then
2899
+ // recorded on the explicit say-so of whoever passed it.
2900
+ recordInputs: (_m = active == null ? void 0 : active.recordInputs) != null ? _m : true,
2901
+ dispatch,
2902
+ parent: (inherited == null ? void 0 : inherited.eventId) === eventId ? inherited : void 0
2903
+ }).ids;
2904
+ }
2905
+ const traceId = base64ToHex((_o = (_n = dispatchIds == null ? void 0 : dispatchIds.traceIdB64) != null ? _n : inherited == null ? void 0 : inherited.traceIdB64) != null ? _o : "");
2906
+ const spanId = base64ToHex((_p = dispatchIds == null ? void 0 : dispatchIds.spanIdB64) != null ? _p : "");
2907
+ if (!traceId || !spanId) {
2908
+ if (this.opts.debug) {
2909
+ console.warn(
2910
+ "[raindrop-ai/ai-sdk] sub-agent dispatch recorded without a carrier: no dispatch span to reference (traces disabled?)"
2911
+ );
2912
+ }
2913
+ return { childEventId, name, headers: {}, carrier: null };
2914
+ }
2915
+ const carrier = {
2916
+ traceId,
2917
+ spanId,
2918
+ eventId,
2919
+ childEventId,
2920
+ ...name ? { name } : {},
2921
+ ...convoId ? { convoId } : {},
2922
+ ...userId ? { userId } : {}
2923
+ };
2924
+ return { childEventId, name, headers: toHeaders(carrier), carrier };
2925
+ }
2926
+ /**
2927
+ * Record a dispatch span for a launch with no open tool-call span to decorate:
2928
+ * a launch from outside a tool's `execute`, or from a caller that is not an
2929
+ * LLM turn at all (an HTTP handler that enqueues jobs). Shaped as a tool call
2930
+ * so it reads identically to a model-issued launch.
2931
+ */
2932
+ synthesizeDispatchSpan(args) {
2933
+ var _a;
2934
+ const spanName = (_a = args.toolName) != null ? _a : DEFAULT_DISPATCH_SPAN_NAME;
2935
+ const span = this.opts.traceShipper.startSpan({
2936
+ name: spanName,
2937
+ parent: args.parent,
2938
+ eventId: args.eventId,
2939
+ userId: args.userId,
2940
+ convoId: args.convoId,
2941
+ operationId: "ai.toolCall",
2942
+ attributes: [
2943
+ attrString("operation.name", "ai.toolCall"),
2944
+ attrString("resource.name", spanName),
2945
+ attrString("ai.toolCall.name", spanName),
2946
+ attrString("ai.toolCall.id", `call_${args.childEventId.replace(/-/g, "").slice(0, 12)}`),
2947
+ // Gated like every other input the turn's spans record: a caller that
2948
+ // disabled input capture must not leak the payload through the one
2949
+ // span this API synthesizes on its behalf.
2950
+ ...args.recordInputs ? [attrString("ai.toolCall.args", boundedStringify(args.input))] : [],
2951
+ ...attrsFrom(args.dispatch)
2952
+ ]
2953
+ });
2954
+ this.opts.traceShipper.endSpan(span, {
2955
+ // A dispatch returns a handle, not an answer — the answer is the child's.
2956
+ attributes: [
2957
+ attrString(
2958
+ "ai.toolCall.result",
2959
+ boundedStringify({ jobId: args.childEventId, status: "accepted" })
2960
+ )
2961
+ ]
2962
+ });
2963
+ return span;
2964
+ }
2965
+ /**
2966
+ * Adopt a carrier as the current run's parent reference.
2967
+ *
2968
+ * A missing carrier is a legitimate state — the child was invoked directly
2969
+ * rather than dispatched — so this always returns a usable run and leaves
2970
+ * `parent` null. Call sites stay unconditional.
2971
+ */
2972
+ resume(carrierOrHeaders, options = {}) {
2973
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2974
+ const carrier = carrierOrHeaders ? isTraceCarrier(carrierOrHeaders) ? carrierOrHeaders : this.readCarrier(carrierOrHeaders) : null;
2975
+ warnUnresolvedCarrier(carrierOrHeaders, carrier);
2976
+ const name = (_a = carrier == null ? void 0 : carrier.name) != null ? _a : options.name;
2977
+ const eventId = (_c = (_b = carrier == null ? void 0 : carrier.childEventId) != null ? _b : options.eventId) != null ? _c : randomUUID();
2978
+ const userId = (_f = (_d = carrier == null ? void 0 : carrier.userId) != null ? _d : options.userId) != null ? _f : (_e = this.opts.defaultContext) == null ? void 0 : _e.userId;
2979
+ const convoId = (_i = (_g = carrier == null ? void 0 : carrier.convoId) != null ? _g : options.convoId) != null ? _i : (_h = this.opts.defaultContext) == null ? void 0 : _h.convoId;
2980
+ const parent = carrier ? {
2981
+ parentEventId: carrier.eventId,
2982
+ ...carrier.spanId ? { parentSpanId: carrier.spanId } : {},
2983
+ ...name ? { name } : {}
2984
+ } : null;
2985
+ return {
2986
+ eventId,
2987
+ name,
2988
+ userId,
2989
+ convoId,
2990
+ parent,
2991
+ metadata: {
2992
+ eventId,
2993
+ ...userId ? { userId } : {},
2994
+ ...convoId ? { convoId } : {},
2995
+ ...parent ? { subagent: parent } : {}
2996
+ },
2997
+ cancel: (reason) => this.settle(eventId, { userId, convoId, name, cancelled: true, output: reason }),
2998
+ fail: (reason) => this.settle(eventId, {
2999
+ userId,
3000
+ convoId,
3001
+ name,
3002
+ handoff: parent != null ? parent : void 0,
3003
+ cancelled: false,
3004
+ output: describeAbortReason(reason),
3005
+ error: reason
3006
+ })
3007
+ };
3008
+ }
3009
+ /**
3010
+ * A carrier arrives from outside this process, so a hostile or merely broken
3011
+ * header bag must cost the job its link, never its run.
3012
+ */
3013
+ readCarrier(headers) {
3014
+ try {
3015
+ return fromHeaders(headers);
3016
+ } catch (err) {
3017
+ if (this.opts.debug) {
3018
+ console.warn(
3019
+ `[raindrop-ai/ai-sdk] sub-agent carrier ignored: ${err instanceof Error ? err.message : err}`
3020
+ );
3021
+ }
3022
+ return null;
3023
+ }
3024
+ }
3025
+ /**
3026
+ * Mark a failed child's own spans as errored.
3027
+ *
3028
+ * Reporting the reason as output is what gets the child an event at all, but
3029
+ * output alone is not enough: status is derived from span shape, and a child
3030
+ * with final output and no error spans derives `finished`. A failed job
3031
+ * reporting success is worse than one reporting nothing, so the failure has
3032
+ * to land on the telemetry too, not just in the text.
3033
+ *
3034
+ * This is the child describing its own run, not a caller writing a status it
3035
+ * cannot observe — the child's own spans are exactly where the contract puts
3036
+ * failure.
3037
+ */
3038
+ recordFailureOnSpans(eventId, args, reason) {
3039
+ var _a, _b, _c;
3040
+ if (!this.opts.sendTraces) return;
3041
+ const failure = (_a = args.error) != null ? _a : reason;
3042
+ let marked = 0;
3043
+ for (const span2 of (_c = (_b = this.opts.spanSource) == null ? void 0 : _b.openSpansForEvent(eventId)) != null ? _c : []) {
3044
+ if (span2.endTimeUnixNano) continue;
3045
+ this.opts.traceShipper.endSpan(span2, { error: failure });
3046
+ marked++;
3047
+ }
3048
+ if (marked > 0) return;
3049
+ const spanName = SUBAGENT_ABORT_SPAN_NAME;
3050
+ const span = this.opts.traceShipper.startSpan({
3051
+ name: spanName,
3052
+ eventId,
3053
+ userId: args.userId,
3054
+ convoId: args.convoId,
3055
+ // Required, not decorative: ingest drops any span carrying no
3056
+ // `ai.operationId` (or traceloop / `gen_ai.*` key) and still answers 200,
3057
+ // so without this the failure disappears silently. The value is
3058
+ // deliberately none of the generate/stream/tool/embed forms, which is
3059
+ // what classifies the span as INTERNAL rather than mislabelling a failed
3060
+ // job as a model call or a tool call.
3061
+ operationId: SUBAGENT_FAILED_OPERATION_ID,
3062
+ // Carries the reverse reference like every other span the child emits:
3063
+ // this is often the child's ONLY span, and a span read on its own is the
3064
+ // whole reason the reference cannot live on the root alone. The name is
3065
+ // stamped even for an unlinked run, which has no caller to reference —
3066
+ // `span_name` no longer says which sub-agent this was, so the attribute
3067
+ // is the only thing that can.
3068
+ attributes: [
3069
+ attrString("resource.name", spanName),
3070
+ ...handoffSpanAttrs(args.handoff),
3071
+ ...subagentNameAttrs(args.name, args.handoff)
3072
+ ]
3073
+ });
3074
+ this.opts.traceShipper.endSpan(span, { error: failure });
3075
+ }
3076
+ async settle(eventId, args) {
3077
+ var _a, _b;
3078
+ if (this.settledEventIds.has(eventId)) {
3079
+ if (this.opts.debug) {
3080
+ console.warn(
3081
+ `[raindrop-ai/ai-sdk] sub-agent ${eventId} already reported an outcome; ignoring the second one`
3082
+ );
3083
+ }
3084
+ return;
3085
+ }
3086
+ this.remember(eventId);
3087
+ const output = args.output && args.output.trim().length > 0 ? args.output : args.cancelled ? SUBAGENT_CANCELLED_OUTPUT : SUBAGENT_ABORTED_OUTPUT;
3088
+ try {
3089
+ if (args.cancelled) {
3090
+ for (const span of (_b = (_a = this.opts.spanSource) == null ? void 0 : _a.openSpansForEvent(eventId)) != null ? _b : []) {
3091
+ stampOpenSpanExact(span, cancelledTerminalAttributes());
3092
+ }
3093
+ } else {
3094
+ this.recordFailureOnSpans(eventId, args, output);
3095
+ }
3096
+ } catch (err) {
3097
+ if (this.opts.debug) {
3098
+ console.warn(
3099
+ `[raindrop-ai/ai-sdk] sub-agent outcome not recorded on spans: ${err instanceof Error ? err.message : err}`
3100
+ );
3101
+ }
3102
+ }
3103
+ if (!this.opts.sendEvents) return;
3104
+ await this.opts.eventShipper.patch(eventId, {
3105
+ userId: args.userId,
3106
+ convoId: args.convoId,
3107
+ // An event is only created from a run that reported something. A child
3108
+ // that dies silently produces no event at all, which pins the caller on
3109
+ // `queued` forever — indistinguishable from a job that never started.
3110
+ output,
3111
+ ...args.cancelled ? { properties: { [HANDOFF_TERMINAL_PROPERTY_KEY]: HANDOFF_TERMINAL_CANCELLED } } : {},
3112
+ isPending: false
3113
+ }).catch((err) => {
3114
+ if (this.opts.debug) {
3115
+ console.warn(
3116
+ `[raindrop-ai/ai-sdk] sub-agent outcome patch failed: ${err instanceof Error ? err.message : err}`
3117
+ );
3118
+ }
3119
+ });
3120
+ }
3121
+ };
3122
+
2471
3123
  // src/internal/usage.ts
2472
3124
  function firstTokenCount(...values) {
2473
3125
  for (const value of values) {
@@ -2660,6 +3312,7 @@ var RaindropTelemetryIntegration = class {
2660
3312
  eventName: (_f = callMeta.eventName) != null ? _f : (_e = this.defaultContext) == null ? void 0 : _e.eventName
2661
3313
  };
2662
3314
  const inherited = getContextManager().getParentSpanIds();
3315
+ const handoff = readDetachedChildLink(metadata);
2663
3316
  const eventIdGenerated = (metadata == null ? void 0 : metadata["raindrop.internal.eventIdGenerated"]) === "true" || (metadata == null ? void 0 : metadata["raindrop.internal.eventIdGenerated"]) === true || (callContextMetadata == null ? void 0 : callContextMetadata.eventIdGenerated) === true;
2664
3317
  const explicitEventId = callMeta.eventId && !eventIdGenerated ? callMeta.eventId : void 0;
2665
3318
  const eventId = (_j = (_i = (_h = explicitEventId != null ? explicitEventId : (_g = this.defaultContext) == null ? void 0 : _g.eventId) != null ? _h : inherited == null ? void 0 : inherited.eventId) != null ? _i : callMeta.eventId) != null ? _j : randomUUID();
@@ -2689,7 +3342,10 @@ var RaindropTelemetryIntegration = class {
2689
3342
  attrString("ai.toolCall.name", subagentName),
2690
3343
  attrString("raindrop.subagent.name", subagentName),
2691
3344
  attrString("raindrop.agent.role", "subagent"),
2692
- ...parentAttrs
3345
+ ...parentAttrs,
3346
+ // Top of this child's tree, so the reverse reference matters as much
3347
+ // here as on the root: these are the spans a reader lands on first.
3348
+ ...handoffSpanAttrs(handoff)
2693
3349
  ]
2694
3350
  });
2695
3351
  const subagentParentRef = this.spanParentRef(subagentToolCallSpan);
@@ -2702,7 +3358,8 @@ var RaindropTelemetryIntegration = class {
2702
3358
  attributes: [
2703
3359
  attrString("operation.name", "agent.subagent"),
2704
3360
  attrString("raindrop.span.kind", "llm_call"),
2705
- attrString("raindrop.subagent.name", subagentName)
3361
+ attrString("raindrop.subagent.name", subagentName),
3362
+ ...handoffSpanAttrs(handoff)
2706
3363
  ]
2707
3364
  });
2708
3365
  this.traceShipper.endSpan(markerSpan);
@@ -2770,7 +3427,9 @@ var RaindropTelemetryIntegration = class {
2770
3427
  inputText: isEmbed ? void 0 : this.extractInputText(event),
2771
3428
  toolCallCount: 0,
2772
3429
  subagentName,
2773
- subagentToolCallSpan
3430
+ subagentToolCallSpan,
3431
+ handoff,
3432
+ nestedInEvent: inheritedParent !== void 0
2774
3433
  });
2775
3434
  };
2776
3435
  // ── onStepStart ─────────────────────────────────────────────────────────
@@ -2815,7 +3474,8 @@ var RaindropTelemetryIntegration = class {
2815
3474
  ...attrsFromModelProvider(event.provider),
2816
3475
  attrString("ai.model.id", event.modelId),
2817
3476
  attrString(MODEL_USAGE_ATTRIBUTES.requestModel, event.modelId),
2818
- ...inputAttrs
3477
+ ...inputAttrs,
3478
+ ...handoffSpanAttrs(state.handoff)
2819
3479
  ]
2820
3480
  });
2821
3481
  state.stepSpan = stepSpan;
@@ -2954,7 +3614,8 @@ var RaindropTelemetryIntegration = class {
2954
3614
  attrString("operation.name", operationName),
2955
3615
  attrString("resource.name", resourceName),
2956
3616
  attrString("ai.telemetry.functionId", state.functionId),
2957
- ...inputAttrs
3617
+ ...inputAttrs,
3618
+ ...handoffSpanAttrs(state.handoff)
2958
3619
  ]
2959
3620
  });
2960
3621
  state.embedSpans.set(event.embedCallId, embedSpan);
@@ -3026,15 +3687,26 @@ var RaindropTelemetryIntegration = class {
3026
3687
  this.traceShipper.endSpan(toolSpan, { error: actualError });
3027
3688
  }
3028
3689
  state.toolSpans.clear();
3690
+ const abortReason = describeAbortReason(actualError);
3029
3691
  if (state.rootSpan) {
3030
- this.traceShipper.endSpan(state.rootSpan, { error: actualError });
3692
+ this.traceShipper.endSpan(state.rootSpan, {
3693
+ error: actualError,
3694
+ attributes: this.abortOutcomeAttrs(state, abortReason, "error")
3695
+ });
3031
3696
  }
3032
3697
  if (state.subagentToolCallSpan) {
3033
3698
  this.traceShipper.endSpan(state.subagentToolCallSpan, { error: actualError });
3034
3699
  }
3035
3700
  const isEmbed = state.operationId === "ai.embed" || state.operationId === "ai.embedMany";
3036
3701
  if (!isEmbed) {
3037
- this.finalizeGenerateEvent(state, state.accumulatedText || void 0, void 0);
3702
+ const errorIsTheRunsOutcome = state.handoff !== void 0 && !state.nestedInEvent;
3703
+ this.finalizeGenerateEvent(
3704
+ state,
3705
+ state.accumulatedText || void 0,
3706
+ void 0,
3707
+ false,
3708
+ errorIsTheRunsOutcome ? { fallbackOutput: abortReason } : void 0
3709
+ );
3038
3710
  }
3039
3711
  this.cleanup(event.callId);
3040
3712
  };
@@ -3071,9 +3743,19 @@ var RaindropTelemetryIntegration = class {
3071
3743
  this.traceShipper.endSpan(toolSpan, { attributes: [abortedAttr] });
3072
3744
  }
3073
3745
  state.toolSpans.clear();
3746
+ const abortIsTheRunsOutcome = state.handoff !== void 0 && !state.nestedInEvent;
3074
3747
  if (state.rootSpan) {
3075
3748
  this.traceShipper.endSpan(state.rootSpan, {
3076
- attributes: [abortedAttr, attrInt("ai.toolCall.count", state.toolCallCount)]
3749
+ attributes: [
3750
+ abortedAttr,
3751
+ attrInt("ai.toolCall.count", state.toolCallCount),
3752
+ // An aborted detached child IS a cancelled sub-agent, and a cancelled
3753
+ // run is span-for-span identical to a finished one — spans close,
3754
+ // nothing errors, there is just no answer. So it is the one outcome
3755
+ // that has to be stated, and the child states it on itself.
3756
+ ...abortIsTheRunsOutcome ? cancelledSpanAttrs() : [],
3757
+ ...abortIsTheRunsOutcome ? this.abortOutcomeAttrs(state, SUBAGENT_CANCELLED_OUTPUT, "stop") : []
3758
+ ]
3077
3759
  });
3078
3760
  }
3079
3761
  if (state.subagentToolCallSpan) {
@@ -3081,7 +3763,17 @@ var RaindropTelemetryIntegration = class {
3081
3763
  attributes: [abortedAttr]
3082
3764
  });
3083
3765
  }
3084
- this.finalizeGenerateEvent(state, state.accumulatedText || void 0, void 0);
3766
+ this.finalizeGenerateEvent(
3767
+ state,
3768
+ state.accumulatedText || void 0,
3769
+ void 0,
3770
+ false,
3771
+ abortIsTheRunsOutcome ? {
3772
+ fallbackOutput: SUBAGENT_CANCELLED_OUTPUT,
3773
+ properties: { [HANDOFF_TERMINAL_PROPERTY_KEY]: HANDOFF_TERMINAL_CANCELLED }
3774
+ } : void 0
3775
+ );
3776
+ if (abortIsTheRunsOutcome) this.subagents.noteSettled(state.eventId);
3085
3777
  this.cleanup(callId);
3086
3778
  };
3087
3779
  // ── executeTool ─────────────────────────────────────────────────────────
@@ -3109,6 +3801,97 @@ var RaindropTelemetryIntegration = class {
3109
3801
  this.subagentWrapping = opts.subagentWrapping !== false;
3110
3802
  this.debug = opts.debug === true;
3111
3803
  this.defaultContext = opts.context;
3804
+ this.subagents = new SubagentApi({
3805
+ traceShipper: this.traceShipper,
3806
+ eventShipper: this.eventShipper,
3807
+ sendTraces: this.sendTraces,
3808
+ sendEvents: this.sendEvents,
3809
+ debug: this.debug,
3810
+ defaultContext: this.defaultContext,
3811
+ settledEventIds: opts.settledEventIds,
3812
+ spanSource: {
3813
+ activeDispatch: () => this.activeDispatch(),
3814
+ openSpansForEvent: (eventId) => this.openSpansForEvent(eventId)
3815
+ }
3816
+ });
3817
+ }
3818
+ /**
3819
+ * Record a detached sub-agent on the turn that is live right now.
3820
+ *
3821
+ * Returns the dispatch — the child's event id and the headers to send with
3822
+ * the job. Dispatching is still yours to do; this only states that you are
3823
+ * about to, so a reader can follow the link before the child exists.
3824
+ */
3825
+ subagent(options = {}) {
3826
+ return this.subagents.subagent(options);
3827
+ }
3828
+ // ── detached sub-agent hand-offs ─────────────────────────────────────────
3829
+ /**
3830
+ * The turn a `subagent()` dispatch is happening inside.
3831
+ *
3832
+ * Resolved from the parent-tool context first, which is the only way to reach
3833
+ * the EXACT span that dispatched: the tool call the model itself issued, still
3834
+ * open while its `execute` runs. Falling back to the event id lets a launch
3835
+ * from elsewhere in the turn (a queue producer called mid-generation) still
3836
+ * find the turn's spans, which is where the tool-events opt-in has to land.
3837
+ */
3838
+ activeDispatch() {
3839
+ var _a;
3840
+ const parentTool = getCurrentParentToolContext();
3841
+ let state = parentTool ? this.getState(parentTool.parentCallId) : void 0;
3842
+ const inheritedEventId = (_a = getContextManager().getParentSpanIds()) == null ? void 0 : _a.eventId;
3843
+ if (!state && inheritedEventId) {
3844
+ for (const candidate of this.callStates.values()) {
3845
+ if (candidate.eventId === inheritedEventId) {
3846
+ state = candidate;
3847
+ break;
3848
+ }
3849
+ }
3850
+ }
3851
+ if (!state) return void 0;
3852
+ return {
3853
+ eventId: state.eventId,
3854
+ userId: state.identity.userId,
3855
+ convoId: state.identity.convoId,
3856
+ dispatchSpan: parentTool ? state.toolSpans.get(parentTool.toolCallId) : void 0,
3857
+ turnSpans: [state.rootSpan, state.stepSpan].filter(
3858
+ (span) => span !== void 0
3859
+ ),
3860
+ recordInputs: state.recordInputs
3861
+ };
3862
+ }
3863
+ /** Spans still open for an event, so a late outcome can still be stated. */
3864
+ openSpansForEvent(eventId) {
3865
+ const spans = [];
3866
+ for (const state of this.callStates.values()) {
3867
+ if (state.eventId !== eventId) continue;
3868
+ if (state.rootSpan) spans.push(state.rootSpan);
3869
+ if (state.stepSpan) spans.push(state.stepSpan);
3870
+ }
3871
+ return spans;
3872
+ }
3873
+ /**
3874
+ * End-attributes that keep a detached child legible when it ends without an
3875
+ * answer — an error, an abort, a cancellation.
3876
+ *
3877
+ * An event is created from an LLM span only when it has a finish reason and
3878
+ * non-empty output, so a child that dies silently produces NO event, and the
3879
+ * caller's hand-off pill stays on `queued` forever — indistinguishable from a
3880
+ * job that never started. Reporting the abort reason as the run's output is
3881
+ * what makes the outcome visible. Only detached children get this: an inline
3882
+ * generation's failure is already legible from its caller's own event.
3883
+ *
3884
+ * And only the run's OUTERMOST generation gets it (see `nestedInEvent`): an
3885
+ * inner sub-call ending badly is not the run ending, and since an event is
3886
+ * created from an LLM span with output, `ai.response.text` here would state
3887
+ * the run's outcome through the span while the turn is still working.
3888
+ */
3889
+ abortOutcomeAttrs(state, reason, finishReason) {
3890
+ if (!state.handoff || state.nestedInEvent) return [];
3891
+ return [
3892
+ attrString("ai.response.text", capText2(state.accumulatedText || reason)),
3893
+ attrString("ai.response.finishReason", finishReason)
3894
+ ];
3112
3895
  }
3113
3896
  // ── helpers ──────────────────────────────────────────────────────────────
3114
3897
  getState(callId) {
@@ -3227,7 +4010,8 @@ var RaindropTelemetryIntegration = class {
3227
4010
  attrString("ai.telemetry.functionId", state.functionId),
3228
4011
  attrString("ai.toolCall.name", toolCall.toolName),
3229
4012
  attrString("ai.toolCall.id", toolCall.toolCallId),
3230
- ...inputAttrs
4013
+ ...inputAttrs,
4014
+ ...handoffSpanAttrs(state.handoff)
3231
4015
  ]
3232
4016
  });
3233
4017
  state.toolSpans.set(toolCall.toolCallId, toolSpan);
@@ -3306,7 +4090,8 @@ var RaindropTelemetryIntegration = class {
3306
4090
  attrString("ai.toolCall.name", call.toolName),
3307
4091
  attrString("ai.toolCall.id", call.toolCallId),
3308
4092
  attrString("ai.toolCall.providerExecuted", "true"),
3309
- ...inputAttrs
4093
+ ...inputAttrs,
4094
+ ...handoffSpanAttrs(state.handoff)
3310
4095
  ]
3311
4096
  });
3312
4097
  state.toolCallCount += 1;
@@ -3387,19 +4172,22 @@ var RaindropTelemetryIntegration = class {
3387
4172
  * is finalized by that resume — otherwise each execution would finalize the
3388
4173
  * same event ID into a separate canonical row.
3389
4174
  */
3390
- finalizeGenerateEvent(state, output, model, keepPending = false) {
4175
+ finalizeGenerateEvent(state, output, model, keepPending = false, detachedOutcome) {
3391
4176
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3392
4177
  const suppressSubagentEvent = state.subagentName !== void 0 && state.subagentToolCallSpan !== void 0;
3393
4178
  if (!this.sendEvents || suppressSubagentEvent) return;
4179
+ if (this.subagents.wasSettledExplicitly(state.eventId)) return;
3394
4180
  const callMeta = this.extractRaindropMetadata(state.metadata);
3395
4181
  const userId = (_b = callMeta.userId) != null ? _b : (_a = this.defaultContext) == null ? void 0 : _a.userId;
3396
4182
  if (!userId) return;
3397
4183
  const eventName = (_e = (_d = callMeta.eventName) != null ? _d : (_c = this.defaultContext) == null ? void 0 : _c.eventName) != null ? _e : state.operationId;
3398
4184
  const cappedOutput = capText2(output);
4185
+ const reportedOutput = state.handoff && detachedOutcome && !(cappedOutput == null ? void 0 : cappedOutput.trim()) ? detachedOutcome.fallbackOutput : cappedOutput;
3399
4186
  const input = capText2(state.inputText);
3400
4187
  const properties = {
3401
4188
  ...(_f = this.defaultContext) == null ? void 0 : _f.properties,
3402
- ...callMeta.properties
4189
+ ...callMeta.properties,
4190
+ ...state.handoff ? detachedOutcome == null ? void 0 : detachedOutcome.properties : void 0
3403
4191
  };
3404
4192
  const featureFlags = {
3405
4193
  ...((_g = this.defaultContext) == null ? void 0 : _g.featureFlags) ? normalizeFeatureFlags(this.defaultContext.featureFlags) : {},
@@ -3411,7 +4199,7 @@ var RaindropTelemetryIntegration = class {
3411
4199
  userId,
3412
4200
  convoId,
3413
4201
  input,
3414
- output: cappedOutput,
4202
+ output: reportedOutput,
3415
4203
  model,
3416
4204
  properties: Object.keys(properties).length > 0 ? properties : void 0,
3417
4205
  featureFlags: Object.keys(featureFlags).length > 0 ? featureFlags : void 0,
@@ -4323,6 +5111,7 @@ function wrapAISDK(aiSDK, deps) {
4323
5111
  sendTraces: ((_a = deps.options.send) == null ? void 0 : _a.traces) !== false,
4324
5112
  sendEvents: ((_b = deps.options.send) == null ? void 0 : _b.events) !== false,
4325
5113
  debug,
5114
+ settledEventIds: deps.settledEventIds,
4326
5115
  context: {
4327
5116
  userId: wrapTimeCtx.userId,
4328
5117
  eventId: wrapTimeCtx.eventId,
@@ -5532,6 +6321,8 @@ function eventMetadata(options) {
5532
6321
  if (options.properties) result["raindrop.properties"] = JSON.stringify(options.properties);
5533
6322
  if (options.featureFlags)
5534
6323
  result["raindrop.featureFlags"] = JSON.stringify(normalizeFeatureFlags(options.featureFlags));
6324
+ if (options.subagent) Object.assign(result, detachedChildMetadata(options.subagent));
6325
+ if (options.toolEvents) result[TOOL_EVENTS_SUFFIX] = options.toolEvents;
5535
6326
  return result;
5536
6327
  }
5537
6328
  function deriveChatTurnMessageId(request) {
@@ -5614,12 +6405,22 @@ function createRaindropAISDK(opts) {
5614
6405
  transformSpan: (_j = opts.traces) == null ? void 0 : _j.transformSpan,
5615
6406
  disableDefaultRedaction: (_k = opts.traces) == null ? void 0 : _k.disableDefaultRedaction
5616
6407
  });
6408
+ const settledEventIds = /* @__PURE__ */ new Set();
6409
+ const subagentApi = new SubagentApi({
6410
+ traceShipper,
6411
+ eventShipper,
6412
+ sendTraces: tracesEnabled,
6413
+ sendEvents: eventsEnabled,
6414
+ debug: envDebug,
6415
+ settledEventIds
6416
+ });
5617
6417
  return {
5618
6418
  wrap(aiSDK, options) {
5619
6419
  return wrapAISDK(aiSDK, {
5620
6420
  options: options != null ? options : {},
5621
6421
  eventShipper,
5622
- traceShipper
6422
+ traceShipper,
6423
+ settledEventIds
5623
6424
  });
5624
6425
  },
5625
6426
  createSelfDiagnosticsTool(options = {}) {
@@ -5656,9 +6457,14 @@ function createRaindropAISDK(opts) {
5656
6457
  sendEvents: eventsEnabled,
5657
6458
  subagentWrapping,
5658
6459
  debug: envDebug,
6460
+ settledEventIds,
5659
6461
  context
5660
6462
  });
5661
6463
  },
6464
+ subagents: subagentApi,
6465
+ subagent(options = {}) {
6466
+ return subagentApi.subagent(options);
6467
+ },
5662
6468
  events: {
5663
6469
  async patch(eventId, patch) {
5664
6470
  await eventShipper.patch(eventId, patch);
@@ -5784,8 +6590,17 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = async_hooks.AsyncLocalStorage;
5784
6590
  exports.DEFAULT_MAX_TEXT_FIELD_CHARS = DEFAULT_MAX_TEXT_FIELD_CHARS2;
5785
6591
  exports.DEFAULT_REDACT_ATTRIBUTE_KEYS = DEFAULT_REDACT_ATTRIBUTE_KEYS;
5786
6592
  exports.DEFAULT_SECRET_KEY_NAMES = DEFAULT_SECRET_KEY_NAMES;
6593
+ exports.DETACHED_MODE = DETACHED_MODE;
6594
+ exports.HANDOFF_ATTRIBUTE_PREFIXES = HANDOFF_ATTRIBUTE_PREFIXES;
6595
+ exports.HANDOFF_ATTRIBUTE_SUFFIXES = HANDOFF_ATTRIBUTE_SUFFIXES;
6596
+ exports.HANDOFF_TERMINAL_CANCELLED = HANDOFF_TERMINAL_CANCELLED;
6597
+ exports.HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_TERMINAL_PROPERTY_KEY;
5787
6598
  exports.REDACTED_PLACEHOLDER = REDACTED_PLACEHOLDER;
5788
6599
  exports.RaindropTelemetryIntegration = RaindropTelemetryIntegration;
6600
+ exports.SUBAGENT_ABORTED_OUTPUT = SUBAGENT_ABORTED_OUTPUT;
6601
+ exports.SUBAGENT_AGENT_ROLE = SUBAGENT_AGENT_ROLE;
6602
+ exports.SUBAGENT_CANCELLED_OUTPUT = SUBAGENT_CANCELLED_OUTPUT;
6603
+ exports.TOOL_EVENTS_ALLOW = TOOL_EVENTS_ALLOW;
5789
6604
  exports.TRUNCATION_MARKER = TRUNCATION_MARKER2;
5790
6605
  exports._resetParentToolContextStorage = _resetParentToolContextStorage;
5791
6606
  exports._resetRaindropCallMetadataStorage = _resetRaindropCallMetadataStorage;
@@ -5799,6 +6614,7 @@ exports.defaultTransformSpan = defaultTransformSpan;
5799
6614
  exports.enterParentToolContext = enterParentToolContext;
5800
6615
  exports.eventMetadata = eventMetadata;
5801
6616
  exports.eventMetadataFromChatRequest = eventMetadataFromChatRequest;
6617
+ exports.fromHeaders = fromHeaders;
5802
6618
  exports.getContextManager = getContextManager;
5803
6619
  exports.getCurrentParentToolContext = getCurrentParentToolContext;
5804
6620
  exports.getCurrentRaindropCallMetadata = getCurrentRaindropCallMetadata;
@@ -5807,6 +6623,9 @@ exports.raindrop = raindrop;
5807
6623
  exports.readRaindropCallMetadataFromArgs = readRaindropCallMetadataFromArgs;
5808
6624
  exports.redactJsonAttributeValue = redactJsonAttributeValue;
5809
6625
  exports.redactSecretsInObject = redactSecretsInObject;
6626
+ exports.resetUnresolvedCarrierWarning = resetUnresolvedCarrierWarning;
5810
6627
  exports.runWithParentToolContext = runWithParentToolContext;
5811
6628
  exports.runWithRaindropCallMetadata = runWithRaindropCallMetadata;
6629
+ exports.toHeaders = toHeaders;
6630
+ exports.toLangSmithHeaders = toLangSmithHeaders;
5812
6631
  exports.withCurrent = withCurrent;