@raindrop-ai/ai-sdk 0.2.0 → 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.
@@ -1,4 +1,4 @@
1
- // ../core/dist/chunk-QTUH3BHZ.js
1
+ // ../core/dist/chunk-YUIXRQWN.js
2
2
  function normalizeFeatureFlags(input) {
3
3
  if (Array.isArray(input)) {
4
4
  return Object.fromEntries(input.map((name) => [name, "true"]));
@@ -61,6 +61,311 @@ function base64Encode(bytes) {
61
61
  }
62
62
  return out;
63
63
  }
64
+ function base64ToHex(value) {
65
+ if (!value) return "";
66
+ const maybeBuffer = globalThis.Buffer;
67
+ if (maybeBuffer) {
68
+ return maybeBuffer.from(value, "base64").toString("hex");
69
+ }
70
+ const atobFn = globalThis.atob;
71
+ if (typeof atobFn !== "function") return "";
72
+ try {
73
+ const binary = atobFn(value);
74
+ let hex = "";
75
+ for (let i = 0; i < binary.length; i++) {
76
+ hex += (binary.charCodeAt(i) & 255).toString(16).padStart(2, "0");
77
+ }
78
+ return hex;
79
+ } catch (e) {
80
+ return "";
81
+ }
82
+ }
83
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
84
+ mode: "raindrop.handoff.mode",
85
+ childEventId: "raindrop.handoff.childEventId",
86
+ parentEventId: "raindrop.handoff.parentEventId",
87
+ parentSpanId: "raindrop.handoff.parentSpanId",
88
+ name: "raindrop.handoff.name",
89
+ terminal: "raindrop.handoff.terminal",
90
+ agentRole: "raindrop.agent.role"
91
+ };
92
+ var HANDOFF_ATTRIBUTE_PREFIXES = [
93
+ "ai.telemetry.metadata.",
94
+ "ai.settings.context.",
95
+ ""
96
+ ];
97
+ var AI_SDK_METADATA_PREFIX = "ai.telemetry.metadata.";
98
+ var DETACHED_MODE = "detached";
99
+ var SUBAGENT_AGENT_ROLE = "subagent";
100
+ var HANDOFF_TERMINAL_CANCELLED = "cancelled";
101
+ var TOOL_EVENTS_SUFFIX = "raindrop.toolEvents";
102
+ var TOOL_EVENTS_ALLOW = "allow";
103
+ var SUBAGENT_ABORT_OPERATION_ID = "ai.subagent.failed";
104
+ var SUBAGENT_ABORT_SPAN_NAME = SUBAGENT_ABORT_OPERATION_ID;
105
+ var DEFAULT_DISPATCH_SPAN_NAME = "launch_subagent";
106
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
107
+ function defined(entries) {
108
+ const out = {};
109
+ for (const [key, value] of Object.entries(entries)) {
110
+ if (typeof value === "string" && value.trim().length > 0) out[key] = value;
111
+ }
112
+ return out;
113
+ }
114
+ function detachedDispatchMetadata(dispatch) {
115
+ return defined({
116
+ [HANDOFF_ATTRIBUTE_SUFFIXES.mode]: DETACHED_MODE,
117
+ [HANDOFF_ATTRIBUTE_SUFFIXES.childEventId]: dispatch.childEventId,
118
+ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: dispatch.name
119
+ });
120
+ }
121
+ function detachedChildMetadata(link) {
122
+ return defined({
123
+ [HANDOFF_ATTRIBUTE_SUFFIXES.parentEventId]: link.parentEventId,
124
+ [HANDOFF_ATTRIBUTE_SUFFIXES.parentSpanId]: link.parentSpanId,
125
+ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: link.name,
126
+ [HANDOFF_ATTRIBUTE_SUFFIXES.agentRole]: SUBAGENT_AGENT_ROLE
127
+ });
128
+ }
129
+ function cancelledTerminalMetadata() {
130
+ return { [HANDOFF_ATTRIBUTE_SUFFIXES.terminal]: HANDOFF_TERMINAL_CANCELLED };
131
+ }
132
+ function cancelledTerminalAttributes() {
133
+ const bare = cancelledTerminalMetadata();
134
+ return { ...bare, ...withMetadataPrefix(bare) };
135
+ }
136
+ function toolEventsAllowMetadata() {
137
+ return { [TOOL_EVENTS_SUFFIX]: TOOL_EVENTS_ALLOW };
138
+ }
139
+ function withMetadataPrefix(metadata) {
140
+ const out = {};
141
+ for (const [key, value] of Object.entries(metadata)) {
142
+ out[key.startsWith(AI_SDK_METADATA_PREFIX) ? key : `${AI_SDK_METADATA_PREFIX}${key}`] = value;
143
+ }
144
+ return out;
145
+ }
146
+ function readHandoffField(metadata, suffix) {
147
+ for (const prefix of HANDOFF_ATTRIBUTE_PREFIXES) {
148
+ const value = metadata[`${prefix}${suffix}`];
149
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
150
+ }
151
+ return void 0;
152
+ }
153
+ function readDetachedChildLink(metadata) {
154
+ if (!metadata) return void 0;
155
+ const parentEventId = readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.parentEventId);
156
+ if (!parentEventId) return void 0;
157
+ return {
158
+ parentEventId,
159
+ parentSpanId: readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.parentSpanId),
160
+ name: readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.name)
161
+ };
162
+ }
163
+ var BAGGAGE_KEYS = {
164
+ eventId: "raindrop-event-id",
165
+ childEventId: "raindrop-child-event-id",
166
+ name: "raindrop-handoff-name",
167
+ convoId: "raindrop-convo-id",
168
+ userId: "raindrop-user-id",
169
+ // The dispatch span and its trace, carried as fields as well as in
170
+ // `traceparent`. Not redundant: OTel's HTTP instrumentations rewrite
171
+ // `traceparent` from whatever span is current as the request goes out, so by
172
+ // the time the child reads it the ids name the HTTP client span and its trace
173
+ // instead of the dispatch. The child would then point its reverse reference at
174
+ // a span the launcher's UI does not know as a dispatch. A field states which
175
+ // span it means rather than meaning "whoever wrote this header last".
176
+ dispatchSpanId: "raindrop-dispatch-span-id",
177
+ traceId: "raindrop-trace-id"
178
+ };
179
+ var TRACEPARENT_HEADER = "traceparent";
180
+ var BAGGAGE_HEADER = "baggage";
181
+ var W3C_TRACE_ID = /^[0-9a-f]{32}$/;
182
+ var W3C_SPAN_ID = /^[0-9a-f]{16}$/;
183
+ function isW3CTraceId(value) {
184
+ return value !== void 0 && W3C_TRACE_ID.test(value);
185
+ }
186
+ function isW3CSpanId(value) {
187
+ return value !== void 0 && W3C_SPAN_ID.test(value);
188
+ }
189
+ var warnedNonConformantIds = false;
190
+ function warnNonConformantIdsOnce(traceId, spanId) {
191
+ if (warnedNonConformantIds) return;
192
+ warnedNonConformantIds = true;
193
+ console.warn(
194
+ `[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.`
195
+ );
196
+ }
197
+ var HANDOFF_HEADER = "x-raindrop-handoff";
198
+ var LANGSMITH_TRACE_HEADER = "langsmith-trace";
199
+ var LANGSMITH_METADATA_BAGGAGE_KEY = "langsmith-metadata";
200
+ function encodeBaggage(entries) {
201
+ return Object.entries(entries).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(",");
202
+ }
203
+ function decodeBaggage(header) {
204
+ if (!header) return {};
205
+ const parsed = {};
206
+ for (const part of header.split(",")) {
207
+ const [rawKey, ...rest] = part.split("=");
208
+ if (!rawKey || rest.length === 0) continue;
209
+ const key = rawKey.trim();
210
+ let value;
211
+ try {
212
+ value = decodeURIComponent(rest.join("=").trim());
213
+ } catch (e) {
214
+ value = rest.join("=").trim();
215
+ }
216
+ if (key in parsed && parsed[key] !== value) throw new ConflictingCarrierError(key);
217
+ parsed[key] = value;
218
+ }
219
+ return parsed;
220
+ }
221
+ function carrierBaggage(carrier) {
222
+ return {
223
+ [BAGGAGE_KEYS.eventId]: carrier.eventId,
224
+ [BAGGAGE_KEYS.childEventId]: carrier.childEventId,
225
+ [BAGGAGE_KEYS.name]: carrier.name,
226
+ [BAGGAGE_KEYS.convoId]: carrier.convoId,
227
+ [BAGGAGE_KEYS.userId]: carrier.userId,
228
+ [BAGGAGE_KEYS.dispatchSpanId]: carrier.spanId,
229
+ [BAGGAGE_KEYS.traceId]: carrier.traceId
230
+ };
231
+ }
232
+ function toHeaders(carrier) {
233
+ const fields = encodeBaggage(carrierBaggage(carrier));
234
+ const headers = {
235
+ [BAGGAGE_HEADER]: fields,
236
+ [HANDOFF_HEADER]: fields
237
+ };
238
+ if (isW3CTraceId(carrier.traceId) && isW3CSpanId(carrier.spanId)) {
239
+ headers[TRACEPARENT_HEADER] = `00-${carrier.traceId}-${carrier.spanId}-01`;
240
+ }
241
+ return headers;
242
+ }
243
+ function langsmithStamp() {
244
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "000000");
245
+ }
246
+ function toLangSmithHeaders(carrier) {
247
+ const stamp = langsmithStamp();
248
+ const dottedOrder = `${stamp}Z${carrier.traceId}.${stamp}Z${carrier.spanId}`;
249
+ return {
250
+ [LANGSMITH_TRACE_HEADER]: dottedOrder,
251
+ [BAGGAGE_HEADER]: encodeBaggage({
252
+ [LANGSMITH_METADATA_BAGGAGE_KEY]: JSON.stringify(carrierBaggage(carrier))
253
+ }),
254
+ // Carried here too: a LangSmith-shaped carrier is rewritten in transit
255
+ // exactly like the native one, and an injected `traceparent` is preferred
256
+ // over the dotted order on read.
257
+ [HANDOFF_HEADER]: encodeBaggage(carrierBaggage(carrier))
258
+ };
259
+ }
260
+ function isHeaderLookup(headers) {
261
+ return "get" in headers && typeof headers.get === "function";
262
+ }
263
+ var ConflictingCarrierError = class extends Error {
264
+ };
265
+ function readHeader(headers, name) {
266
+ var _a, _b, _c;
267
+ if (isHeaderLookup(headers)) return (_a = headers.get(name)) != null ? _a : void 0;
268
+ const value = (_c = headers[name]) != null ? _c : (_b = Object.entries(headers).find(([key]) => key.toLowerCase() === name)) == null ? void 0 : _b[1];
269
+ if (!Array.isArray(value)) return value;
270
+ const distinct = new Set(value);
271
+ if (distinct.size > 1) throw new ConflictingCarrierError(name);
272
+ return value[0];
273
+ }
274
+ function readTraceHeader(headers, name) {
275
+ const value = readHeader(headers, name);
276
+ if (!(value == null ? void 0 : value.includes(","))) return value;
277
+ const copies = value.split(",").map((copy) => copy.trim());
278
+ if (new Set(copies).size > 1) throw new ConflictingCarrierError(name);
279
+ return copies[0];
280
+ }
281
+ function fromHeaders(headers) {
282
+ var _a, _b;
283
+ let baggage;
284
+ let handoff;
285
+ let traceparent;
286
+ let langsmithTrace;
287
+ try {
288
+ baggage = decodeBaggage(readHeader(headers, BAGGAGE_HEADER));
289
+ handoff = decodeBaggage(readHeader(headers, HANDOFF_HEADER));
290
+ traceparent = readTraceHeader(headers, TRACEPARENT_HEADER);
291
+ langsmithTrace = readTraceHeader(headers, LANGSMITH_TRACE_HEADER);
292
+ } catch (error) {
293
+ if (error instanceof ConflictingCarrierError) return null;
294
+ throw error;
295
+ }
296
+ const langsmithMetadata = baggage[LANGSMITH_METADATA_BAGGAGE_KEY];
297
+ const nested = langsmithMetadata ? safeParseStringRecord(langsmithMetadata) : {};
298
+ for (const [key, value] of Object.entries(nested)) {
299
+ if (key in baggage && baggage[key] !== value) return null;
300
+ }
301
+ const fields = {
302
+ ...baggage,
303
+ ...nested,
304
+ // Last, so it wins: the standard headers may have been rewritten in transit
305
+ // by a propagator, this one is only ever written by us.
306
+ ...handoff
307
+ };
308
+ let traceId;
309
+ let spanId;
310
+ if (traceparent) {
311
+ const [, parsedTraceId, parsedSpanId] = traceparent.split("-");
312
+ if (isW3CTraceId(parsedTraceId) && isW3CSpanId(parsedSpanId)) {
313
+ traceId = parsedTraceId;
314
+ spanId = parsedSpanId;
315
+ }
316
+ }
317
+ if (!traceId && !spanId && langsmithTrace) {
318
+ const segments = langsmithTrace.split(".");
319
+ traceId = (_a = segments[0]) == null ? void 0 : _a.split("Z").pop();
320
+ spanId = (_b = segments[segments.length - 1]) == null ? void 0 : _b.split("Z").pop();
321
+ }
322
+ traceId = fields[BAGGAGE_KEYS.traceId] || traceId;
323
+ spanId = fields[BAGGAGE_KEYS.dispatchSpanId] || spanId;
324
+ const eventId = fields[BAGGAGE_KEYS.eventId];
325
+ if (!traceId || !spanId || !eventId) return null;
326
+ if (!isW3CTraceId(traceId) || !isW3CSpanId(spanId)) {
327
+ warnNonConformantIdsOnce(traceId, spanId);
328
+ }
329
+ const carrier = { traceId, spanId, eventId };
330
+ const childEventId = fields[BAGGAGE_KEYS.childEventId];
331
+ if (childEventId) carrier.childEventId = childEventId;
332
+ const name = fields[BAGGAGE_KEYS.name];
333
+ if (name) carrier.name = name;
334
+ const convoId = fields[BAGGAGE_KEYS.convoId];
335
+ if (convoId) carrier.convoId = convoId;
336
+ const userId = fields[BAGGAGE_KEYS.userId];
337
+ if (userId) carrier.userId = userId;
338
+ return carrier;
339
+ }
340
+ 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.`;
341
+ var UNRESOLVED_CARRIER_WARNED_KEY = /* @__PURE__ */ Symbol.for("raindrop.handoff.warnedUnresolvedCarrier");
342
+ function warnedHolder() {
343
+ return globalThis;
344
+ }
345
+ function resetUnresolvedCarrierWarning() {
346
+ warnedHolder()[UNRESOLVED_CARRIER_WARNED_KEY] = false;
347
+ }
348
+ function warnUnresolvedCarrier(headers, carrier) {
349
+ if (carrier) return;
350
+ if (!headers) return;
351
+ const holder = warnedHolder();
352
+ if (holder[UNRESOLVED_CARRIER_WARNED_KEY]) return;
353
+ holder[UNRESOLVED_CARRIER_WARNED_KEY] = true;
354
+ console.warn(UNRESOLVED_CARRIER_WARNING);
355
+ }
356
+ function safeParseStringRecord(raw) {
357
+ try {
358
+ const parsed = JSON.parse(raw);
359
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
360
+ const result = {};
361
+ for (const [key, value] of Object.entries(parsed)) {
362
+ if (typeof value === "string") result[key] = value;
363
+ }
364
+ return result;
365
+ } catch (e) {
366
+ return {};
367
+ }
368
+ }
64
369
  function runWithTracingSuppressed(fn) {
65
370
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
66
371
  if (typeof hook !== "function") return fn();
@@ -824,6 +1129,193 @@ var EventShipper = class {
824
1129
  }
825
1130
  }
826
1131
  };
1132
+ var MODEL_USAGE_ATTRIBUTES = {
1133
+ providerName: "gen_ai.provider.name",
1134
+ requestModel: "gen_ai.request.model",
1135
+ responseModel: "gen_ai.response.model",
1136
+ inputTokens: "gen_ai.usage.input_tokens",
1137
+ outputTokens: "gen_ai.usage.output_tokens",
1138
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
1139
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
1140
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
1141
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
1142
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
1143
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
1144
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
1145
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
1146
+ };
1147
+ var MODEL_PROVIDER_NAMES = [
1148
+ ["google.vertex", "google-vertex"],
1149
+ ["vertex.anthropic", "google-vertex"],
1150
+ ["amazon-bedrock", "amazon-bedrock"],
1151
+ ["bedrock", "amazon-bedrock"],
1152
+ ["anthropic", "anthropic"],
1153
+ ["openai", "openai"],
1154
+ ["google", "google"],
1155
+ ["azure", "azure"],
1156
+ ["openrouter", "openrouter"]
1157
+ ];
1158
+ function canonicalModelProvider(provider) {
1159
+ var _a;
1160
+ const normalizedProvider = provider.trim().toLowerCase();
1161
+ if (normalizedProvider === "gateway") return "vercel";
1162
+ const match = MODEL_PROVIDER_NAMES.find(
1163
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
1164
+ );
1165
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : provider;
1166
+ }
1167
+ function tokenCount(value) {
1168
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1169
+ }
1170
+ function exclusiveTokens({
1171
+ explicit,
1172
+ total,
1173
+ parts,
1174
+ semantics
1175
+ }) {
1176
+ if (explicit !== void 0) return explicit;
1177
+ if (total === void 0) return void 0;
1178
+ if (semantics === "exclusive") return total;
1179
+ const observedParts = parts.filter((part) => part !== void 0 && part > 0);
1180
+ if (semantics === void 0 && observedParts.length > 0) return void 0;
1181
+ const exclusive = total - observedParts.reduce((sum, part) => sum + part, 0);
1182
+ return exclusive >= 0 ? exclusive : void 0;
1183
+ }
1184
+ function buildModelUsageAttributes(usage) {
1185
+ const inputTokens = tokenCount(usage.inputTokens);
1186
+ const outputTokens = tokenCount(usage.outputTokens);
1187
+ const explicitNonCachedInputTokens = tokenCount(usage.nonCachedInputTokens);
1188
+ const explicitNonReasoningOutputTokens = tokenCount(usage.nonReasoningOutputTokens);
1189
+ const reasoningTokens = tokenCount(usage.reasoningTokens);
1190
+ const cacheReadInputTokens = tokenCount(usage.cacheReadInputTokens);
1191
+ const cacheWriteInputTokens = tokenCount(usage.cacheWriteInputTokens);
1192
+ const nonCachedInputTokens = exclusiveTokens({
1193
+ explicit: explicitNonCachedInputTokens,
1194
+ total: inputTokens,
1195
+ parts: [cacheReadInputTokens, cacheWriteInputTokens],
1196
+ semantics: usage.inputTokenSemantics
1197
+ });
1198
+ const nonReasoningOutputTokens = exclusiveTokens({
1199
+ explicit: explicitNonReasoningOutputTokens,
1200
+ total: outputTokens,
1201
+ parts: [reasoningTokens],
1202
+ semantics: usage.outputTokenSemantics
1203
+ });
1204
+ const exclusivePoolAttributes = nonCachedInputTokens !== void 0 && nonReasoningOutputTokens !== void 0 ? [
1205
+ // backend consumes this complete raindrop.* set as one atomic billing-pool contract.
1206
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonCachedInputTokens, nonCachedInputTokens),
1207
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonReasoningOutputTokens, nonReasoningOutputTokens),
1208
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningOutputTokens, reasoningTokens),
1209
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadTokens, cacheReadInputTokens),
1210
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteTokens, cacheWriteInputTokens)
1211
+ ] : [];
1212
+ return [
1213
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, inputTokens),
1214
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, outputTokens),
1215
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, reasoningTokens),
1216
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, cacheReadInputTokens),
1217
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, cacheWriteInputTokens),
1218
+ ...exclusivePoolAttributes
1219
+ ].filter((attribute) => attribute !== void 0);
1220
+ }
1221
+ var STRING_ALIASES = {
1222
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
1223
+ };
1224
+ var NUMBER_ALIASES = {
1225
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
1226
+ "gen_ai.usage.prompt_tokens",
1227
+ "ai.usage.prompt_tokens",
1228
+ "ai.usage.promptTokens",
1229
+ "ai.usage.input_tokens",
1230
+ "ai.usage.inputTokens"
1231
+ ],
1232
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
1233
+ "gen_ai.usage.completion_tokens",
1234
+ "ai.usage.completion_tokens",
1235
+ "ai.usage.completionTokens",
1236
+ "ai.usage.output_tokens",
1237
+ "ai.usage.outputTokens"
1238
+ ],
1239
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
1240
+ "gen_ai.usage.reasoning.output_tokens",
1241
+ "ai.usage.reasoningTokens",
1242
+ "ai.usage.thoughts_tokens"
1243
+ ],
1244
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
1245
+ "gen_ai.usage.cache_read_tokens",
1246
+ "gen_ai.usage.cache_read.input_tokens",
1247
+ "ai.usage.cachedInputTokens",
1248
+ "ai.usage.cached_tokens",
1249
+ "ai.usage.cache_read_tokens",
1250
+ "ai.usage.cache_read_input_tokens",
1251
+ "ai.usage.cacheReadInputTokens"
1252
+ ],
1253
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
1254
+ "gen_ai.usage.cache_creation_input_tokens",
1255
+ "gen_ai.usage.cache_creation.input_tokens",
1256
+ "ai.usage.cacheWriteInputTokens",
1257
+ "ai.usage.cache_creation_input_tokens",
1258
+ "ai.usage.cacheCreationInputTokens"
1259
+ ]
1260
+ };
1261
+ function hasAttribute(attributes, key) {
1262
+ return attributes.some((attribute) => attribute.key === key);
1263
+ }
1264
+ function findStringAttribute(attributes, keys) {
1265
+ return attributes.find(
1266
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
1267
+ );
1268
+ }
1269
+ function findNumberAttribute(attributes, keys) {
1270
+ return attributes.find((attribute) => {
1271
+ if (!keys.includes(attribute.key)) return false;
1272
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
1273
+ return true;
1274
+ }
1275
+ const intValue = attribute.value.intValue;
1276
+ return integerValue(intValue) !== void 0;
1277
+ });
1278
+ }
1279
+ function integerValue(value) {
1280
+ if (typeof value === "number") {
1281
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1282
+ }
1283
+ if (typeof value !== "string") return void 0;
1284
+ const parsed = Number(value);
1285
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
1286
+ }
1287
+ function appendAlias(attributes, target, source) {
1288
+ if (!source || hasAttribute(attributes, target)) return attributes;
1289
+ return [...attributes, { key: target, value: { ...source.value } }];
1290
+ }
1291
+ function appendProviderAlias(attributes) {
1292
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
1293
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
1294
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
1295
+ const attribute = attrString(
1296
+ MODEL_USAGE_ATTRIBUTES.providerName,
1297
+ canonicalModelProvider(source.value.stringValue)
1298
+ );
1299
+ return attribute ? [...attributes, attribute] : attributes;
1300
+ }
1301
+ function normalizeModelUsageSpan(span) {
1302
+ const original = span.attributes;
1303
+ if (!original || original.length === 0) return span;
1304
+ const responseModel = findStringAttribute(original, [
1305
+ MODEL_USAGE_ATTRIBUTES.responseModel,
1306
+ "ai.response.model"
1307
+ ]);
1308
+ if (!responseModel) return span;
1309
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
1310
+ attributes = appendProviderAlias(attributes);
1311
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
1312
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
1313
+ }
1314
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
1315
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
1316
+ }
1317
+ return attributes === original ? span : { ...span, attributes };
1318
+ }
827
1319
  var DEFAULT_SECRET_KEY_NAMES = [
828
1320
  "apikey",
829
1321
  "apisecret",
@@ -1024,6 +1516,10 @@ var TraceShipper = class {
1024
1516
  return null;
1025
1517
  }
1026
1518
  }
1519
+ try {
1520
+ current = normalizeModelUsageSpan(current);
1521
+ } catch (e) {
1522
+ }
1027
1523
  if (!this.disableDefaultRedaction) {
1028
1524
  current = defaultTransformSpan(current);
1029
1525
  }
@@ -1357,7 +1853,7 @@ async function* asyncGeneratorWithCurrent(span, gen) {
1357
1853
  // package.json
1358
1854
  var package_default = {
1359
1855
  name: "@raindrop-ai/ai-sdk",
1360
- version: "0.2.0"};
1856
+ version: "0.3.0"};
1361
1857
 
1362
1858
  // src/internal/version.ts
1363
1859
  var libraryName = package_default.name;
@@ -2054,6 +2550,15 @@ function attrsFromHeaders(headers) {
2054
2550
  if (!isRecord(headers)) return [];
2055
2551
  return Object.entries(headers).filter(([, v]) => typeof v === "string").map(([k, v]) => attrString(`ai.request.headers.${k}`, v));
2056
2552
  }
2553
+ function attrsFromModelProvider(provider) {
2554
+ if (typeof provider !== "string" || provider.length === 0) return [];
2555
+ const providerName = canonicalModelProvider(provider);
2556
+ return [
2557
+ attrString("ai.model.provider", provider),
2558
+ attrString(MODEL_USAGE_ATTRIBUTES.providerName, providerName),
2559
+ attrString("gen_ai.system", provider)
2560
+ ];
2561
+ }
2057
2562
  function attrsFromSettings(args) {
2058
2563
  if (!isRecord(args)) return [];
2059
2564
  const result = [];
@@ -2236,6 +2741,473 @@ function runWithParentToolContext(ctx, fn) {
2236
2741
  return getStorage2().run(ctx, fn);
2237
2742
  }
2238
2743
 
2744
+ // src/internal/subagents.ts
2745
+ var SUBAGENT_CANCELLED_OUTPUT = "Cancelled before producing a result.";
2746
+ var SUBAGENT_ABORTED_OUTPUT = "Aborted before producing a result.";
2747
+ var SUBAGENT_FAILED_OPERATION_ID = SUBAGENT_ABORT_OPERATION_ID;
2748
+ var MAX_SETTLED_EVENT_IDS = 1024;
2749
+ function isTraceCarrier(value) {
2750
+ const candidate = value;
2751
+ return typeof candidate.traceId === "string" && typeof candidate.spanId === "string" && typeof candidate.eventId === "string";
2752
+ }
2753
+ function attrsFrom(metadata) {
2754
+ return Object.entries(withMetadataPrefix(metadata)).map(([key, value]) => attrString(key, value));
2755
+ }
2756
+ function attrsExact(attributes) {
2757
+ return Object.entries(attributes).map(([key, value]) => attrString(key, value));
2758
+ }
2759
+ function handoffSpanAttrs(link) {
2760
+ if (!link) return [];
2761
+ return attrsFrom(detachedChildMetadata(link));
2762
+ }
2763
+ function subagentNameAttrs(name, link) {
2764
+ if (!name || (link == null ? void 0 : link.name)) return [];
2765
+ return attrsFrom({ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: name });
2766
+ }
2767
+ function cancelledSpanAttrs() {
2768
+ return attrsExact(cancelledTerminalAttributes());
2769
+ }
2770
+ function describeAbortReason(reason) {
2771
+ if (reason instanceof Error) return `Aborted: ${reason.message}`;
2772
+ if (typeof reason === "string" && reason.trim().length > 0) return reason;
2773
+ if (reason === void 0 || reason === null) return SUBAGENT_ABORTED_OUTPUT;
2774
+ const serialized = boundedStringify(reason);
2775
+ return serialized ? `Aborted: ${serialized}` : SUBAGENT_ABORTED_OUTPUT;
2776
+ }
2777
+ function stampOpenSpan(span, metadata) {
2778
+ if (span.endTimeUnixNano) return;
2779
+ span.attributes.push(...attrsFrom(metadata));
2780
+ }
2781
+ function stampOpenSpanExact(span, attributes) {
2782
+ if (span.endTimeUnixNano) return;
2783
+ span.attributes.push(...attrsExact(attributes));
2784
+ }
2785
+ var SubagentApi = class {
2786
+ constructor(opts) {
2787
+ var _a;
2788
+ this.opts = opts;
2789
+ this.settledEventIds = (_a = opts.settledEventIds) != null ? _a : /* @__PURE__ */ new Set();
2790
+ }
2791
+ /** True when this child's outcome has already been reported. */
2792
+ wasSettledExplicitly(eventId) {
2793
+ return this.settledEventIds.has(eventId);
2794
+ }
2795
+ /**
2796
+ * Record an outcome reported somewhere other than `cancel()` / `fail()`, so
2797
+ * first-outcome-wins holds across every path that can state one.
2798
+ *
2799
+ * An aborted generation is such a path: an AbortSignal cancellation IS the
2800
+ * run's outcome, and the `fail(err)` that a catch block around the aborted
2801
+ * call naturally reports would otherwise error the child's spans — which is
2802
+ * the only thing separating `failed` from `cancelled`.
2803
+ */
2804
+ noteSettled(eventId) {
2805
+ this.remember(eventId);
2806
+ }
2807
+ remember(eventId) {
2808
+ if (this.settledEventIds.has(eventId)) return;
2809
+ if (this.settledEventIds.size >= MAX_SETTLED_EVENT_IDS) {
2810
+ const oldest = this.settledEventIds.values().next();
2811
+ if (!oldest.done) this.settledEventIds.delete(oldest.value);
2812
+ }
2813
+ this.settledEventIds.add(eventId);
2814
+ }
2815
+ /**
2816
+ * Record a detached sub-agent on this turn: allocate the child's event id,
2817
+ * stamp the dispatch, and return the headers to send with the job.
2818
+ *
2819
+ * This dispatches nothing — you do, moments later, with the returned headers.
2820
+ * Nothing here waits for the child either, or claims to know how it is doing.
2821
+ * Readers derive that from the child's own event.
2822
+ */
2823
+ subagent(options = {}) {
2824
+ var _a;
2825
+ const childEventId = (_a = options.childEventId) != null ? _a : randomUUID();
2826
+ const name = options.name;
2827
+ try {
2828
+ return this.recordLaunch(childEventId, name, options);
2829
+ } catch (err) {
2830
+ if (this.opts.debug) {
2831
+ console.warn(
2832
+ `[raindrop-ai/ai-sdk] sub-agent dispatch not recorded: ${err instanceof Error ? err.message : err}`
2833
+ );
2834
+ }
2835
+ return { childEventId, name, headers: {}, carrier: null };
2836
+ }
2837
+ }
2838
+ recordLaunch(childEventId, name, options) {
2839
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
2840
+ const active = (_a = this.opts.spanSource) == null ? void 0 : _a.activeDispatch();
2841
+ const inherited = getContextManager().getParentSpanIds();
2842
+ 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;
2843
+ 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;
2844
+ 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;
2845
+ if (!eventId) {
2846
+ return { childEventId, name, headers: {}, carrier: null };
2847
+ }
2848
+ const dispatch = detachedDispatchMetadata({ childEventId, name });
2849
+ const toolEvents = toolEventsAllowMetadata();
2850
+ for (const span of (_l = active == null ? void 0 : active.turnSpans) != null ? _l : []) {
2851
+ stampOpenSpan(span, toolEvents);
2852
+ }
2853
+ let dispatchIds;
2854
+ if (active == null ? void 0 : active.dispatchSpan) {
2855
+ stampOpenSpan(active.dispatchSpan, dispatch);
2856
+ dispatchIds = active.dispatchSpan.ids;
2857
+ } else if (this.opts.sendTraces) {
2858
+ dispatchIds = this.synthesizeDispatchSpan({
2859
+ childEventId,
2860
+ name,
2861
+ toolName: options.toolName,
2862
+ eventId,
2863
+ userId,
2864
+ convoId,
2865
+ input: options.input,
2866
+ // No active turn means no telemetry setting to honor: `input` is then
2867
+ // recorded on the explicit say-so of whoever passed it.
2868
+ recordInputs: (_m = active == null ? void 0 : active.recordInputs) != null ? _m : true,
2869
+ dispatch,
2870
+ parent: (inherited == null ? void 0 : inherited.eventId) === eventId ? inherited : void 0
2871
+ }).ids;
2872
+ }
2873
+ const traceId = base64ToHex((_o = (_n = dispatchIds == null ? void 0 : dispatchIds.traceIdB64) != null ? _n : inherited == null ? void 0 : inherited.traceIdB64) != null ? _o : "");
2874
+ const spanId = base64ToHex((_p = dispatchIds == null ? void 0 : dispatchIds.spanIdB64) != null ? _p : "");
2875
+ if (!traceId || !spanId) {
2876
+ if (this.opts.debug) {
2877
+ console.warn(
2878
+ "[raindrop-ai/ai-sdk] sub-agent dispatch recorded without a carrier: no dispatch span to reference (traces disabled?)"
2879
+ );
2880
+ }
2881
+ return { childEventId, name, headers: {}, carrier: null };
2882
+ }
2883
+ const carrier = {
2884
+ traceId,
2885
+ spanId,
2886
+ eventId,
2887
+ childEventId,
2888
+ ...name ? { name } : {},
2889
+ ...convoId ? { convoId } : {},
2890
+ ...userId ? { userId } : {}
2891
+ };
2892
+ return { childEventId, name, headers: toHeaders(carrier), carrier };
2893
+ }
2894
+ /**
2895
+ * Record a dispatch span for a launch with no open tool-call span to decorate:
2896
+ * a launch from outside a tool's `execute`, or from a caller that is not an
2897
+ * LLM turn at all (an HTTP handler that enqueues jobs). Shaped as a tool call
2898
+ * so it reads identically to a model-issued launch.
2899
+ */
2900
+ synthesizeDispatchSpan(args) {
2901
+ var _a;
2902
+ const spanName = (_a = args.toolName) != null ? _a : DEFAULT_DISPATCH_SPAN_NAME;
2903
+ const span = this.opts.traceShipper.startSpan({
2904
+ name: spanName,
2905
+ parent: args.parent,
2906
+ eventId: args.eventId,
2907
+ userId: args.userId,
2908
+ convoId: args.convoId,
2909
+ operationId: "ai.toolCall",
2910
+ attributes: [
2911
+ attrString("operation.name", "ai.toolCall"),
2912
+ attrString("resource.name", spanName),
2913
+ attrString("ai.toolCall.name", spanName),
2914
+ attrString("ai.toolCall.id", `call_${args.childEventId.replace(/-/g, "").slice(0, 12)}`),
2915
+ // Gated like every other input the turn's spans record: a caller that
2916
+ // disabled input capture must not leak the payload through the one
2917
+ // span this API synthesizes on its behalf.
2918
+ ...args.recordInputs ? [attrString("ai.toolCall.args", boundedStringify(args.input))] : [],
2919
+ ...attrsFrom(args.dispatch)
2920
+ ]
2921
+ });
2922
+ this.opts.traceShipper.endSpan(span, {
2923
+ // A dispatch returns a handle, not an answer — the answer is the child's.
2924
+ attributes: [
2925
+ attrString(
2926
+ "ai.toolCall.result",
2927
+ boundedStringify({ jobId: args.childEventId, status: "accepted" })
2928
+ )
2929
+ ]
2930
+ });
2931
+ return span;
2932
+ }
2933
+ /**
2934
+ * Adopt a carrier as the current run's parent reference.
2935
+ *
2936
+ * A missing carrier is a legitimate state — the child was invoked directly
2937
+ * rather than dispatched — so this always returns a usable run and leaves
2938
+ * `parent` null. Call sites stay unconditional.
2939
+ */
2940
+ resume(carrierOrHeaders, options = {}) {
2941
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2942
+ const carrier = carrierOrHeaders ? isTraceCarrier(carrierOrHeaders) ? carrierOrHeaders : this.readCarrier(carrierOrHeaders) : null;
2943
+ warnUnresolvedCarrier(carrierOrHeaders, carrier);
2944
+ const name = (_a = carrier == null ? void 0 : carrier.name) != null ? _a : options.name;
2945
+ const eventId = (_c = (_b = carrier == null ? void 0 : carrier.childEventId) != null ? _b : options.eventId) != null ? _c : randomUUID();
2946
+ 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;
2947
+ 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;
2948
+ const parent = carrier ? {
2949
+ parentEventId: carrier.eventId,
2950
+ ...carrier.spanId ? { parentSpanId: carrier.spanId } : {},
2951
+ ...name ? { name } : {}
2952
+ } : null;
2953
+ return {
2954
+ eventId,
2955
+ name,
2956
+ userId,
2957
+ convoId,
2958
+ parent,
2959
+ metadata: {
2960
+ eventId,
2961
+ ...userId ? { userId } : {},
2962
+ ...convoId ? { convoId } : {},
2963
+ ...parent ? { subagent: parent } : {}
2964
+ },
2965
+ cancel: (reason) => this.settle(eventId, { userId, convoId, name, cancelled: true, output: reason }),
2966
+ fail: (reason) => this.settle(eventId, {
2967
+ userId,
2968
+ convoId,
2969
+ name,
2970
+ handoff: parent != null ? parent : void 0,
2971
+ cancelled: false,
2972
+ output: describeAbortReason(reason),
2973
+ error: reason
2974
+ })
2975
+ };
2976
+ }
2977
+ /**
2978
+ * A carrier arrives from outside this process, so a hostile or merely broken
2979
+ * header bag must cost the job its link, never its run.
2980
+ */
2981
+ readCarrier(headers) {
2982
+ try {
2983
+ return fromHeaders(headers);
2984
+ } catch (err) {
2985
+ if (this.opts.debug) {
2986
+ console.warn(
2987
+ `[raindrop-ai/ai-sdk] sub-agent carrier ignored: ${err instanceof Error ? err.message : err}`
2988
+ );
2989
+ }
2990
+ return null;
2991
+ }
2992
+ }
2993
+ /**
2994
+ * Mark a failed child's own spans as errored.
2995
+ *
2996
+ * Reporting the reason as output is what gets the child an event at all, but
2997
+ * output alone is not enough: status is derived from span shape, and a child
2998
+ * with final output and no error spans derives `finished`. A failed job
2999
+ * reporting success is worse than one reporting nothing, so the failure has
3000
+ * to land on the telemetry too, not just in the text.
3001
+ *
3002
+ * This is the child describing its own run, not a caller writing a status it
3003
+ * cannot observe — the child's own spans are exactly where the contract puts
3004
+ * failure.
3005
+ */
3006
+ recordFailureOnSpans(eventId, args, reason) {
3007
+ var _a, _b, _c;
3008
+ if (!this.opts.sendTraces) return;
3009
+ const failure = (_a = args.error) != null ? _a : reason;
3010
+ let marked = 0;
3011
+ for (const span2 of (_c = (_b = this.opts.spanSource) == null ? void 0 : _b.openSpansForEvent(eventId)) != null ? _c : []) {
3012
+ if (span2.endTimeUnixNano) continue;
3013
+ this.opts.traceShipper.endSpan(span2, { error: failure });
3014
+ marked++;
3015
+ }
3016
+ if (marked > 0) return;
3017
+ const spanName = SUBAGENT_ABORT_SPAN_NAME;
3018
+ const span = this.opts.traceShipper.startSpan({
3019
+ name: spanName,
3020
+ eventId,
3021
+ userId: args.userId,
3022
+ convoId: args.convoId,
3023
+ // Required, not decorative: ingest drops any span carrying no
3024
+ // `ai.operationId` (or traceloop / `gen_ai.*` key) and still answers 200,
3025
+ // so without this the failure disappears silently. The value is
3026
+ // deliberately none of the generate/stream/tool/embed forms, which is
3027
+ // what classifies the span as INTERNAL rather than mislabelling a failed
3028
+ // job as a model call or a tool call.
3029
+ operationId: SUBAGENT_FAILED_OPERATION_ID,
3030
+ // Carries the reverse reference like every other span the child emits:
3031
+ // this is often the child's ONLY span, and a span read on its own is the
3032
+ // whole reason the reference cannot live on the root alone. The name is
3033
+ // stamped even for an unlinked run, which has no caller to reference —
3034
+ // `span_name` no longer says which sub-agent this was, so the attribute
3035
+ // is the only thing that can.
3036
+ attributes: [
3037
+ attrString("resource.name", spanName),
3038
+ ...handoffSpanAttrs(args.handoff),
3039
+ ...subagentNameAttrs(args.name, args.handoff)
3040
+ ]
3041
+ });
3042
+ this.opts.traceShipper.endSpan(span, { error: failure });
3043
+ }
3044
+ async settle(eventId, args) {
3045
+ var _a, _b;
3046
+ if (this.settledEventIds.has(eventId)) {
3047
+ if (this.opts.debug) {
3048
+ console.warn(
3049
+ `[raindrop-ai/ai-sdk] sub-agent ${eventId} already reported an outcome; ignoring the second one`
3050
+ );
3051
+ }
3052
+ return;
3053
+ }
3054
+ this.remember(eventId);
3055
+ const output = args.output && args.output.trim().length > 0 ? args.output : args.cancelled ? SUBAGENT_CANCELLED_OUTPUT : SUBAGENT_ABORTED_OUTPUT;
3056
+ try {
3057
+ if (args.cancelled) {
3058
+ for (const span of (_b = (_a = this.opts.spanSource) == null ? void 0 : _a.openSpansForEvent(eventId)) != null ? _b : []) {
3059
+ stampOpenSpanExact(span, cancelledTerminalAttributes());
3060
+ }
3061
+ } else {
3062
+ this.recordFailureOnSpans(eventId, args, output);
3063
+ }
3064
+ } catch (err) {
3065
+ if (this.opts.debug) {
3066
+ console.warn(
3067
+ `[raindrop-ai/ai-sdk] sub-agent outcome not recorded on spans: ${err instanceof Error ? err.message : err}`
3068
+ );
3069
+ }
3070
+ }
3071
+ if (!this.opts.sendEvents) return;
3072
+ await this.opts.eventShipper.patch(eventId, {
3073
+ userId: args.userId,
3074
+ convoId: args.convoId,
3075
+ // An event is only created from a run that reported something. A child
3076
+ // that dies silently produces no event at all, which pins the caller on
3077
+ // `queued` forever — indistinguishable from a job that never started.
3078
+ output,
3079
+ ...args.cancelled ? { properties: { [HANDOFF_TERMINAL_PROPERTY_KEY]: HANDOFF_TERMINAL_CANCELLED } } : {},
3080
+ isPending: false
3081
+ }).catch((err) => {
3082
+ if (this.opts.debug) {
3083
+ console.warn(
3084
+ `[raindrop-ai/ai-sdk] sub-agent outcome patch failed: ${err instanceof Error ? err.message : err}`
3085
+ );
3086
+ }
3087
+ });
3088
+ }
3089
+ };
3090
+
3091
+ // src/internal/usage.ts
3092
+ function firstTokenCount(...values) {
3093
+ for (const value of values) {
3094
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
3095
+ return value;
3096
+ }
3097
+ }
3098
+ return void 0;
3099
+ }
3100
+ function flatUsageSemantics(provider) {
3101
+ const normalizedProvider = typeof provider === "string" ? provider.trim().toLowerCase() : void 0;
3102
+ if (normalizedProvider === "anthropic" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("anthropic.")) || normalizedProvider === "vertex.anthropic" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("vertex.anthropic."))) {
3103
+ return {
3104
+ inputTokenSemantics: "exclusive",
3105
+ outputTokenSemantics: "total"
3106
+ };
3107
+ }
3108
+ if (normalizedProvider === "openai" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("openai.")) || normalizedProvider === "azure" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("azure."))) {
3109
+ return {
3110
+ inputTokenSemantics: "total",
3111
+ outputTokenSemantics: "total"
3112
+ };
3113
+ }
3114
+ if (normalizedProvider === "google" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("google."))) {
3115
+ return {
3116
+ inputTokenSemantics: "total",
3117
+ // Google v5 reports candidate output and thoughts separately.
3118
+ outputTokenSemantics: "exclusive"
3119
+ };
3120
+ }
3121
+ return {};
3122
+ }
3123
+ var CACHE_WRITE_METADATA_KEYS = [
3124
+ ["anthropic", "cacheCreationInputTokens"],
3125
+ ["openai", "cacheWriteTokens"],
3126
+ ["bedrock", "cacheWriteInputTokens"]
3127
+ ];
3128
+ function cacheWriteTokensFromProviderMetadata(providerMetadata) {
3129
+ if (!isRecord(providerMetadata)) return void 0;
3130
+ return firstTokenCount(
3131
+ ...CACHE_WRITE_METADATA_KEYS.flatMap(([namespace, key]) => {
3132
+ const scope = providerMetadata[namespace];
3133
+ if (!isRecord(scope)) return [];
3134
+ const usage = scope["usage"];
3135
+ return [scope[key], isRecord(usage) ? usage[key] : void 0];
3136
+ })
3137
+ );
3138
+ }
3139
+ function extractUsageMetricsFromUsage(usage, provider, providerMetadata) {
3140
+ if (!isRecord(usage)) return {};
3141
+ const inputTokenValue = usage["inputTokens"];
3142
+ const outputTokenValue = usage["outputTokens"];
3143
+ const inputTokenDetails = usage["inputTokenDetails"];
3144
+ const outputTokenDetails = usage["outputTokenDetails"];
3145
+ const hasStructuredUsage = isRecord(inputTokenValue) || isRecord(outputTokenValue) || isRecord(inputTokenDetails) || isRecord(outputTokenDetails);
3146
+ const inputTokens = firstTokenCount(
3147
+ isRecord(inputTokenValue) ? inputTokenValue["total"] : void 0,
3148
+ inputTokenValue,
3149
+ usage["promptTokens"],
3150
+ usage["prompt_tokens"]
3151
+ );
3152
+ const outputTokens = firstTokenCount(
3153
+ isRecord(outputTokenValue) ? outputTokenValue["total"] : void 0,
3154
+ outputTokenValue,
3155
+ usage["completionTokens"],
3156
+ usage["completion_tokens"]
3157
+ );
3158
+ const reasoningTokens = firstTokenCount(
3159
+ isRecord(outputTokenValue) ? outputTokenValue["reasoning"] : void 0,
3160
+ isRecord(outputTokenDetails) ? outputTokenDetails["reasoningTokens"] : void 0,
3161
+ usage["reasoningTokens"],
3162
+ usage["completionReasoningTokens"],
3163
+ usage["completion_reasoning_tokens"],
3164
+ usage["reasoning_tokens"],
3165
+ usage["thinkingTokens"],
3166
+ usage["thinking_tokens"]
3167
+ );
3168
+ const cacheReadInputTokens = firstTokenCount(
3169
+ isRecord(inputTokenValue) ? inputTokenValue["cacheRead"] : void 0,
3170
+ isRecord(inputTokenDetails) ? inputTokenDetails["cacheReadTokens"] : void 0,
3171
+ usage["cachedInputTokens"],
3172
+ usage["promptCachedTokens"],
3173
+ usage["prompt_cached_tokens"]
3174
+ );
3175
+ const cacheWriteInputTokens = firstTokenCount(
3176
+ isRecord(inputTokenValue) ? inputTokenValue["cacheWrite"] : void 0,
3177
+ isRecord(inputTokenDetails) ? inputTokenDetails["cacheWriteTokens"] : void 0,
3178
+ usage["cacheWriteInputTokens"],
3179
+ usage["cacheCreationInputTokens"],
3180
+ usage["cache_creation_input_tokens"],
3181
+ cacheWriteTokensFromProviderMetadata(providerMetadata)
3182
+ );
3183
+ const nonCachedInputTokens = firstTokenCount(
3184
+ isRecord(inputTokenValue) ? inputTokenValue["noCache"] : void 0,
3185
+ isRecord(inputTokenDetails) ? inputTokenDetails["noCacheTokens"] : void 0
3186
+ );
3187
+ const nonReasoningOutputTokens = firstTokenCount(
3188
+ isRecord(outputTokenValue) ? outputTokenValue["text"] : void 0,
3189
+ isRecord(outputTokenDetails) ? outputTokenDetails["textTokens"] : void 0
3190
+ );
3191
+ const totalTokens = firstTokenCount(
3192
+ usage["totalTokens"],
3193
+ usage["tokens"],
3194
+ usage["total_tokens"],
3195
+ inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0
3196
+ );
3197
+ return {
3198
+ inputTokens,
3199
+ outputTokens,
3200
+ totalTokens,
3201
+ nonCachedInputTokens,
3202
+ nonReasoningOutputTokens,
3203
+ reasoningTokens,
3204
+ cacheReadInputTokens,
3205
+ cachedInputTokens: cacheReadInputTokens,
3206
+ cacheWriteInputTokens,
3207
+ ...hasStructuredUsage ? {} : flatUsageSemantics(provider)
3208
+ };
3209
+ }
3210
+
2239
3211
  // src/internal/raindrop-telemetry-integration.ts
2240
3212
  function hasUnresolvedToolApproval(event) {
2241
3213
  var _a;
@@ -2308,6 +3280,7 @@ var RaindropTelemetryIntegration = class {
2308
3280
  eventName: (_f = callMeta.eventName) != null ? _f : (_e = this.defaultContext) == null ? void 0 : _e.eventName
2309
3281
  };
2310
3282
  const inherited = getContextManager().getParentSpanIds();
3283
+ const handoff = readDetachedChildLink(metadata);
2311
3284
  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;
2312
3285
  const explicitEventId = callMeta.eventId && !eventIdGenerated ? callMeta.eventId : void 0;
2313
3286
  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();
@@ -2337,7 +3310,10 @@ var RaindropTelemetryIntegration = class {
2337
3310
  attrString("ai.toolCall.name", subagentName),
2338
3311
  attrString("raindrop.subagent.name", subagentName),
2339
3312
  attrString("raindrop.agent.role", "subagent"),
2340
- ...parentAttrs
3313
+ ...parentAttrs,
3314
+ // Top of this child's tree, so the reverse reference matters as much
3315
+ // here as on the root: these are the spans a reader lands on first.
3316
+ ...handoffSpanAttrs(handoff)
2341
3317
  ]
2342
3318
  });
2343
3319
  const subagentParentRef = this.spanParentRef(subagentToolCallSpan);
@@ -2350,7 +3326,8 @@ var RaindropTelemetryIntegration = class {
2350
3326
  attributes: [
2351
3327
  attrString("operation.name", "agent.subagent"),
2352
3328
  attrString("raindrop.span.kind", "llm_call"),
2353
- attrString("raindrop.subagent.name", subagentName)
3329
+ attrString("raindrop.subagent.name", subagentName),
3330
+ ...handoffSpanAttrs(handoff)
2354
3331
  ]
2355
3332
  });
2356
3333
  this.traceShipper.endSpan(markerSpan);
@@ -2405,6 +3382,8 @@ var RaindropTelemetryIntegration = class {
2405
3382
  rootParent: rootSpan ? this.spanParentRef(rootSpan) : rootParentOverride != null ? rootParentOverride : inheritedParent,
2406
3383
  stepSpan: void 0,
2407
3384
  stepParent: void 0,
3385
+ stepProvider: void 0,
3386
+ stepModelId: void 0,
2408
3387
  toolSpans: /* @__PURE__ */ new Map(),
2409
3388
  embedSpans: /* @__PURE__ */ new Map(),
2410
3389
  parentContextToolCallIds: /* @__PURE__ */ new Set(),
@@ -2416,7 +3395,9 @@ var RaindropTelemetryIntegration = class {
2416
3395
  inputText: isEmbed ? void 0 : this.extractInputText(event),
2417
3396
  toolCallCount: 0,
2418
3397
  subagentName,
2419
- subagentToolCallSpan
3398
+ subagentToolCallSpan,
3399
+ handoff,
3400
+ nestedInEvent: inheritedParent !== void 0
2420
3401
  });
2421
3402
  };
2422
3403
  // ── onStepStart ─────────────────────────────────────────────────────────
@@ -2458,15 +3439,17 @@ var RaindropTelemetryIntegration = class {
2458
3439
  attrString("operation.name", operationName),
2459
3440
  attrString("resource.name", resourceName),
2460
3441
  attrString("ai.telemetry.functionId", state.functionId),
2461
- attrString("ai.model.provider", event.provider),
3442
+ ...attrsFromModelProvider(event.provider),
2462
3443
  attrString("ai.model.id", event.modelId),
2463
- attrString("gen_ai.system", event.provider),
2464
- attrString("gen_ai.request.model", event.modelId),
2465
- ...inputAttrs
3444
+ attrString(MODEL_USAGE_ATTRIBUTES.requestModel, event.modelId),
3445
+ ...inputAttrs,
3446
+ ...handoffSpanAttrs(state.handoff)
2466
3447
  ]
2467
3448
  });
2468
3449
  state.stepSpan = stepSpan;
2469
3450
  state.stepParent = this.spanParentRef(stepSpan);
3451
+ state.stepProvider = event.provider;
3452
+ state.stepModelId = event.modelId;
2470
3453
  };
2471
3454
  this.onToolExecutionStart = (event) => this.toolExecutionStart(event);
2472
3455
  this.onToolExecutionEnd = (event) => this.toolExecutionEnd(event);
@@ -2517,23 +3500,24 @@ var RaindropTelemetryIntegration = class {
2517
3500
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2518
3501
  const state = this.getState(event.callId);
2519
3502
  if (!(state == null ? void 0 : state.stepSpan)) return;
3503
+ const responseModelId = (_b = (_a = event.response) == null ? void 0 : _a.modelId) != null ? _b : state.stepModelId;
2520
3504
  const outputAttrs = [];
2521
3505
  if (state.recordOutputs) {
2522
3506
  outputAttrs.push(
2523
3507
  attrString("ai.response.finishReason", event.finishReason),
2524
- attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2525
- attrString("ai.response.id", (_b = event.response) == null ? void 0 : _b.id),
2526
- attrString("ai.response.model", (_c = event.response) == null ? void 0 : _c.modelId),
3508
+ attrString("ai.response.text", capText2((_c = event.text) != null ? _c : void 0)),
3509
+ attrString("ai.response.id", (_d = event.response) == null ? void 0 : _d.id),
3510
+ attrString("ai.response.model", responseModelId),
2527
3511
  attrString(
2528
3512
  "ai.response.timestamp",
2529
- ((_d = event.response) == null ? void 0 : _d.timestamp) instanceof Date ? event.response.timestamp.toISOString() : (_e = event.response) == null ? void 0 : _e.timestamp
3513
+ ((_e = event.response) == null ? void 0 : _e.timestamp) instanceof Date ? event.response.timestamp.toISOString() : (_f = event.response) == null ? void 0 : _f.timestamp
2530
3514
  ),
2531
3515
  attrString(
2532
3516
  "ai.response.providerMetadata",
2533
3517
  event.providerMetadata ? safeJsonWithUint8(event.providerMetadata) : void 0
2534
3518
  )
2535
3519
  );
2536
- if (((_f = event.toolCalls) == null ? void 0 : _f.length) > 0) {
3520
+ if (((_g = event.toolCalls) == null ? void 0 : _g.length) > 0) {
2537
3521
  outputAttrs.push(
2538
3522
  attrString(
2539
3523
  "ai.response.toolCalls",
@@ -2547,7 +3531,7 @@ var RaindropTelemetryIntegration = class {
2547
3531
  )
2548
3532
  );
2549
3533
  }
2550
- if (((_g = event.reasoning) == null ? void 0 : _g.length) > 0) {
3534
+ if (((_h = event.reasoning) == null ? void 0 : _h.length) > 0) {
2551
3535
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2552
3536
  if (reasoningText) {
2553
3537
  outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
@@ -2556,25 +3540,31 @@ var RaindropTelemetryIntegration = class {
2556
3540
  }
2557
3541
  outputAttrs.push(
2558
3542
  attrStringArray("gen_ai.response.finish_reasons", [event.finishReason]),
2559
- attrString("gen_ai.response.id", (_h = event.response) == null ? void 0 : _h.id),
2560
- attrString("gen_ai.response.model", (_i = event.response) == null ? void 0 : _i.modelId)
3543
+ attrString("gen_ai.response.id", (_i = event.response) == null ? void 0 : _i.id),
3544
+ attrString(MODEL_USAGE_ATTRIBUTES.responseModel, responseModelId)
2561
3545
  );
2562
3546
  const usage = event.usage;
2563
3547
  if (usage) {
3548
+ const usageMetrics = extractUsageMetricsFromUsage(
3549
+ usage,
3550
+ state.stepProvider,
3551
+ event.providerMetadata
3552
+ );
2564
3553
  outputAttrs.push(
2565
- attrInt("ai.usage.inputTokens", usage.inputTokens),
2566
- attrInt("ai.usage.outputTokens", usage.outputTokens),
2567
- attrInt("ai.usage.totalTokens", usage.totalTokens),
2568
- attrInt("ai.usage.reasoningTokens", usage.reasoningTokens),
2569
- attrInt("ai.usage.cachedInputTokens", usage.cachedInputTokens),
2570
- attrInt("gen_ai.usage.input_tokens", usage.inputTokens),
2571
- attrInt("gen_ai.usage.output_tokens", usage.outputTokens)
3554
+ attrInt("ai.usage.inputTokens", usageMetrics.inputTokens),
3555
+ attrInt("ai.usage.outputTokens", usageMetrics.outputTokens),
3556
+ attrInt("ai.usage.totalTokens", usageMetrics.totalTokens),
3557
+ attrInt("ai.usage.reasoningTokens", usageMetrics.reasoningTokens),
3558
+ attrInt("ai.usage.cachedInputTokens", usageMetrics.cachedInputTokens),
3559
+ ...buildModelUsageAttributes(usageMetrics)
2572
3560
  );
2573
3561
  }
2574
3562
  this.emitProviderExecutedToolSpans(event, state);
2575
3563
  this.traceShipper.endSpan(state.stepSpan, { attributes: outputAttrs });
2576
3564
  state.stepSpan = void 0;
2577
3565
  state.stepParent = void 0;
3566
+ state.stepProvider = void 0;
3567
+ state.stepModelId = void 0;
2578
3568
  };
2579
3569
  // ── onEmbedStart ────────────────────────────────────────────────────────
2580
3570
  this.onEmbedStart = (event) => {
@@ -2592,7 +3582,8 @@ var RaindropTelemetryIntegration = class {
2592
3582
  attrString("operation.name", operationName),
2593
3583
  attrString("resource.name", resourceName),
2594
3584
  attrString("ai.telemetry.functionId", state.functionId),
2595
- ...inputAttrs
3585
+ ...inputAttrs,
3586
+ ...handoffSpanAttrs(state.handoff)
2596
3587
  ]
2597
3588
  });
2598
3589
  state.embedSpans.set(event.embedCallId, embedSpan);
@@ -2649,6 +3640,8 @@ var RaindropTelemetryIntegration = class {
2649
3640
  const actualError = (_a = event.error) != null ? _a : error;
2650
3641
  if (state.stepSpan) {
2651
3642
  this.traceShipper.endSpan(state.stepSpan, { error: actualError });
3643
+ state.stepProvider = void 0;
3644
+ state.stepModelId = void 0;
2652
3645
  }
2653
3646
  for (const embedSpan of state.embedSpans.values()) {
2654
3647
  this.traceShipper.endSpan(embedSpan, { error: actualError });
@@ -2662,15 +3655,26 @@ var RaindropTelemetryIntegration = class {
2662
3655
  this.traceShipper.endSpan(toolSpan, { error: actualError });
2663
3656
  }
2664
3657
  state.toolSpans.clear();
3658
+ const abortReason = describeAbortReason(actualError);
2665
3659
  if (state.rootSpan) {
2666
- this.traceShipper.endSpan(state.rootSpan, { error: actualError });
3660
+ this.traceShipper.endSpan(state.rootSpan, {
3661
+ error: actualError,
3662
+ attributes: this.abortOutcomeAttrs(state, abortReason, "error")
3663
+ });
2667
3664
  }
2668
3665
  if (state.subagentToolCallSpan) {
2669
3666
  this.traceShipper.endSpan(state.subagentToolCallSpan, { error: actualError });
2670
3667
  }
2671
3668
  const isEmbed = state.operationId === "ai.embed" || state.operationId === "ai.embedMany";
2672
3669
  if (!isEmbed) {
2673
- this.finalizeGenerateEvent(state, state.accumulatedText || void 0, void 0);
3670
+ const errorIsTheRunsOutcome = state.handoff !== void 0 && !state.nestedInEvent;
3671
+ this.finalizeGenerateEvent(
3672
+ state,
3673
+ state.accumulatedText || void 0,
3674
+ void 0,
3675
+ false,
3676
+ errorIsTheRunsOutcome ? { fallbackOutput: abortReason } : void 0
3677
+ );
2674
3678
  }
2675
3679
  this.cleanup(event.callId);
2676
3680
  };
@@ -2692,6 +3696,8 @@ var RaindropTelemetryIntegration = class {
2692
3696
  this.traceShipper.endSpan(state.stepSpan, { attributes: [abortedAttr] });
2693
3697
  state.stepSpan = void 0;
2694
3698
  state.stepParent = void 0;
3699
+ state.stepProvider = void 0;
3700
+ state.stepModelId = void 0;
2695
3701
  }
2696
3702
  for (const embedSpan of state.embedSpans.values()) {
2697
3703
  this.traceShipper.endSpan(embedSpan, { attributes: [abortedAttr] });
@@ -2705,9 +3711,19 @@ var RaindropTelemetryIntegration = class {
2705
3711
  this.traceShipper.endSpan(toolSpan, { attributes: [abortedAttr] });
2706
3712
  }
2707
3713
  state.toolSpans.clear();
3714
+ const abortIsTheRunsOutcome = state.handoff !== void 0 && !state.nestedInEvent;
2708
3715
  if (state.rootSpan) {
2709
3716
  this.traceShipper.endSpan(state.rootSpan, {
2710
- attributes: [abortedAttr, attrInt("ai.toolCall.count", state.toolCallCount)]
3717
+ attributes: [
3718
+ abortedAttr,
3719
+ attrInt("ai.toolCall.count", state.toolCallCount),
3720
+ // An aborted detached child IS a cancelled sub-agent, and a cancelled
3721
+ // run is span-for-span identical to a finished one — spans close,
3722
+ // nothing errors, there is just no answer. So it is the one outcome
3723
+ // that has to be stated, and the child states it on itself.
3724
+ ...abortIsTheRunsOutcome ? cancelledSpanAttrs() : [],
3725
+ ...abortIsTheRunsOutcome ? this.abortOutcomeAttrs(state, SUBAGENT_CANCELLED_OUTPUT, "stop") : []
3726
+ ]
2711
3727
  });
2712
3728
  }
2713
3729
  if (state.subagentToolCallSpan) {
@@ -2715,7 +3731,17 @@ var RaindropTelemetryIntegration = class {
2715
3731
  attributes: [abortedAttr]
2716
3732
  });
2717
3733
  }
2718
- this.finalizeGenerateEvent(state, state.accumulatedText || void 0, void 0);
3734
+ this.finalizeGenerateEvent(
3735
+ state,
3736
+ state.accumulatedText || void 0,
3737
+ void 0,
3738
+ false,
3739
+ abortIsTheRunsOutcome ? {
3740
+ fallbackOutput: SUBAGENT_CANCELLED_OUTPUT,
3741
+ properties: { [HANDOFF_TERMINAL_PROPERTY_KEY]: HANDOFF_TERMINAL_CANCELLED }
3742
+ } : void 0
3743
+ );
3744
+ if (abortIsTheRunsOutcome) this.subagents.noteSettled(state.eventId);
2719
3745
  this.cleanup(callId);
2720
3746
  };
2721
3747
  // ── executeTool ─────────────────────────────────────────────────────────
@@ -2743,6 +3769,97 @@ var RaindropTelemetryIntegration = class {
2743
3769
  this.subagentWrapping = opts.subagentWrapping !== false;
2744
3770
  this.debug = opts.debug === true;
2745
3771
  this.defaultContext = opts.context;
3772
+ this.subagents = new SubagentApi({
3773
+ traceShipper: this.traceShipper,
3774
+ eventShipper: this.eventShipper,
3775
+ sendTraces: this.sendTraces,
3776
+ sendEvents: this.sendEvents,
3777
+ debug: this.debug,
3778
+ defaultContext: this.defaultContext,
3779
+ settledEventIds: opts.settledEventIds,
3780
+ spanSource: {
3781
+ activeDispatch: () => this.activeDispatch(),
3782
+ openSpansForEvent: (eventId) => this.openSpansForEvent(eventId)
3783
+ }
3784
+ });
3785
+ }
3786
+ /**
3787
+ * Record a detached sub-agent on the turn that is live right now.
3788
+ *
3789
+ * Returns the dispatch — the child's event id and the headers to send with
3790
+ * the job. Dispatching is still yours to do; this only states that you are
3791
+ * about to, so a reader can follow the link before the child exists.
3792
+ */
3793
+ subagent(options = {}) {
3794
+ return this.subagents.subagent(options);
3795
+ }
3796
+ // ── detached sub-agent hand-offs ─────────────────────────────────────────
3797
+ /**
3798
+ * The turn a `subagent()` dispatch is happening inside.
3799
+ *
3800
+ * Resolved from the parent-tool context first, which is the only way to reach
3801
+ * the EXACT span that dispatched: the tool call the model itself issued, still
3802
+ * open while its `execute` runs. Falling back to the event id lets a launch
3803
+ * from elsewhere in the turn (a queue producer called mid-generation) still
3804
+ * find the turn's spans, which is where the tool-events opt-in has to land.
3805
+ */
3806
+ activeDispatch() {
3807
+ var _a;
3808
+ const parentTool = getCurrentParentToolContext();
3809
+ let state = parentTool ? this.getState(parentTool.parentCallId) : void 0;
3810
+ const inheritedEventId = (_a = getContextManager().getParentSpanIds()) == null ? void 0 : _a.eventId;
3811
+ if (!state && inheritedEventId) {
3812
+ for (const candidate of this.callStates.values()) {
3813
+ if (candidate.eventId === inheritedEventId) {
3814
+ state = candidate;
3815
+ break;
3816
+ }
3817
+ }
3818
+ }
3819
+ if (!state) return void 0;
3820
+ return {
3821
+ eventId: state.eventId,
3822
+ userId: state.identity.userId,
3823
+ convoId: state.identity.convoId,
3824
+ dispatchSpan: parentTool ? state.toolSpans.get(parentTool.toolCallId) : void 0,
3825
+ turnSpans: [state.rootSpan, state.stepSpan].filter(
3826
+ (span) => span !== void 0
3827
+ ),
3828
+ recordInputs: state.recordInputs
3829
+ };
3830
+ }
3831
+ /** Spans still open for an event, so a late outcome can still be stated. */
3832
+ openSpansForEvent(eventId) {
3833
+ const spans = [];
3834
+ for (const state of this.callStates.values()) {
3835
+ if (state.eventId !== eventId) continue;
3836
+ if (state.rootSpan) spans.push(state.rootSpan);
3837
+ if (state.stepSpan) spans.push(state.stepSpan);
3838
+ }
3839
+ return spans;
3840
+ }
3841
+ /**
3842
+ * End-attributes that keep a detached child legible when it ends without an
3843
+ * answer — an error, an abort, a cancellation.
3844
+ *
3845
+ * An event is created from an LLM span only when it has a finish reason and
3846
+ * non-empty output, so a child that dies silently produces NO event, and the
3847
+ * caller's hand-off pill stays on `queued` forever — indistinguishable from a
3848
+ * job that never started. Reporting the abort reason as the run's output is
3849
+ * what makes the outcome visible. Only detached children get this: an inline
3850
+ * generation's failure is already legible from its caller's own event.
3851
+ *
3852
+ * And only the run's OUTERMOST generation gets it (see `nestedInEvent`): an
3853
+ * inner sub-call ending badly is not the run ending, and since an event is
3854
+ * created from an LLM span with output, `ai.response.text` here would state
3855
+ * the run's outcome through the span while the turn is still working.
3856
+ */
3857
+ abortOutcomeAttrs(state, reason, finishReason) {
3858
+ if (!state.handoff || state.nestedInEvent) return [];
3859
+ return [
3860
+ attrString("ai.response.text", capText2(state.accumulatedText || reason)),
3861
+ attrString("ai.response.finishReason", finishReason)
3862
+ ];
2746
3863
  }
2747
3864
  // ── helpers ──────────────────────────────────────────────────────────────
2748
3865
  getState(callId) {
@@ -2861,7 +3978,8 @@ var RaindropTelemetryIntegration = class {
2861
3978
  attrString("ai.telemetry.functionId", state.functionId),
2862
3979
  attrString("ai.toolCall.name", toolCall.toolName),
2863
3980
  attrString("ai.toolCall.id", toolCall.toolCallId),
2864
- ...inputAttrs
3981
+ ...inputAttrs,
3982
+ ...handoffSpanAttrs(state.handoff)
2865
3983
  ]
2866
3984
  });
2867
3985
  state.toolSpans.set(toolCall.toolCallId, toolSpan);
@@ -2940,7 +4058,8 @@ var RaindropTelemetryIntegration = class {
2940
4058
  attrString("ai.toolCall.name", call.toolName),
2941
4059
  attrString("ai.toolCall.id", call.toolCallId),
2942
4060
  attrString("ai.toolCall.providerExecuted", "true"),
2943
- ...inputAttrs
4061
+ ...inputAttrs,
4062
+ ...handoffSpanAttrs(state.handoff)
2944
4063
  ]
2945
4064
  });
2946
4065
  state.toolCallCount += 1;
@@ -2991,12 +4110,13 @@ var RaindropTelemetryIntegration = class {
2991
4110
  }
2992
4111
  const usage = (_d = event.totalUsage) != null ? _d : event.usage;
2993
4112
  if (usage) {
4113
+ const usageMetrics = extractUsageMetricsFromUsage(usage);
2994
4114
  outputAttrs.push(
2995
- attrInt("ai.usage.inputTokens", usage.inputTokens),
2996
- attrInt("ai.usage.outputTokens", usage.outputTokens),
2997
- attrInt("ai.usage.totalTokens", usage.totalTokens),
2998
- attrInt("ai.usage.reasoningTokens", usage.reasoningTokens),
2999
- attrInt("ai.usage.cachedInputTokens", usage.cachedInputTokens)
4115
+ attrInt("ai.usage.inputTokens", usageMetrics.inputTokens),
4116
+ attrInt("ai.usage.outputTokens", usageMetrics.outputTokens),
4117
+ attrInt("ai.usage.totalTokens", usageMetrics.totalTokens),
4118
+ attrInt("ai.usage.reasoningTokens", usageMetrics.reasoningTokens),
4119
+ attrInt("ai.usage.cachedInputTokens", usageMetrics.cachedInputTokens)
3000
4120
  );
3001
4121
  }
3002
4122
  outputAttrs.push(attrInt("ai.toolCall.count", state.toolCallCount));
@@ -3020,19 +4140,22 @@ var RaindropTelemetryIntegration = class {
3020
4140
  * is finalized by that resume — otherwise each execution would finalize the
3021
4141
  * same event ID into a separate canonical row.
3022
4142
  */
3023
- finalizeGenerateEvent(state, output, model, keepPending = false) {
4143
+ finalizeGenerateEvent(state, output, model, keepPending = false, detachedOutcome) {
3024
4144
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3025
4145
  const suppressSubagentEvent = state.subagentName !== void 0 && state.subagentToolCallSpan !== void 0;
3026
4146
  if (!this.sendEvents || suppressSubagentEvent) return;
4147
+ if (this.subagents.wasSettledExplicitly(state.eventId)) return;
3027
4148
  const callMeta = this.extractRaindropMetadata(state.metadata);
3028
4149
  const userId = (_b = callMeta.userId) != null ? _b : (_a = this.defaultContext) == null ? void 0 : _a.userId;
3029
4150
  if (!userId) return;
3030
4151
  const eventName = (_e = (_d = callMeta.eventName) != null ? _d : (_c = this.defaultContext) == null ? void 0 : _c.eventName) != null ? _e : state.operationId;
3031
4152
  const cappedOutput = capText2(output);
4153
+ const reportedOutput = state.handoff && detachedOutcome && !(cappedOutput == null ? void 0 : cappedOutput.trim()) ? detachedOutcome.fallbackOutput : cappedOutput;
3032
4154
  const input = capText2(state.inputText);
3033
4155
  const properties = {
3034
4156
  ...(_f = this.defaultContext) == null ? void 0 : _f.properties,
3035
- ...callMeta.properties
4157
+ ...callMeta.properties,
4158
+ ...state.handoff ? detachedOutcome == null ? void 0 : detachedOutcome.properties : void 0
3036
4159
  };
3037
4160
  const featureFlags = {
3038
4161
  ...((_g = this.defaultContext) == null ? void 0 : _g.featureFlags) ? normalizeFeatureFlags(this.defaultContext.featureFlags) : {},
@@ -3044,7 +4167,7 @@ var RaindropTelemetryIntegration = class {
3044
4167
  userId,
3045
4168
  convoId,
3046
4169
  input,
3047
- output: cappedOutput,
4170
+ output: reportedOutput,
3048
4171
  model,
3049
4172
  properties: Object.keys(properties).length > 0 ? properties : void 0,
3050
4173
  featureFlags: Object.keys(featureFlags).length > 0 ? featureFlags : void 0,
@@ -3449,14 +4572,6 @@ function runWithParentSpanContextSync(ctx, fn) {
3449
4572
  function isAsyncIterable(value) {
3450
4573
  return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
3451
4574
  }
3452
- function firstFiniteNumber(...values) {
3453
- for (const value of values) {
3454
- if (typeof value === "number" && Number.isFinite(value)) {
3455
- return value;
3456
- }
3457
- }
3458
- return void 0;
3459
- }
3460
4575
  function resolveUsageRecord(result) {
3461
4576
  if (!isRecord(result)) return void 0;
3462
4577
  let usage;
@@ -3472,50 +4587,7 @@ function resolveUsageRecord(result) {
3472
4587
  return isRecord(usage) ? usage : void 0;
3473
4588
  }
3474
4589
  function extractUsageMetrics(result) {
3475
- const usage = resolveUsageRecord(result);
3476
- if (!usage) return {};
3477
- const inputTokenValue = usage["inputTokens"];
3478
- const outputTokenValue = usage["outputTokens"];
3479
- const inputTokens = firstFiniteNumber(
3480
- isRecord(inputTokenValue) ? inputTokenValue["total"] : void 0,
3481
- inputTokenValue,
3482
- usage["promptTokens"],
3483
- usage["prompt_tokens"]
3484
- );
3485
- const outputTokens = firstFiniteNumber(
3486
- isRecord(outputTokenValue) ? outputTokenValue["total"] : void 0,
3487
- outputTokenValue,
3488
- usage["completionTokens"],
3489
- usage["completion_tokens"]
3490
- );
3491
- const totalTokens = firstFiniteNumber(
3492
- usage["totalTokens"],
3493
- usage["tokens"],
3494
- usage["total_tokens"],
3495
- inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0
3496
- );
3497
- const reasoningTokens = firstFiniteNumber(
3498
- isRecord(outputTokenValue) ? outputTokenValue["reasoning"] : void 0,
3499
- usage["reasoningTokens"],
3500
- usage["completionReasoningTokens"],
3501
- usage["completion_reasoning_tokens"],
3502
- usage["reasoning_tokens"],
3503
- usage["thinkingTokens"],
3504
- usage["thinking_tokens"]
3505
- );
3506
- const cachedInputTokens = firstFiniteNumber(
3507
- isRecord(inputTokenValue) ? inputTokenValue["cacheRead"] : void 0,
3508
- usage["cachedInputTokens"],
3509
- usage["promptCachedTokens"],
3510
- usage["prompt_cached_tokens"]
3511
- );
3512
- return {
3513
- inputTokens,
3514
- outputTokens,
3515
- totalTokens,
3516
- reasoningTokens,
3517
- cachedInputTokens
3518
- };
4590
+ return extractUsageMetricsFromUsage(resolveUsageRecord(result));
3519
4591
  }
3520
4592
  function isObjectOperation(operation) {
3521
4593
  return operation === "generateObject" || operation === "streamObject";
@@ -4007,6 +5079,7 @@ function wrapAISDK(aiSDK, deps) {
4007
5079
  sendTraces: ((_a = deps.options.send) == null ? void 0 : _a.traces) !== false,
4008
5080
  sendEvents: ((_b = deps.options.send) == null ? void 0 : _b.events) !== false,
4009
5081
  debug,
5082
+ settledEventIds: deps.settledEventIds,
4010
5083
  context: {
4011
5084
  userId: wrapTimeCtx.userId,
4012
5085
  eventId: wrapTimeCtx.eventId,
@@ -4906,8 +5979,13 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4906
5979
  ended = true;
4907
5980
  const msToFirstChunk = firstChunkMs !== void 0 ? firstChunkMs - startMs : void 0;
4908
5981
  const msToFinish = finishMs !== void 0 ? finishMs - startMs : void 0;
4909
- const inputTokens = extractNestedTokens(usage, "inputTokens");
4910
- const outputTokens = extractNestedTokens(usage, "outputTokens");
5982
+ const usageMetrics = extractUsageMetricsFromUsage(
5983
+ usage,
5984
+ modelInfo.provider,
5985
+ providerMetadata
5986
+ );
5987
+ const finalResponseModelId = responseModelId != null ? responseModelId : modelInfo.modelId;
5988
+ const { inputTokens, outputTokens } = usageMetrics;
4911
5989
  const avgOutTokensPerSecond = msToFinish && outputTokens !== void 0 && msToFinish > 0 ? 1e3 * outputTokens / msToFinish : void 0;
4912
5990
  ctx.traceShipper.endSpan(span, {
4913
5991
  attributes: [
@@ -4919,7 +5997,7 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4919
5997
  safeJsonWithUint8(toolCallsLocal.length ? toolCallsLocal : void 0)
4920
5998
  ),
4921
5999
  attrString("ai.response.id", responseId),
4922
- attrString("ai.response.model", responseModelId),
6000
+ attrString("ai.response.model", finalResponseModelId),
4923
6001
  attrString("ai.response.timestamp", responseTimestampIso),
4924
6002
  attrString(
4925
6003
  "ai.response.providerMetadata",
@@ -4930,9 +6008,8 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4930
6008
  attrInt("ai.usage.outputTokens", outputTokens),
4931
6009
  ...finishReason ? [attrStringArray("gen_ai.response.finish_reasons", [finishReason])] : [],
4932
6010
  attrString("gen_ai.response.id", responseId),
4933
- attrString("gen_ai.response.model", responseModelId),
4934
- attrInt("gen_ai.usage.input_tokens", inputTokens),
4935
- attrInt("gen_ai.usage.output_tokens", outputTokens),
6011
+ attrString(MODEL_USAGE_ATTRIBUTES.responseModel, finalResponseModelId),
6012
+ ...buildModelUsageAttributes(usageMetrics),
4936
6013
  ...msToFirstChunk !== void 0 ? [attrInt("ai.stream.msToFirstChunk", msToFirstChunk)] : [],
4937
6014
  ...msToFinish !== void 0 ? [attrInt("ai.stream.msToFinish", msToFinish)] : [],
4938
6015
  ...avgOutTokensPerSecond !== void 0 ? [attrDouble("ai.stream.avgOutputTokensPerSecond", avgOutTokensPerSecond)] : [],
@@ -5038,15 +6115,14 @@ function startDoGenerateSpan(operationId, options, modelInfo, parent, ctx) {
5038
6115
  attrString("operation.name", operationName),
5039
6116
  attrString("resource.name", resourceName),
5040
6117
  attrString("ai.telemetry.functionId", (_b = ctx.telemetry) == null ? void 0 : _b.functionId),
5041
- attrString("ai.model.provider", modelInfo.provider),
6118
+ ...attrsFromModelProvider(modelInfo.provider),
5042
6119
  attrString("ai.model.id", modelInfo.modelId),
5043
6120
  ...((_c = ctx.telemetry) == null ? void 0 : _c.recordInputs) === false ? [] : [
5044
6121
  attrString("ai.prompt.messages", promptJson),
5045
6122
  attrStringArray("ai.prompt.tools", toolsJson),
5046
6123
  attrString("ai.prompt.toolChoice", toolChoiceJson)
5047
6124
  ],
5048
- attrString("gen_ai.system", modelInfo.provider),
5049
- attrString("gen_ai.request.model", modelInfo.modelId),
6125
+ attrString(MODEL_USAGE_ATTRIBUTES.requestModel, modelInfo.modelId),
5050
6126
  ...attrsFromGenAiRequest(options),
5051
6127
  attrProviderOptions(options)
5052
6128
  ]
@@ -5079,8 +6155,12 @@ function endDoGenerateSpan(span, result, modelInfo, ctx) {
5079
6155
  } else {
5080
6156
  responseTimestampIso = (/* @__PURE__ */ new Date()).toISOString();
5081
6157
  }
5082
- const inputTokens = extractNestedTokens(usage, "inputTokens");
5083
- const outputTokens = extractNestedTokens(usage, "outputTokens");
6158
+ const usageMetrics = extractUsageMetricsFromUsage(
6159
+ usage,
6160
+ modelInfo.provider,
6161
+ providerMetadata
6162
+ );
6163
+ const { inputTokens, outputTokens } = usageMetrics;
5084
6164
  ctx.traceShipper.endSpan(span, {
5085
6165
  attributes: [
5086
6166
  ...((_a = ctx.telemetry) == null ? void 0 : _a.recordOutputs) === false ? [] : [
@@ -5099,9 +6179,8 @@ function endDoGenerateSpan(span, result, modelInfo, ctx) {
5099
6179
  attrInt("ai.usage.completionTokens", outputTokens),
5100
6180
  ...finishReason ? [attrStringArray("gen_ai.response.finish_reasons", [finishReason])] : [],
5101
6181
  attrString("gen_ai.response.id", responseId),
5102
- attrString("gen_ai.response.model", responseModelId),
5103
- attrInt("gen_ai.usage.input_tokens", inputTokens),
5104
- attrInt("gen_ai.usage.output_tokens", outputTokens)
6182
+ attrString(MODEL_USAGE_ATTRIBUTES.responseModel, responseModelId),
6183
+ ...buildModelUsageAttributes(usageMetrics)
5105
6184
  ]
5106
6185
  });
5107
6186
  }
@@ -5122,15 +6201,14 @@ function startDoStreamSpan(operationId, options, modelInfo, parent, ctx) {
5122
6201
  attrString("operation.name", operationName),
5123
6202
  attrString("resource.name", resourceName),
5124
6203
  attrString("ai.telemetry.functionId", (_b = ctx.telemetry) == null ? void 0 : _b.functionId),
5125
- attrString("ai.model.provider", modelInfo.provider),
6204
+ ...attrsFromModelProvider(modelInfo.provider),
5126
6205
  attrString("ai.model.id", modelInfo.modelId),
5127
6206
  ...((_c = ctx.telemetry) == null ? void 0 : _c.recordInputs) === false ? [] : [
5128
6207
  attrString("ai.prompt.messages", promptJson),
5129
6208
  attrStringArray("ai.prompt.tools", toolsJson),
5130
6209
  attrString("ai.prompt.toolChoice", toolChoiceJson)
5131
6210
  ],
5132
- attrString("gen_ai.system", modelInfo.provider),
5133
- attrString("gen_ai.request.model", modelInfo.modelId),
6211
+ attrString(MODEL_USAGE_ATTRIBUTES.requestModel, modelInfo.modelId),
5134
6212
  ...attrsFromGenAiRequest(options),
5135
6213
  attrProviderOptions(options)
5136
6214
  ]
@@ -5195,13 +6273,6 @@ function mergeAttachments(...groups) {
5195
6273
  }
5196
6274
  return merged.length ? merged : void 0;
5197
6275
  }
5198
- function extractNestedTokens(usage, key) {
5199
- if (!isRecord(usage)) return void 0;
5200
- const val = usage[key];
5201
- if (typeof val === "number") return val;
5202
- if (isRecord(val) && typeof val["total"] === "number") return val["total"];
5203
- return void 0;
5204
- }
5205
6276
 
5206
6277
  // src/index.ts
5207
6278
  function eventMetadata(options) {
@@ -5218,6 +6289,8 @@ function eventMetadata(options) {
5218
6289
  if (options.properties) result["raindrop.properties"] = JSON.stringify(options.properties);
5219
6290
  if (options.featureFlags)
5220
6291
  result["raindrop.featureFlags"] = JSON.stringify(normalizeFeatureFlags(options.featureFlags));
6292
+ if (options.subagent) Object.assign(result, detachedChildMetadata(options.subagent));
6293
+ if (options.toolEvents) result[TOOL_EVENTS_SUFFIX] = options.toolEvents;
5221
6294
  return result;
5222
6295
  }
5223
6296
  function deriveChatTurnMessageId(request) {
@@ -5300,12 +6373,22 @@ function createRaindropAISDK(opts) {
5300
6373
  transformSpan: (_j = opts.traces) == null ? void 0 : _j.transformSpan,
5301
6374
  disableDefaultRedaction: (_k = opts.traces) == null ? void 0 : _k.disableDefaultRedaction
5302
6375
  });
6376
+ const settledEventIds = /* @__PURE__ */ new Set();
6377
+ const subagentApi = new SubagentApi({
6378
+ traceShipper,
6379
+ eventShipper,
6380
+ sendTraces: tracesEnabled,
6381
+ sendEvents: eventsEnabled,
6382
+ debug: envDebug,
6383
+ settledEventIds
6384
+ });
5303
6385
  return {
5304
6386
  wrap(aiSDK, options) {
5305
6387
  return wrapAISDK(aiSDK, {
5306
6388
  options: options != null ? options : {},
5307
6389
  eventShipper,
5308
- traceShipper
6390
+ traceShipper,
6391
+ settledEventIds
5309
6392
  });
5310
6393
  },
5311
6394
  createSelfDiagnosticsTool(options = {}) {
@@ -5342,9 +6425,14 @@ function createRaindropAISDK(opts) {
5342
6425
  sendEvents: eventsEnabled,
5343
6426
  subagentWrapping,
5344
6427
  debug: envDebug,
6428
+ settledEventIds,
5345
6429
  context
5346
6430
  });
5347
6431
  },
6432
+ subagents: subagentApi,
6433
+ subagent(options = {}) {
6434
+ return subagentApi.subagent(options);
6435
+ },
5348
6436
  events: {
5349
6437
  async patch(eventId, patch) {
5350
6438
  await eventShipper.patch(eventId, patch);
@@ -5464,4 +6552,4 @@ function raindrop(options = {}) {
5464
6552
  return client.createTelemetryIntegration({ context, subagentWrapping });
5465
6553
  }
5466
6554
 
5467
- export { DEFAULT_MAX_TEXT_FIELD_CHARS2 as DEFAULT_MAX_TEXT_FIELD_CHARS, DEFAULT_REDACT_ATTRIBUTE_KEYS, DEFAULT_SECRET_KEY_NAMES, REDACTED_PLACEHOLDER, RaindropTelemetryIntegration, TRUNCATION_MARKER2 as TRUNCATION_MARKER, _resetParentToolContextStorage, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, boundedStringify, capText2 as capText, clearParentToolContext, createRaindropAISDK, currentSpan, defaultTransformSpan, enterParentToolContext, eventMetadata, eventMetadataFromChatRequest, getContextManager, getCurrentParentToolContext, getCurrentRaindropCallMetadata, normalizeFeatureFlags, raindrop, readRaindropCallMetadataFromArgs, redactJsonAttributeValue, redactSecretsInObject, runWithParentToolContext, runWithRaindropCallMetadata, withCurrent };
6555
+ export { DEFAULT_MAX_TEXT_FIELD_CHARS2 as DEFAULT_MAX_TEXT_FIELD_CHARS, DEFAULT_REDACT_ATTRIBUTE_KEYS, DEFAULT_SECRET_KEY_NAMES, DETACHED_MODE, HANDOFF_ATTRIBUTE_PREFIXES, HANDOFF_ATTRIBUTE_SUFFIXES, HANDOFF_TERMINAL_CANCELLED, HANDOFF_TERMINAL_PROPERTY_KEY, REDACTED_PLACEHOLDER, RaindropTelemetryIntegration, SUBAGENT_ABORTED_OUTPUT, SUBAGENT_AGENT_ROLE, SUBAGENT_CANCELLED_OUTPUT, TOOL_EVENTS_ALLOW, TRUNCATION_MARKER2 as TRUNCATION_MARKER, _resetParentToolContextStorage, _resetRaindropCallMetadataStorage, _resetWarnedMissingUserId, boundedStringify, capText2 as capText, clearParentToolContext, createRaindropAISDK, currentSpan, defaultTransformSpan, enterParentToolContext, eventMetadata, eventMetadataFromChatRequest, fromHeaders, getContextManager, getCurrentParentToolContext, getCurrentRaindropCallMetadata, normalizeFeatureFlags, raindrop, readRaindropCallMetadataFromArgs, redactJsonAttributeValue, redactSecretsInObject, resetUnresolvedCarrierWarning, runWithParentToolContext, runWithRaindropCallMetadata, toHeaders, toLangSmithHeaders, withCurrent };