@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,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'async_hooks';
2
2
 
3
- // ../core/dist/chunk-QTUH3BHZ.js
3
+ // ../core/dist/chunk-YUIXRQWN.js
4
4
  function normalizeFeatureFlags(input) {
5
5
  if (Array.isArray(input)) {
6
6
  return Object.fromEntries(input.map((name) => [name, "true"]));
@@ -63,6 +63,311 @@ function base64Encode(bytes) {
63
63
  }
64
64
  return out;
65
65
  }
66
+ function base64ToHex(value) {
67
+ if (!value) return "";
68
+ const maybeBuffer = globalThis.Buffer;
69
+ if (maybeBuffer) {
70
+ return maybeBuffer.from(value, "base64").toString("hex");
71
+ }
72
+ const atobFn = globalThis.atob;
73
+ if (typeof atobFn !== "function") return "";
74
+ try {
75
+ const binary = atobFn(value);
76
+ let hex = "";
77
+ for (let i = 0; i < binary.length; i++) {
78
+ hex += (binary.charCodeAt(i) & 255).toString(16).padStart(2, "0");
79
+ }
80
+ return hex;
81
+ } catch (e) {
82
+ return "";
83
+ }
84
+ }
85
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
86
+ mode: "raindrop.handoff.mode",
87
+ childEventId: "raindrop.handoff.childEventId",
88
+ parentEventId: "raindrop.handoff.parentEventId",
89
+ parentSpanId: "raindrop.handoff.parentSpanId",
90
+ name: "raindrop.handoff.name",
91
+ terminal: "raindrop.handoff.terminal",
92
+ agentRole: "raindrop.agent.role"
93
+ };
94
+ var HANDOFF_ATTRIBUTE_PREFIXES = [
95
+ "ai.telemetry.metadata.",
96
+ "ai.settings.context.",
97
+ ""
98
+ ];
99
+ var AI_SDK_METADATA_PREFIX = "ai.telemetry.metadata.";
100
+ var DETACHED_MODE = "detached";
101
+ var SUBAGENT_AGENT_ROLE = "subagent";
102
+ var HANDOFF_TERMINAL_CANCELLED = "cancelled";
103
+ var TOOL_EVENTS_SUFFIX = "raindrop.toolEvents";
104
+ var TOOL_EVENTS_ALLOW = "allow";
105
+ var SUBAGENT_ABORT_OPERATION_ID = "ai.subagent.failed";
106
+ var SUBAGENT_ABORT_SPAN_NAME = SUBAGENT_ABORT_OPERATION_ID;
107
+ var DEFAULT_DISPATCH_SPAN_NAME = "launch_subagent";
108
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
109
+ function defined(entries) {
110
+ const out = {};
111
+ for (const [key, value] of Object.entries(entries)) {
112
+ if (typeof value === "string" && value.trim().length > 0) out[key] = value;
113
+ }
114
+ return out;
115
+ }
116
+ function detachedDispatchMetadata(dispatch) {
117
+ return defined({
118
+ [HANDOFF_ATTRIBUTE_SUFFIXES.mode]: DETACHED_MODE,
119
+ [HANDOFF_ATTRIBUTE_SUFFIXES.childEventId]: dispatch.childEventId,
120
+ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: dispatch.name
121
+ });
122
+ }
123
+ function detachedChildMetadata(link) {
124
+ return defined({
125
+ [HANDOFF_ATTRIBUTE_SUFFIXES.parentEventId]: link.parentEventId,
126
+ [HANDOFF_ATTRIBUTE_SUFFIXES.parentSpanId]: link.parentSpanId,
127
+ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: link.name,
128
+ [HANDOFF_ATTRIBUTE_SUFFIXES.agentRole]: SUBAGENT_AGENT_ROLE
129
+ });
130
+ }
131
+ function cancelledTerminalMetadata() {
132
+ return { [HANDOFF_ATTRIBUTE_SUFFIXES.terminal]: HANDOFF_TERMINAL_CANCELLED };
133
+ }
134
+ function cancelledTerminalAttributes() {
135
+ const bare = cancelledTerminalMetadata();
136
+ return { ...bare, ...withMetadataPrefix(bare) };
137
+ }
138
+ function toolEventsAllowMetadata() {
139
+ return { [TOOL_EVENTS_SUFFIX]: TOOL_EVENTS_ALLOW };
140
+ }
141
+ function withMetadataPrefix(metadata) {
142
+ const out = {};
143
+ for (const [key, value] of Object.entries(metadata)) {
144
+ out[key.startsWith(AI_SDK_METADATA_PREFIX) ? key : `${AI_SDK_METADATA_PREFIX}${key}`] = value;
145
+ }
146
+ return out;
147
+ }
148
+ function readHandoffField(metadata, suffix) {
149
+ for (const prefix of HANDOFF_ATTRIBUTE_PREFIXES) {
150
+ const value = metadata[`${prefix}${suffix}`];
151
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
152
+ }
153
+ return void 0;
154
+ }
155
+ function readDetachedChildLink(metadata) {
156
+ if (!metadata) return void 0;
157
+ const parentEventId = readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.parentEventId);
158
+ if (!parentEventId) return void 0;
159
+ return {
160
+ parentEventId,
161
+ parentSpanId: readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.parentSpanId),
162
+ name: readHandoffField(metadata, HANDOFF_ATTRIBUTE_SUFFIXES.name)
163
+ };
164
+ }
165
+ var BAGGAGE_KEYS = {
166
+ eventId: "raindrop-event-id",
167
+ childEventId: "raindrop-child-event-id",
168
+ name: "raindrop-handoff-name",
169
+ convoId: "raindrop-convo-id",
170
+ userId: "raindrop-user-id",
171
+ // The dispatch span and its trace, carried as fields as well as in
172
+ // `traceparent`. Not redundant: OTel's HTTP instrumentations rewrite
173
+ // `traceparent` from whatever span is current as the request goes out, so by
174
+ // the time the child reads it the ids name the HTTP client span and its trace
175
+ // instead of the dispatch. The child would then point its reverse reference at
176
+ // a span the launcher's UI does not know as a dispatch. A field states which
177
+ // span it means rather than meaning "whoever wrote this header last".
178
+ dispatchSpanId: "raindrop-dispatch-span-id",
179
+ traceId: "raindrop-trace-id"
180
+ };
181
+ var TRACEPARENT_HEADER = "traceparent";
182
+ var BAGGAGE_HEADER = "baggage";
183
+ var W3C_TRACE_ID = /^[0-9a-f]{32}$/;
184
+ var W3C_SPAN_ID = /^[0-9a-f]{16}$/;
185
+ function isW3CTraceId(value) {
186
+ return value !== void 0 && W3C_TRACE_ID.test(value);
187
+ }
188
+ function isW3CSpanId(value) {
189
+ return value !== void 0 && W3C_SPAN_ID.test(value);
190
+ }
191
+ var warnedNonConformantIds = false;
192
+ function warnNonConformantIdsOnce(traceId, spanId) {
193
+ if (warnedNonConformantIds) return;
194
+ warnedNonConformantIds = true;
195
+ console.warn(
196
+ `[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.`
197
+ );
198
+ }
199
+ var HANDOFF_HEADER = "x-raindrop-handoff";
200
+ var LANGSMITH_TRACE_HEADER = "langsmith-trace";
201
+ var LANGSMITH_METADATA_BAGGAGE_KEY = "langsmith-metadata";
202
+ function encodeBaggage(entries) {
203
+ return Object.entries(entries).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(",");
204
+ }
205
+ function decodeBaggage(header) {
206
+ if (!header) return {};
207
+ const parsed = {};
208
+ for (const part of header.split(",")) {
209
+ const [rawKey, ...rest] = part.split("=");
210
+ if (!rawKey || rest.length === 0) continue;
211
+ const key = rawKey.trim();
212
+ let value;
213
+ try {
214
+ value = decodeURIComponent(rest.join("=").trim());
215
+ } catch (e) {
216
+ value = rest.join("=").trim();
217
+ }
218
+ if (key in parsed && parsed[key] !== value) throw new ConflictingCarrierError(key);
219
+ parsed[key] = value;
220
+ }
221
+ return parsed;
222
+ }
223
+ function carrierBaggage(carrier) {
224
+ return {
225
+ [BAGGAGE_KEYS.eventId]: carrier.eventId,
226
+ [BAGGAGE_KEYS.childEventId]: carrier.childEventId,
227
+ [BAGGAGE_KEYS.name]: carrier.name,
228
+ [BAGGAGE_KEYS.convoId]: carrier.convoId,
229
+ [BAGGAGE_KEYS.userId]: carrier.userId,
230
+ [BAGGAGE_KEYS.dispatchSpanId]: carrier.spanId,
231
+ [BAGGAGE_KEYS.traceId]: carrier.traceId
232
+ };
233
+ }
234
+ function toHeaders(carrier) {
235
+ const fields = encodeBaggage(carrierBaggage(carrier));
236
+ const headers = {
237
+ [BAGGAGE_HEADER]: fields,
238
+ [HANDOFF_HEADER]: fields
239
+ };
240
+ if (isW3CTraceId(carrier.traceId) && isW3CSpanId(carrier.spanId)) {
241
+ headers[TRACEPARENT_HEADER] = `00-${carrier.traceId}-${carrier.spanId}-01`;
242
+ }
243
+ return headers;
244
+ }
245
+ function langsmithStamp() {
246
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "000000");
247
+ }
248
+ function toLangSmithHeaders(carrier) {
249
+ const stamp = langsmithStamp();
250
+ const dottedOrder = `${stamp}Z${carrier.traceId}.${stamp}Z${carrier.spanId}`;
251
+ return {
252
+ [LANGSMITH_TRACE_HEADER]: dottedOrder,
253
+ [BAGGAGE_HEADER]: encodeBaggage({
254
+ [LANGSMITH_METADATA_BAGGAGE_KEY]: JSON.stringify(carrierBaggage(carrier))
255
+ }),
256
+ // Carried here too: a LangSmith-shaped carrier is rewritten in transit
257
+ // exactly like the native one, and an injected `traceparent` is preferred
258
+ // over the dotted order on read.
259
+ [HANDOFF_HEADER]: encodeBaggage(carrierBaggage(carrier))
260
+ };
261
+ }
262
+ function isHeaderLookup(headers) {
263
+ return "get" in headers && typeof headers.get === "function";
264
+ }
265
+ var ConflictingCarrierError = class extends Error {
266
+ };
267
+ function readHeader(headers, name) {
268
+ var _a, _b, _c;
269
+ if (isHeaderLookup(headers)) return (_a = headers.get(name)) != null ? _a : void 0;
270
+ const value = (_c = headers[name]) != null ? _c : (_b = Object.entries(headers).find(([key]) => key.toLowerCase() === name)) == null ? void 0 : _b[1];
271
+ if (!Array.isArray(value)) return value;
272
+ const distinct = new Set(value);
273
+ if (distinct.size > 1) throw new ConflictingCarrierError(name);
274
+ return value[0];
275
+ }
276
+ function readTraceHeader(headers, name) {
277
+ const value = readHeader(headers, name);
278
+ if (!(value == null ? void 0 : value.includes(","))) return value;
279
+ const copies = value.split(",").map((copy) => copy.trim());
280
+ if (new Set(copies).size > 1) throw new ConflictingCarrierError(name);
281
+ return copies[0];
282
+ }
283
+ function fromHeaders(headers) {
284
+ var _a, _b;
285
+ let baggage;
286
+ let handoff;
287
+ let traceparent;
288
+ let langsmithTrace;
289
+ try {
290
+ baggage = decodeBaggage(readHeader(headers, BAGGAGE_HEADER));
291
+ handoff = decodeBaggage(readHeader(headers, HANDOFF_HEADER));
292
+ traceparent = readTraceHeader(headers, TRACEPARENT_HEADER);
293
+ langsmithTrace = readTraceHeader(headers, LANGSMITH_TRACE_HEADER);
294
+ } catch (error) {
295
+ if (error instanceof ConflictingCarrierError) return null;
296
+ throw error;
297
+ }
298
+ const langsmithMetadata = baggage[LANGSMITH_METADATA_BAGGAGE_KEY];
299
+ const nested = langsmithMetadata ? safeParseStringRecord(langsmithMetadata) : {};
300
+ for (const [key, value] of Object.entries(nested)) {
301
+ if (key in baggage && baggage[key] !== value) return null;
302
+ }
303
+ const fields = {
304
+ ...baggage,
305
+ ...nested,
306
+ // Last, so it wins: the standard headers may have been rewritten in transit
307
+ // by a propagator, this one is only ever written by us.
308
+ ...handoff
309
+ };
310
+ let traceId;
311
+ let spanId;
312
+ if (traceparent) {
313
+ const [, parsedTraceId, parsedSpanId] = traceparent.split("-");
314
+ if (isW3CTraceId(parsedTraceId) && isW3CSpanId(parsedSpanId)) {
315
+ traceId = parsedTraceId;
316
+ spanId = parsedSpanId;
317
+ }
318
+ }
319
+ if (!traceId && !spanId && langsmithTrace) {
320
+ const segments = langsmithTrace.split(".");
321
+ traceId = (_a = segments[0]) == null ? void 0 : _a.split("Z").pop();
322
+ spanId = (_b = segments[segments.length - 1]) == null ? void 0 : _b.split("Z").pop();
323
+ }
324
+ traceId = fields[BAGGAGE_KEYS.traceId] || traceId;
325
+ spanId = fields[BAGGAGE_KEYS.dispatchSpanId] || spanId;
326
+ const eventId = fields[BAGGAGE_KEYS.eventId];
327
+ if (!traceId || !spanId || !eventId) return null;
328
+ if (!isW3CTraceId(traceId) || !isW3CSpanId(spanId)) {
329
+ warnNonConformantIdsOnce(traceId, spanId);
330
+ }
331
+ const carrier = { traceId, spanId, eventId };
332
+ const childEventId = fields[BAGGAGE_KEYS.childEventId];
333
+ if (childEventId) carrier.childEventId = childEventId;
334
+ const name = fields[BAGGAGE_KEYS.name];
335
+ if (name) carrier.name = name;
336
+ const convoId = fields[BAGGAGE_KEYS.convoId];
337
+ if (convoId) carrier.convoId = convoId;
338
+ const userId = fields[BAGGAGE_KEYS.userId];
339
+ if (userId) carrier.userId = userId;
340
+ return carrier;
341
+ }
342
+ 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.`;
343
+ var UNRESOLVED_CARRIER_WARNED_KEY = /* @__PURE__ */ Symbol.for("raindrop.handoff.warnedUnresolvedCarrier");
344
+ function warnedHolder() {
345
+ return globalThis;
346
+ }
347
+ function resetUnresolvedCarrierWarning() {
348
+ warnedHolder()[UNRESOLVED_CARRIER_WARNED_KEY] = false;
349
+ }
350
+ function warnUnresolvedCarrier(headers, carrier) {
351
+ if (carrier) return;
352
+ if (!headers) return;
353
+ const holder = warnedHolder();
354
+ if (holder[UNRESOLVED_CARRIER_WARNED_KEY]) return;
355
+ holder[UNRESOLVED_CARRIER_WARNED_KEY] = true;
356
+ console.warn(UNRESOLVED_CARRIER_WARNING);
357
+ }
358
+ function safeParseStringRecord(raw) {
359
+ try {
360
+ const parsed = JSON.parse(raw);
361
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
362
+ const result = {};
363
+ for (const [key, value] of Object.entries(parsed)) {
364
+ if (typeof value === "string") result[key] = value;
365
+ }
366
+ return result;
367
+ } catch (e) {
368
+ return {};
369
+ }
370
+ }
66
371
  function runWithTracingSuppressed(fn) {
67
372
  const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
68
373
  if (typeof hook !== "function") return fn();
@@ -826,6 +1131,193 @@ var EventShipper = class {
826
1131
  }
827
1132
  }
828
1133
  };
1134
+ var MODEL_USAGE_ATTRIBUTES = {
1135
+ providerName: "gen_ai.provider.name",
1136
+ requestModel: "gen_ai.request.model",
1137
+ responseModel: "gen_ai.response.model",
1138
+ inputTokens: "gen_ai.usage.input_tokens",
1139
+ outputTokens: "gen_ai.usage.output_tokens",
1140
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
1141
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
1142
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
1143
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
1144
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
1145
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
1146
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
1147
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
1148
+ };
1149
+ var MODEL_PROVIDER_NAMES = [
1150
+ ["google.vertex", "google-vertex"],
1151
+ ["vertex.anthropic", "google-vertex"],
1152
+ ["amazon-bedrock", "amazon-bedrock"],
1153
+ ["bedrock", "amazon-bedrock"],
1154
+ ["anthropic", "anthropic"],
1155
+ ["openai", "openai"],
1156
+ ["google", "google"],
1157
+ ["azure", "azure"],
1158
+ ["openrouter", "openrouter"]
1159
+ ];
1160
+ function canonicalModelProvider(provider) {
1161
+ var _a;
1162
+ const normalizedProvider = provider.trim().toLowerCase();
1163
+ if (normalizedProvider === "gateway") return "vercel";
1164
+ const match = MODEL_PROVIDER_NAMES.find(
1165
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
1166
+ );
1167
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : provider;
1168
+ }
1169
+ function tokenCount(value) {
1170
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1171
+ }
1172
+ function exclusiveTokens({
1173
+ explicit,
1174
+ total,
1175
+ parts,
1176
+ semantics
1177
+ }) {
1178
+ if (explicit !== void 0) return explicit;
1179
+ if (total === void 0) return void 0;
1180
+ if (semantics === "exclusive") return total;
1181
+ const observedParts = parts.filter((part) => part !== void 0 && part > 0);
1182
+ if (semantics === void 0 && observedParts.length > 0) return void 0;
1183
+ const exclusive = total - observedParts.reduce((sum, part) => sum + part, 0);
1184
+ return exclusive >= 0 ? exclusive : void 0;
1185
+ }
1186
+ function buildModelUsageAttributes(usage) {
1187
+ const inputTokens = tokenCount(usage.inputTokens);
1188
+ const outputTokens = tokenCount(usage.outputTokens);
1189
+ const explicitNonCachedInputTokens = tokenCount(usage.nonCachedInputTokens);
1190
+ const explicitNonReasoningOutputTokens = tokenCount(usage.nonReasoningOutputTokens);
1191
+ const reasoningTokens = tokenCount(usage.reasoningTokens);
1192
+ const cacheReadInputTokens = tokenCount(usage.cacheReadInputTokens);
1193
+ const cacheWriteInputTokens = tokenCount(usage.cacheWriteInputTokens);
1194
+ const nonCachedInputTokens = exclusiveTokens({
1195
+ explicit: explicitNonCachedInputTokens,
1196
+ total: inputTokens,
1197
+ parts: [cacheReadInputTokens, cacheWriteInputTokens],
1198
+ semantics: usage.inputTokenSemantics
1199
+ });
1200
+ const nonReasoningOutputTokens = exclusiveTokens({
1201
+ explicit: explicitNonReasoningOutputTokens,
1202
+ total: outputTokens,
1203
+ parts: [reasoningTokens],
1204
+ semantics: usage.outputTokenSemantics
1205
+ });
1206
+ const exclusivePoolAttributes = nonCachedInputTokens !== void 0 && nonReasoningOutputTokens !== void 0 ? [
1207
+ // backend consumes this complete raindrop.* set as one atomic billing-pool contract.
1208
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonCachedInputTokens, nonCachedInputTokens),
1209
+ attrInt(MODEL_USAGE_ATTRIBUTES.nonReasoningOutputTokens, nonReasoningOutputTokens),
1210
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningOutputTokens, reasoningTokens),
1211
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadTokens, cacheReadInputTokens),
1212
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteTokens, cacheWriteInputTokens)
1213
+ ] : [];
1214
+ return [
1215
+ attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, inputTokens),
1216
+ attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, outputTokens),
1217
+ attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, reasoningTokens),
1218
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, cacheReadInputTokens),
1219
+ attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, cacheWriteInputTokens),
1220
+ ...exclusivePoolAttributes
1221
+ ].filter((attribute) => attribute !== void 0);
1222
+ }
1223
+ var STRING_ALIASES = {
1224
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
1225
+ };
1226
+ var NUMBER_ALIASES = {
1227
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
1228
+ "gen_ai.usage.prompt_tokens",
1229
+ "ai.usage.prompt_tokens",
1230
+ "ai.usage.promptTokens",
1231
+ "ai.usage.input_tokens",
1232
+ "ai.usage.inputTokens"
1233
+ ],
1234
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
1235
+ "gen_ai.usage.completion_tokens",
1236
+ "ai.usage.completion_tokens",
1237
+ "ai.usage.completionTokens",
1238
+ "ai.usage.output_tokens",
1239
+ "ai.usage.outputTokens"
1240
+ ],
1241
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
1242
+ "gen_ai.usage.reasoning.output_tokens",
1243
+ "ai.usage.reasoningTokens",
1244
+ "ai.usage.thoughts_tokens"
1245
+ ],
1246
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
1247
+ "gen_ai.usage.cache_read_tokens",
1248
+ "gen_ai.usage.cache_read.input_tokens",
1249
+ "ai.usage.cachedInputTokens",
1250
+ "ai.usage.cached_tokens",
1251
+ "ai.usage.cache_read_tokens",
1252
+ "ai.usage.cache_read_input_tokens",
1253
+ "ai.usage.cacheReadInputTokens"
1254
+ ],
1255
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
1256
+ "gen_ai.usage.cache_creation_input_tokens",
1257
+ "gen_ai.usage.cache_creation.input_tokens",
1258
+ "ai.usage.cacheWriteInputTokens",
1259
+ "ai.usage.cache_creation_input_tokens",
1260
+ "ai.usage.cacheCreationInputTokens"
1261
+ ]
1262
+ };
1263
+ function hasAttribute(attributes, key) {
1264
+ return attributes.some((attribute) => attribute.key === key);
1265
+ }
1266
+ function findStringAttribute(attributes, keys) {
1267
+ return attributes.find(
1268
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
1269
+ );
1270
+ }
1271
+ function findNumberAttribute(attributes, keys) {
1272
+ return attributes.find((attribute) => {
1273
+ if (!keys.includes(attribute.key)) return false;
1274
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
1275
+ return true;
1276
+ }
1277
+ const intValue = attribute.value.intValue;
1278
+ return integerValue(intValue) !== void 0;
1279
+ });
1280
+ }
1281
+ function integerValue(value) {
1282
+ if (typeof value === "number") {
1283
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1284
+ }
1285
+ if (typeof value !== "string") return void 0;
1286
+ const parsed = Number(value);
1287
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
1288
+ }
1289
+ function appendAlias(attributes, target, source) {
1290
+ if (!source || hasAttribute(attributes, target)) return attributes;
1291
+ return [...attributes, { key: target, value: { ...source.value } }];
1292
+ }
1293
+ function appendProviderAlias(attributes) {
1294
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
1295
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
1296
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
1297
+ const attribute = attrString(
1298
+ MODEL_USAGE_ATTRIBUTES.providerName,
1299
+ canonicalModelProvider(source.value.stringValue)
1300
+ );
1301
+ return attribute ? [...attributes, attribute] : attributes;
1302
+ }
1303
+ function normalizeModelUsageSpan(span) {
1304
+ const original = span.attributes;
1305
+ if (!original || original.length === 0) return span;
1306
+ const responseModel = findStringAttribute(original, [
1307
+ MODEL_USAGE_ATTRIBUTES.responseModel,
1308
+ "ai.response.model"
1309
+ ]);
1310
+ if (!responseModel) return span;
1311
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
1312
+ attributes = appendProviderAlias(attributes);
1313
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
1314
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
1315
+ }
1316
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
1317
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
1318
+ }
1319
+ return attributes === original ? span : { ...span, attributes };
1320
+ }
829
1321
  var DEFAULT_SECRET_KEY_NAMES = [
830
1322
  "apikey",
831
1323
  "apisecret",
@@ -1026,6 +1518,10 @@ var TraceShipper = class {
1026
1518
  return null;
1027
1519
  }
1028
1520
  }
1521
+ try {
1522
+ current = normalizeModelUsageSpan(current);
1523
+ } catch (e) {
1524
+ }
1029
1525
  if (!this.disableDefaultRedaction) {
1030
1526
  current = defaultTransformSpan(current);
1031
1527
  }
@@ -2060,6 +2556,15 @@ function attrsFromHeaders(headers) {
2060
2556
  if (!isRecord(headers)) return [];
2061
2557
  return Object.entries(headers).filter(([, v]) => typeof v === "string").map(([k, v]) => attrString(`ai.request.headers.${k}`, v));
2062
2558
  }
2559
+ function attrsFromModelProvider(provider) {
2560
+ if (typeof provider !== "string" || provider.length === 0) return [];
2561
+ const providerName = canonicalModelProvider(provider);
2562
+ return [
2563
+ attrString("ai.model.provider", provider),
2564
+ attrString(MODEL_USAGE_ATTRIBUTES.providerName, providerName),
2565
+ attrString("gen_ai.system", provider)
2566
+ ];
2567
+ }
2063
2568
  function attrsFromSettings(args) {
2064
2569
  if (!isRecord(args)) return [];
2065
2570
  const result = [];
@@ -2242,6 +2747,473 @@ function runWithParentToolContext(ctx, fn) {
2242
2747
  return getStorage2().run(ctx, fn);
2243
2748
  }
2244
2749
 
2750
+ // src/internal/subagents.ts
2751
+ var SUBAGENT_CANCELLED_OUTPUT = "Cancelled before producing a result.";
2752
+ var SUBAGENT_ABORTED_OUTPUT = "Aborted before producing a result.";
2753
+ var SUBAGENT_FAILED_OPERATION_ID = SUBAGENT_ABORT_OPERATION_ID;
2754
+ var MAX_SETTLED_EVENT_IDS = 1024;
2755
+ function isTraceCarrier(value) {
2756
+ const candidate = value;
2757
+ return typeof candidate.traceId === "string" && typeof candidate.spanId === "string" && typeof candidate.eventId === "string";
2758
+ }
2759
+ function attrsFrom(metadata) {
2760
+ return Object.entries(withMetadataPrefix(metadata)).map(([key, value]) => attrString(key, value));
2761
+ }
2762
+ function attrsExact(attributes) {
2763
+ return Object.entries(attributes).map(([key, value]) => attrString(key, value));
2764
+ }
2765
+ function handoffSpanAttrs(link) {
2766
+ if (!link) return [];
2767
+ return attrsFrom(detachedChildMetadata(link));
2768
+ }
2769
+ function subagentNameAttrs(name, link) {
2770
+ if (!name || (link == null ? void 0 : link.name)) return [];
2771
+ return attrsFrom({ [HANDOFF_ATTRIBUTE_SUFFIXES.name]: name });
2772
+ }
2773
+ function cancelledSpanAttrs() {
2774
+ return attrsExact(cancelledTerminalAttributes());
2775
+ }
2776
+ function describeAbortReason(reason) {
2777
+ if (reason instanceof Error) return `Aborted: ${reason.message}`;
2778
+ if (typeof reason === "string" && reason.trim().length > 0) return reason;
2779
+ if (reason === void 0 || reason === null) return SUBAGENT_ABORTED_OUTPUT;
2780
+ const serialized = boundedStringify(reason);
2781
+ return serialized ? `Aborted: ${serialized}` : SUBAGENT_ABORTED_OUTPUT;
2782
+ }
2783
+ function stampOpenSpan(span, metadata) {
2784
+ if (span.endTimeUnixNano) return;
2785
+ span.attributes.push(...attrsFrom(metadata));
2786
+ }
2787
+ function stampOpenSpanExact(span, attributes) {
2788
+ if (span.endTimeUnixNano) return;
2789
+ span.attributes.push(...attrsExact(attributes));
2790
+ }
2791
+ var SubagentApi = class {
2792
+ constructor(opts) {
2793
+ var _a;
2794
+ this.opts = opts;
2795
+ this.settledEventIds = (_a = opts.settledEventIds) != null ? _a : /* @__PURE__ */ new Set();
2796
+ }
2797
+ /** True when this child's outcome has already been reported. */
2798
+ wasSettledExplicitly(eventId) {
2799
+ return this.settledEventIds.has(eventId);
2800
+ }
2801
+ /**
2802
+ * Record an outcome reported somewhere other than `cancel()` / `fail()`, so
2803
+ * first-outcome-wins holds across every path that can state one.
2804
+ *
2805
+ * An aborted generation is such a path: an AbortSignal cancellation IS the
2806
+ * run's outcome, and the `fail(err)` that a catch block around the aborted
2807
+ * call naturally reports would otherwise error the child's spans — which is
2808
+ * the only thing separating `failed` from `cancelled`.
2809
+ */
2810
+ noteSettled(eventId) {
2811
+ this.remember(eventId);
2812
+ }
2813
+ remember(eventId) {
2814
+ if (this.settledEventIds.has(eventId)) return;
2815
+ if (this.settledEventIds.size >= MAX_SETTLED_EVENT_IDS) {
2816
+ const oldest = this.settledEventIds.values().next();
2817
+ if (!oldest.done) this.settledEventIds.delete(oldest.value);
2818
+ }
2819
+ this.settledEventIds.add(eventId);
2820
+ }
2821
+ /**
2822
+ * Record a detached sub-agent on this turn: allocate the child's event id,
2823
+ * stamp the dispatch, and return the headers to send with the job.
2824
+ *
2825
+ * This dispatches nothing — you do, moments later, with the returned headers.
2826
+ * Nothing here waits for the child either, or claims to know how it is doing.
2827
+ * Readers derive that from the child's own event.
2828
+ */
2829
+ subagent(options = {}) {
2830
+ var _a;
2831
+ const childEventId = (_a = options.childEventId) != null ? _a : randomUUID();
2832
+ const name = options.name;
2833
+ try {
2834
+ return this.recordLaunch(childEventId, name, options);
2835
+ } catch (err) {
2836
+ if (this.opts.debug) {
2837
+ console.warn(
2838
+ `[raindrop-ai/ai-sdk] sub-agent dispatch not recorded: ${err instanceof Error ? err.message : err}`
2839
+ );
2840
+ }
2841
+ return { childEventId, name, headers: {}, carrier: null };
2842
+ }
2843
+ }
2844
+ recordLaunch(childEventId, name, options) {
2845
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
2846
+ const active = (_a = this.opts.spanSource) == null ? void 0 : _a.activeDispatch();
2847
+ const inherited = getContextManager().getParentSpanIds();
2848
+ 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;
2849
+ 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;
2850
+ 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;
2851
+ if (!eventId) {
2852
+ return { childEventId, name, headers: {}, carrier: null };
2853
+ }
2854
+ const dispatch = detachedDispatchMetadata({ childEventId, name });
2855
+ const toolEvents = toolEventsAllowMetadata();
2856
+ for (const span of (_l = active == null ? void 0 : active.turnSpans) != null ? _l : []) {
2857
+ stampOpenSpan(span, toolEvents);
2858
+ }
2859
+ let dispatchIds;
2860
+ if (active == null ? void 0 : active.dispatchSpan) {
2861
+ stampOpenSpan(active.dispatchSpan, dispatch);
2862
+ dispatchIds = active.dispatchSpan.ids;
2863
+ } else if (this.opts.sendTraces) {
2864
+ dispatchIds = this.synthesizeDispatchSpan({
2865
+ childEventId,
2866
+ name,
2867
+ toolName: options.toolName,
2868
+ eventId,
2869
+ userId,
2870
+ convoId,
2871
+ input: options.input,
2872
+ // No active turn means no telemetry setting to honor: `input` is then
2873
+ // recorded on the explicit say-so of whoever passed it.
2874
+ recordInputs: (_m = active == null ? void 0 : active.recordInputs) != null ? _m : true,
2875
+ dispatch,
2876
+ parent: (inherited == null ? void 0 : inherited.eventId) === eventId ? inherited : void 0
2877
+ }).ids;
2878
+ }
2879
+ const traceId = base64ToHex((_o = (_n = dispatchIds == null ? void 0 : dispatchIds.traceIdB64) != null ? _n : inherited == null ? void 0 : inherited.traceIdB64) != null ? _o : "");
2880
+ const spanId = base64ToHex((_p = dispatchIds == null ? void 0 : dispatchIds.spanIdB64) != null ? _p : "");
2881
+ if (!traceId || !spanId) {
2882
+ if (this.opts.debug) {
2883
+ console.warn(
2884
+ "[raindrop-ai/ai-sdk] sub-agent dispatch recorded without a carrier: no dispatch span to reference (traces disabled?)"
2885
+ );
2886
+ }
2887
+ return { childEventId, name, headers: {}, carrier: null };
2888
+ }
2889
+ const carrier = {
2890
+ traceId,
2891
+ spanId,
2892
+ eventId,
2893
+ childEventId,
2894
+ ...name ? { name } : {},
2895
+ ...convoId ? { convoId } : {},
2896
+ ...userId ? { userId } : {}
2897
+ };
2898
+ return { childEventId, name, headers: toHeaders(carrier), carrier };
2899
+ }
2900
+ /**
2901
+ * Record a dispatch span for a launch with no open tool-call span to decorate:
2902
+ * a launch from outside a tool's `execute`, or from a caller that is not an
2903
+ * LLM turn at all (an HTTP handler that enqueues jobs). Shaped as a tool call
2904
+ * so it reads identically to a model-issued launch.
2905
+ */
2906
+ synthesizeDispatchSpan(args) {
2907
+ var _a;
2908
+ const spanName = (_a = args.toolName) != null ? _a : DEFAULT_DISPATCH_SPAN_NAME;
2909
+ const span = this.opts.traceShipper.startSpan({
2910
+ name: spanName,
2911
+ parent: args.parent,
2912
+ eventId: args.eventId,
2913
+ userId: args.userId,
2914
+ convoId: args.convoId,
2915
+ operationId: "ai.toolCall",
2916
+ attributes: [
2917
+ attrString("operation.name", "ai.toolCall"),
2918
+ attrString("resource.name", spanName),
2919
+ attrString("ai.toolCall.name", spanName),
2920
+ attrString("ai.toolCall.id", `call_${args.childEventId.replace(/-/g, "").slice(0, 12)}`),
2921
+ // Gated like every other input the turn's spans record: a caller that
2922
+ // disabled input capture must not leak the payload through the one
2923
+ // span this API synthesizes on its behalf.
2924
+ ...args.recordInputs ? [attrString("ai.toolCall.args", boundedStringify(args.input))] : [],
2925
+ ...attrsFrom(args.dispatch)
2926
+ ]
2927
+ });
2928
+ this.opts.traceShipper.endSpan(span, {
2929
+ // A dispatch returns a handle, not an answer — the answer is the child's.
2930
+ attributes: [
2931
+ attrString(
2932
+ "ai.toolCall.result",
2933
+ boundedStringify({ jobId: args.childEventId, status: "accepted" })
2934
+ )
2935
+ ]
2936
+ });
2937
+ return span;
2938
+ }
2939
+ /**
2940
+ * Adopt a carrier as the current run's parent reference.
2941
+ *
2942
+ * A missing carrier is a legitimate state — the child was invoked directly
2943
+ * rather than dispatched — so this always returns a usable run and leaves
2944
+ * `parent` null. Call sites stay unconditional.
2945
+ */
2946
+ resume(carrierOrHeaders, options = {}) {
2947
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2948
+ const carrier = carrierOrHeaders ? isTraceCarrier(carrierOrHeaders) ? carrierOrHeaders : this.readCarrier(carrierOrHeaders) : null;
2949
+ warnUnresolvedCarrier(carrierOrHeaders, carrier);
2950
+ const name = (_a = carrier == null ? void 0 : carrier.name) != null ? _a : options.name;
2951
+ const eventId = (_c = (_b = carrier == null ? void 0 : carrier.childEventId) != null ? _b : options.eventId) != null ? _c : randomUUID();
2952
+ 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;
2953
+ 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;
2954
+ const parent = carrier ? {
2955
+ parentEventId: carrier.eventId,
2956
+ ...carrier.spanId ? { parentSpanId: carrier.spanId } : {},
2957
+ ...name ? { name } : {}
2958
+ } : null;
2959
+ return {
2960
+ eventId,
2961
+ name,
2962
+ userId,
2963
+ convoId,
2964
+ parent,
2965
+ metadata: {
2966
+ eventId,
2967
+ ...userId ? { userId } : {},
2968
+ ...convoId ? { convoId } : {},
2969
+ ...parent ? { subagent: parent } : {}
2970
+ },
2971
+ cancel: (reason) => this.settle(eventId, { userId, convoId, name, cancelled: true, output: reason }),
2972
+ fail: (reason) => this.settle(eventId, {
2973
+ userId,
2974
+ convoId,
2975
+ name,
2976
+ handoff: parent != null ? parent : void 0,
2977
+ cancelled: false,
2978
+ output: describeAbortReason(reason),
2979
+ error: reason
2980
+ })
2981
+ };
2982
+ }
2983
+ /**
2984
+ * A carrier arrives from outside this process, so a hostile or merely broken
2985
+ * header bag must cost the job its link, never its run.
2986
+ */
2987
+ readCarrier(headers) {
2988
+ try {
2989
+ return fromHeaders(headers);
2990
+ } catch (err) {
2991
+ if (this.opts.debug) {
2992
+ console.warn(
2993
+ `[raindrop-ai/ai-sdk] sub-agent carrier ignored: ${err instanceof Error ? err.message : err}`
2994
+ );
2995
+ }
2996
+ return null;
2997
+ }
2998
+ }
2999
+ /**
3000
+ * Mark a failed child's own spans as errored.
3001
+ *
3002
+ * Reporting the reason as output is what gets the child an event at all, but
3003
+ * output alone is not enough: status is derived from span shape, and a child
3004
+ * with final output and no error spans derives `finished`. A failed job
3005
+ * reporting success is worse than one reporting nothing, so the failure has
3006
+ * to land on the telemetry too, not just in the text.
3007
+ *
3008
+ * This is the child describing its own run, not a caller writing a status it
3009
+ * cannot observe — the child's own spans are exactly where the contract puts
3010
+ * failure.
3011
+ */
3012
+ recordFailureOnSpans(eventId, args, reason) {
3013
+ var _a, _b, _c;
3014
+ if (!this.opts.sendTraces) return;
3015
+ const failure = (_a = args.error) != null ? _a : reason;
3016
+ let marked = 0;
3017
+ for (const span2 of (_c = (_b = this.opts.spanSource) == null ? void 0 : _b.openSpansForEvent(eventId)) != null ? _c : []) {
3018
+ if (span2.endTimeUnixNano) continue;
3019
+ this.opts.traceShipper.endSpan(span2, { error: failure });
3020
+ marked++;
3021
+ }
3022
+ if (marked > 0) return;
3023
+ const spanName = SUBAGENT_ABORT_SPAN_NAME;
3024
+ const span = this.opts.traceShipper.startSpan({
3025
+ name: spanName,
3026
+ eventId,
3027
+ userId: args.userId,
3028
+ convoId: args.convoId,
3029
+ // Required, not decorative: ingest drops any span carrying no
3030
+ // `ai.operationId` (or traceloop / `gen_ai.*` key) and still answers 200,
3031
+ // so without this the failure disappears silently. The value is
3032
+ // deliberately none of the generate/stream/tool/embed forms, which is
3033
+ // what classifies the span as INTERNAL rather than mislabelling a failed
3034
+ // job as a model call or a tool call.
3035
+ operationId: SUBAGENT_FAILED_OPERATION_ID,
3036
+ // Carries the reverse reference like every other span the child emits:
3037
+ // this is often the child's ONLY span, and a span read on its own is the
3038
+ // whole reason the reference cannot live on the root alone. The name is
3039
+ // stamped even for an unlinked run, which has no caller to reference —
3040
+ // `span_name` no longer says which sub-agent this was, so the attribute
3041
+ // is the only thing that can.
3042
+ attributes: [
3043
+ attrString("resource.name", spanName),
3044
+ ...handoffSpanAttrs(args.handoff),
3045
+ ...subagentNameAttrs(args.name, args.handoff)
3046
+ ]
3047
+ });
3048
+ this.opts.traceShipper.endSpan(span, { error: failure });
3049
+ }
3050
+ async settle(eventId, args) {
3051
+ var _a, _b;
3052
+ if (this.settledEventIds.has(eventId)) {
3053
+ if (this.opts.debug) {
3054
+ console.warn(
3055
+ `[raindrop-ai/ai-sdk] sub-agent ${eventId} already reported an outcome; ignoring the second one`
3056
+ );
3057
+ }
3058
+ return;
3059
+ }
3060
+ this.remember(eventId);
3061
+ const output = args.output && args.output.trim().length > 0 ? args.output : args.cancelled ? SUBAGENT_CANCELLED_OUTPUT : SUBAGENT_ABORTED_OUTPUT;
3062
+ try {
3063
+ if (args.cancelled) {
3064
+ for (const span of (_b = (_a = this.opts.spanSource) == null ? void 0 : _a.openSpansForEvent(eventId)) != null ? _b : []) {
3065
+ stampOpenSpanExact(span, cancelledTerminalAttributes());
3066
+ }
3067
+ } else {
3068
+ this.recordFailureOnSpans(eventId, args, output);
3069
+ }
3070
+ } catch (err) {
3071
+ if (this.opts.debug) {
3072
+ console.warn(
3073
+ `[raindrop-ai/ai-sdk] sub-agent outcome not recorded on spans: ${err instanceof Error ? err.message : err}`
3074
+ );
3075
+ }
3076
+ }
3077
+ if (!this.opts.sendEvents) return;
3078
+ await this.opts.eventShipper.patch(eventId, {
3079
+ userId: args.userId,
3080
+ convoId: args.convoId,
3081
+ // An event is only created from a run that reported something. A child
3082
+ // that dies silently produces no event at all, which pins the caller on
3083
+ // `queued` forever — indistinguishable from a job that never started.
3084
+ output,
3085
+ ...args.cancelled ? { properties: { [HANDOFF_TERMINAL_PROPERTY_KEY]: HANDOFF_TERMINAL_CANCELLED } } : {},
3086
+ isPending: false
3087
+ }).catch((err) => {
3088
+ if (this.opts.debug) {
3089
+ console.warn(
3090
+ `[raindrop-ai/ai-sdk] sub-agent outcome patch failed: ${err instanceof Error ? err.message : err}`
3091
+ );
3092
+ }
3093
+ });
3094
+ }
3095
+ };
3096
+
3097
+ // src/internal/usage.ts
3098
+ function firstTokenCount(...values) {
3099
+ for (const value of values) {
3100
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
3101
+ return value;
3102
+ }
3103
+ }
3104
+ return void 0;
3105
+ }
3106
+ function flatUsageSemantics(provider) {
3107
+ const normalizedProvider = typeof provider === "string" ? provider.trim().toLowerCase() : void 0;
3108
+ if (normalizedProvider === "anthropic" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("anthropic.")) || normalizedProvider === "vertex.anthropic" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("vertex.anthropic."))) {
3109
+ return {
3110
+ inputTokenSemantics: "exclusive",
3111
+ outputTokenSemantics: "total"
3112
+ };
3113
+ }
3114
+ if (normalizedProvider === "openai" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("openai.")) || normalizedProvider === "azure" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("azure."))) {
3115
+ return {
3116
+ inputTokenSemantics: "total",
3117
+ outputTokenSemantics: "total"
3118
+ };
3119
+ }
3120
+ if (normalizedProvider === "google" || (normalizedProvider == null ? void 0 : normalizedProvider.startsWith("google."))) {
3121
+ return {
3122
+ inputTokenSemantics: "total",
3123
+ // Google v5 reports candidate output and thoughts separately.
3124
+ outputTokenSemantics: "exclusive"
3125
+ };
3126
+ }
3127
+ return {};
3128
+ }
3129
+ var CACHE_WRITE_METADATA_KEYS = [
3130
+ ["anthropic", "cacheCreationInputTokens"],
3131
+ ["openai", "cacheWriteTokens"],
3132
+ ["bedrock", "cacheWriteInputTokens"]
3133
+ ];
3134
+ function cacheWriteTokensFromProviderMetadata(providerMetadata) {
3135
+ if (!isRecord(providerMetadata)) return void 0;
3136
+ return firstTokenCount(
3137
+ ...CACHE_WRITE_METADATA_KEYS.flatMap(([namespace, key]) => {
3138
+ const scope = providerMetadata[namespace];
3139
+ if (!isRecord(scope)) return [];
3140
+ const usage = scope["usage"];
3141
+ return [scope[key], isRecord(usage) ? usage[key] : void 0];
3142
+ })
3143
+ );
3144
+ }
3145
+ function extractUsageMetricsFromUsage(usage, provider, providerMetadata) {
3146
+ if (!isRecord(usage)) return {};
3147
+ const inputTokenValue = usage["inputTokens"];
3148
+ const outputTokenValue = usage["outputTokens"];
3149
+ const inputTokenDetails = usage["inputTokenDetails"];
3150
+ const outputTokenDetails = usage["outputTokenDetails"];
3151
+ const hasStructuredUsage = isRecord(inputTokenValue) || isRecord(outputTokenValue) || isRecord(inputTokenDetails) || isRecord(outputTokenDetails);
3152
+ const inputTokens = firstTokenCount(
3153
+ isRecord(inputTokenValue) ? inputTokenValue["total"] : void 0,
3154
+ inputTokenValue,
3155
+ usage["promptTokens"],
3156
+ usage["prompt_tokens"]
3157
+ );
3158
+ const outputTokens = firstTokenCount(
3159
+ isRecord(outputTokenValue) ? outputTokenValue["total"] : void 0,
3160
+ outputTokenValue,
3161
+ usage["completionTokens"],
3162
+ usage["completion_tokens"]
3163
+ );
3164
+ const reasoningTokens = firstTokenCount(
3165
+ isRecord(outputTokenValue) ? outputTokenValue["reasoning"] : void 0,
3166
+ isRecord(outputTokenDetails) ? outputTokenDetails["reasoningTokens"] : void 0,
3167
+ usage["reasoningTokens"],
3168
+ usage["completionReasoningTokens"],
3169
+ usage["completion_reasoning_tokens"],
3170
+ usage["reasoning_tokens"],
3171
+ usage["thinkingTokens"],
3172
+ usage["thinking_tokens"]
3173
+ );
3174
+ const cacheReadInputTokens = firstTokenCount(
3175
+ isRecord(inputTokenValue) ? inputTokenValue["cacheRead"] : void 0,
3176
+ isRecord(inputTokenDetails) ? inputTokenDetails["cacheReadTokens"] : void 0,
3177
+ usage["cachedInputTokens"],
3178
+ usage["promptCachedTokens"],
3179
+ usage["prompt_cached_tokens"]
3180
+ );
3181
+ const cacheWriteInputTokens = firstTokenCount(
3182
+ isRecord(inputTokenValue) ? inputTokenValue["cacheWrite"] : void 0,
3183
+ isRecord(inputTokenDetails) ? inputTokenDetails["cacheWriteTokens"] : void 0,
3184
+ usage["cacheWriteInputTokens"],
3185
+ usage["cacheCreationInputTokens"],
3186
+ usage["cache_creation_input_tokens"],
3187
+ cacheWriteTokensFromProviderMetadata(providerMetadata)
3188
+ );
3189
+ const nonCachedInputTokens = firstTokenCount(
3190
+ isRecord(inputTokenValue) ? inputTokenValue["noCache"] : void 0,
3191
+ isRecord(inputTokenDetails) ? inputTokenDetails["noCacheTokens"] : void 0
3192
+ );
3193
+ const nonReasoningOutputTokens = firstTokenCount(
3194
+ isRecord(outputTokenValue) ? outputTokenValue["text"] : void 0,
3195
+ isRecord(outputTokenDetails) ? outputTokenDetails["textTokens"] : void 0
3196
+ );
3197
+ const totalTokens = firstTokenCount(
3198
+ usage["totalTokens"],
3199
+ usage["tokens"],
3200
+ usage["total_tokens"],
3201
+ inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0
3202
+ );
3203
+ return {
3204
+ inputTokens,
3205
+ outputTokens,
3206
+ totalTokens,
3207
+ nonCachedInputTokens,
3208
+ nonReasoningOutputTokens,
3209
+ reasoningTokens,
3210
+ cacheReadInputTokens,
3211
+ cachedInputTokens: cacheReadInputTokens,
3212
+ cacheWriteInputTokens,
3213
+ ...hasStructuredUsage ? {} : flatUsageSemantics(provider)
3214
+ };
3215
+ }
3216
+
2245
3217
  // src/internal/raindrop-telemetry-integration.ts
2246
3218
  function hasUnresolvedToolApproval(event) {
2247
3219
  var _a;
@@ -2314,6 +3286,7 @@ var RaindropTelemetryIntegration = class {
2314
3286
  eventName: (_f = callMeta.eventName) != null ? _f : (_e = this.defaultContext) == null ? void 0 : _e.eventName
2315
3287
  };
2316
3288
  const inherited = getContextManager().getParentSpanIds();
3289
+ const handoff = readDetachedChildLink(metadata);
2317
3290
  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;
2318
3291
  const explicitEventId = callMeta.eventId && !eventIdGenerated ? callMeta.eventId : void 0;
2319
3292
  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();
@@ -2343,7 +3316,10 @@ var RaindropTelemetryIntegration = class {
2343
3316
  attrString("ai.toolCall.name", subagentName),
2344
3317
  attrString("raindrop.subagent.name", subagentName),
2345
3318
  attrString("raindrop.agent.role", "subagent"),
2346
- ...parentAttrs
3319
+ ...parentAttrs,
3320
+ // Top of this child's tree, so the reverse reference matters as much
3321
+ // here as on the root: these are the spans a reader lands on first.
3322
+ ...handoffSpanAttrs(handoff)
2347
3323
  ]
2348
3324
  });
2349
3325
  const subagentParentRef = this.spanParentRef(subagentToolCallSpan);
@@ -2356,7 +3332,8 @@ var RaindropTelemetryIntegration = class {
2356
3332
  attributes: [
2357
3333
  attrString("operation.name", "agent.subagent"),
2358
3334
  attrString("raindrop.span.kind", "llm_call"),
2359
- attrString("raindrop.subagent.name", subagentName)
3335
+ attrString("raindrop.subagent.name", subagentName),
3336
+ ...handoffSpanAttrs(handoff)
2360
3337
  ]
2361
3338
  });
2362
3339
  this.traceShipper.endSpan(markerSpan);
@@ -2411,6 +3388,8 @@ var RaindropTelemetryIntegration = class {
2411
3388
  rootParent: rootSpan ? this.spanParentRef(rootSpan) : rootParentOverride != null ? rootParentOverride : inheritedParent,
2412
3389
  stepSpan: void 0,
2413
3390
  stepParent: void 0,
3391
+ stepProvider: void 0,
3392
+ stepModelId: void 0,
2414
3393
  toolSpans: /* @__PURE__ */ new Map(),
2415
3394
  embedSpans: /* @__PURE__ */ new Map(),
2416
3395
  parentContextToolCallIds: /* @__PURE__ */ new Set(),
@@ -2422,7 +3401,9 @@ var RaindropTelemetryIntegration = class {
2422
3401
  inputText: isEmbed ? void 0 : this.extractInputText(event),
2423
3402
  toolCallCount: 0,
2424
3403
  subagentName,
2425
- subagentToolCallSpan
3404
+ subagentToolCallSpan,
3405
+ handoff,
3406
+ nestedInEvent: inheritedParent !== void 0
2426
3407
  });
2427
3408
  };
2428
3409
  // ── onStepStart ─────────────────────────────────────────────────────────
@@ -2464,15 +3445,17 @@ var RaindropTelemetryIntegration = class {
2464
3445
  attrString("operation.name", operationName),
2465
3446
  attrString("resource.name", resourceName),
2466
3447
  attrString("ai.telemetry.functionId", state.functionId),
2467
- attrString("ai.model.provider", event.provider),
3448
+ ...attrsFromModelProvider(event.provider),
2468
3449
  attrString("ai.model.id", event.modelId),
2469
- attrString("gen_ai.system", event.provider),
2470
- attrString("gen_ai.request.model", event.modelId),
2471
- ...inputAttrs
3450
+ attrString(MODEL_USAGE_ATTRIBUTES.requestModel, event.modelId),
3451
+ ...inputAttrs,
3452
+ ...handoffSpanAttrs(state.handoff)
2472
3453
  ]
2473
3454
  });
2474
3455
  state.stepSpan = stepSpan;
2475
3456
  state.stepParent = this.spanParentRef(stepSpan);
3457
+ state.stepProvider = event.provider;
3458
+ state.stepModelId = event.modelId;
2476
3459
  };
2477
3460
  this.onToolExecutionStart = (event) => this.toolExecutionStart(event);
2478
3461
  this.onToolExecutionEnd = (event) => this.toolExecutionEnd(event);
@@ -2523,23 +3506,24 @@ var RaindropTelemetryIntegration = class {
2523
3506
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2524
3507
  const state = this.getState(event.callId);
2525
3508
  if (!(state == null ? void 0 : state.stepSpan)) return;
3509
+ const responseModelId = (_b = (_a = event.response) == null ? void 0 : _a.modelId) != null ? _b : state.stepModelId;
2526
3510
  const outputAttrs = [];
2527
3511
  if (state.recordOutputs) {
2528
3512
  outputAttrs.push(
2529
3513
  attrString("ai.response.finishReason", event.finishReason),
2530
- attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2531
- attrString("ai.response.id", (_b = event.response) == null ? void 0 : _b.id),
2532
- attrString("ai.response.model", (_c = event.response) == null ? void 0 : _c.modelId),
3514
+ attrString("ai.response.text", capText2((_c = event.text) != null ? _c : void 0)),
3515
+ attrString("ai.response.id", (_d = event.response) == null ? void 0 : _d.id),
3516
+ attrString("ai.response.model", responseModelId),
2533
3517
  attrString(
2534
3518
  "ai.response.timestamp",
2535
- ((_d = event.response) == null ? void 0 : _d.timestamp) instanceof Date ? event.response.timestamp.toISOString() : (_e = event.response) == null ? void 0 : _e.timestamp
3519
+ ((_e = event.response) == null ? void 0 : _e.timestamp) instanceof Date ? event.response.timestamp.toISOString() : (_f = event.response) == null ? void 0 : _f.timestamp
2536
3520
  ),
2537
3521
  attrString(
2538
3522
  "ai.response.providerMetadata",
2539
3523
  event.providerMetadata ? safeJsonWithUint8(event.providerMetadata) : void 0
2540
3524
  )
2541
3525
  );
2542
- if (((_f = event.toolCalls) == null ? void 0 : _f.length) > 0) {
3526
+ if (((_g = event.toolCalls) == null ? void 0 : _g.length) > 0) {
2543
3527
  outputAttrs.push(
2544
3528
  attrString(
2545
3529
  "ai.response.toolCalls",
@@ -2553,7 +3537,7 @@ var RaindropTelemetryIntegration = class {
2553
3537
  )
2554
3538
  );
2555
3539
  }
2556
- if (((_g = event.reasoning) == null ? void 0 : _g.length) > 0) {
3540
+ if (((_h = event.reasoning) == null ? void 0 : _h.length) > 0) {
2557
3541
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2558
3542
  if (reasoningText) {
2559
3543
  outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
@@ -2562,25 +3546,31 @@ var RaindropTelemetryIntegration = class {
2562
3546
  }
2563
3547
  outputAttrs.push(
2564
3548
  attrStringArray("gen_ai.response.finish_reasons", [event.finishReason]),
2565
- attrString("gen_ai.response.id", (_h = event.response) == null ? void 0 : _h.id),
2566
- attrString("gen_ai.response.model", (_i = event.response) == null ? void 0 : _i.modelId)
3549
+ attrString("gen_ai.response.id", (_i = event.response) == null ? void 0 : _i.id),
3550
+ attrString(MODEL_USAGE_ATTRIBUTES.responseModel, responseModelId)
2567
3551
  );
2568
3552
  const usage = event.usage;
2569
3553
  if (usage) {
3554
+ const usageMetrics = extractUsageMetricsFromUsage(
3555
+ usage,
3556
+ state.stepProvider,
3557
+ event.providerMetadata
3558
+ );
2570
3559
  outputAttrs.push(
2571
- attrInt("ai.usage.inputTokens", usage.inputTokens),
2572
- attrInt("ai.usage.outputTokens", usage.outputTokens),
2573
- attrInt("ai.usage.totalTokens", usage.totalTokens),
2574
- attrInt("ai.usage.reasoningTokens", usage.reasoningTokens),
2575
- attrInt("ai.usage.cachedInputTokens", usage.cachedInputTokens),
2576
- attrInt("gen_ai.usage.input_tokens", usage.inputTokens),
2577
- attrInt("gen_ai.usage.output_tokens", usage.outputTokens)
3560
+ attrInt("ai.usage.inputTokens", usageMetrics.inputTokens),
3561
+ attrInt("ai.usage.outputTokens", usageMetrics.outputTokens),
3562
+ attrInt("ai.usage.totalTokens", usageMetrics.totalTokens),
3563
+ attrInt("ai.usage.reasoningTokens", usageMetrics.reasoningTokens),
3564
+ attrInt("ai.usage.cachedInputTokens", usageMetrics.cachedInputTokens),
3565
+ ...buildModelUsageAttributes(usageMetrics)
2578
3566
  );
2579
3567
  }
2580
3568
  this.emitProviderExecutedToolSpans(event, state);
2581
3569
  this.traceShipper.endSpan(state.stepSpan, { attributes: outputAttrs });
2582
3570
  state.stepSpan = void 0;
2583
3571
  state.stepParent = void 0;
3572
+ state.stepProvider = void 0;
3573
+ state.stepModelId = void 0;
2584
3574
  };
2585
3575
  // ── onEmbedStart ────────────────────────────────────────────────────────
2586
3576
  this.onEmbedStart = (event) => {
@@ -2598,7 +3588,8 @@ var RaindropTelemetryIntegration = class {
2598
3588
  attrString("operation.name", operationName),
2599
3589
  attrString("resource.name", resourceName),
2600
3590
  attrString("ai.telemetry.functionId", state.functionId),
2601
- ...inputAttrs
3591
+ ...inputAttrs,
3592
+ ...handoffSpanAttrs(state.handoff)
2602
3593
  ]
2603
3594
  });
2604
3595
  state.embedSpans.set(event.embedCallId, embedSpan);
@@ -2655,6 +3646,8 @@ var RaindropTelemetryIntegration = class {
2655
3646
  const actualError = (_a = event.error) != null ? _a : error;
2656
3647
  if (state.stepSpan) {
2657
3648
  this.traceShipper.endSpan(state.stepSpan, { error: actualError });
3649
+ state.stepProvider = void 0;
3650
+ state.stepModelId = void 0;
2658
3651
  }
2659
3652
  for (const embedSpan of state.embedSpans.values()) {
2660
3653
  this.traceShipper.endSpan(embedSpan, { error: actualError });
@@ -2668,15 +3661,26 @@ var RaindropTelemetryIntegration = class {
2668
3661
  this.traceShipper.endSpan(toolSpan, { error: actualError });
2669
3662
  }
2670
3663
  state.toolSpans.clear();
3664
+ const abortReason = describeAbortReason(actualError);
2671
3665
  if (state.rootSpan) {
2672
- this.traceShipper.endSpan(state.rootSpan, { error: actualError });
3666
+ this.traceShipper.endSpan(state.rootSpan, {
3667
+ error: actualError,
3668
+ attributes: this.abortOutcomeAttrs(state, abortReason, "error")
3669
+ });
2673
3670
  }
2674
3671
  if (state.subagentToolCallSpan) {
2675
3672
  this.traceShipper.endSpan(state.subagentToolCallSpan, { error: actualError });
2676
3673
  }
2677
3674
  const isEmbed = state.operationId === "ai.embed" || state.operationId === "ai.embedMany";
2678
3675
  if (!isEmbed) {
2679
- this.finalizeGenerateEvent(state, state.accumulatedText || void 0, void 0);
3676
+ const errorIsTheRunsOutcome = state.handoff !== void 0 && !state.nestedInEvent;
3677
+ this.finalizeGenerateEvent(
3678
+ state,
3679
+ state.accumulatedText || void 0,
3680
+ void 0,
3681
+ false,
3682
+ errorIsTheRunsOutcome ? { fallbackOutput: abortReason } : void 0
3683
+ );
2680
3684
  }
2681
3685
  this.cleanup(event.callId);
2682
3686
  };
@@ -2698,6 +3702,8 @@ var RaindropTelemetryIntegration = class {
2698
3702
  this.traceShipper.endSpan(state.stepSpan, { attributes: [abortedAttr] });
2699
3703
  state.stepSpan = void 0;
2700
3704
  state.stepParent = void 0;
3705
+ state.stepProvider = void 0;
3706
+ state.stepModelId = void 0;
2701
3707
  }
2702
3708
  for (const embedSpan of state.embedSpans.values()) {
2703
3709
  this.traceShipper.endSpan(embedSpan, { attributes: [abortedAttr] });
@@ -2711,9 +3717,19 @@ var RaindropTelemetryIntegration = class {
2711
3717
  this.traceShipper.endSpan(toolSpan, { attributes: [abortedAttr] });
2712
3718
  }
2713
3719
  state.toolSpans.clear();
3720
+ const abortIsTheRunsOutcome = state.handoff !== void 0 && !state.nestedInEvent;
2714
3721
  if (state.rootSpan) {
2715
3722
  this.traceShipper.endSpan(state.rootSpan, {
2716
- attributes: [abortedAttr, attrInt("ai.toolCall.count", state.toolCallCount)]
3723
+ attributes: [
3724
+ abortedAttr,
3725
+ attrInt("ai.toolCall.count", state.toolCallCount),
3726
+ // An aborted detached child IS a cancelled sub-agent, and a cancelled
3727
+ // run is span-for-span identical to a finished one — spans close,
3728
+ // nothing errors, there is just no answer. So it is the one outcome
3729
+ // that has to be stated, and the child states it on itself.
3730
+ ...abortIsTheRunsOutcome ? cancelledSpanAttrs() : [],
3731
+ ...abortIsTheRunsOutcome ? this.abortOutcomeAttrs(state, SUBAGENT_CANCELLED_OUTPUT, "stop") : []
3732
+ ]
2717
3733
  });
2718
3734
  }
2719
3735
  if (state.subagentToolCallSpan) {
@@ -2721,7 +3737,17 @@ var RaindropTelemetryIntegration = class {
2721
3737
  attributes: [abortedAttr]
2722
3738
  });
2723
3739
  }
2724
- this.finalizeGenerateEvent(state, state.accumulatedText || void 0, void 0);
3740
+ this.finalizeGenerateEvent(
3741
+ state,
3742
+ state.accumulatedText || void 0,
3743
+ void 0,
3744
+ false,
3745
+ abortIsTheRunsOutcome ? {
3746
+ fallbackOutput: SUBAGENT_CANCELLED_OUTPUT,
3747
+ properties: { [HANDOFF_TERMINAL_PROPERTY_KEY]: HANDOFF_TERMINAL_CANCELLED }
3748
+ } : void 0
3749
+ );
3750
+ if (abortIsTheRunsOutcome) this.subagents.noteSettled(state.eventId);
2725
3751
  this.cleanup(callId);
2726
3752
  };
2727
3753
  // ── executeTool ─────────────────────────────────────────────────────────
@@ -2749,6 +3775,97 @@ var RaindropTelemetryIntegration = class {
2749
3775
  this.subagentWrapping = opts.subagentWrapping !== false;
2750
3776
  this.debug = opts.debug === true;
2751
3777
  this.defaultContext = opts.context;
3778
+ this.subagents = new SubagentApi({
3779
+ traceShipper: this.traceShipper,
3780
+ eventShipper: this.eventShipper,
3781
+ sendTraces: this.sendTraces,
3782
+ sendEvents: this.sendEvents,
3783
+ debug: this.debug,
3784
+ defaultContext: this.defaultContext,
3785
+ settledEventIds: opts.settledEventIds,
3786
+ spanSource: {
3787
+ activeDispatch: () => this.activeDispatch(),
3788
+ openSpansForEvent: (eventId) => this.openSpansForEvent(eventId)
3789
+ }
3790
+ });
3791
+ }
3792
+ /**
3793
+ * Record a detached sub-agent on the turn that is live right now.
3794
+ *
3795
+ * Returns the dispatch — the child's event id and the headers to send with
3796
+ * the job. Dispatching is still yours to do; this only states that you are
3797
+ * about to, so a reader can follow the link before the child exists.
3798
+ */
3799
+ subagent(options = {}) {
3800
+ return this.subagents.subagent(options);
3801
+ }
3802
+ // ── detached sub-agent hand-offs ─────────────────────────────────────────
3803
+ /**
3804
+ * The turn a `subagent()` dispatch is happening inside.
3805
+ *
3806
+ * Resolved from the parent-tool context first, which is the only way to reach
3807
+ * the EXACT span that dispatched: the tool call the model itself issued, still
3808
+ * open while its `execute` runs. Falling back to the event id lets a launch
3809
+ * from elsewhere in the turn (a queue producer called mid-generation) still
3810
+ * find the turn's spans, which is where the tool-events opt-in has to land.
3811
+ */
3812
+ activeDispatch() {
3813
+ var _a;
3814
+ const parentTool = getCurrentParentToolContext();
3815
+ let state = parentTool ? this.getState(parentTool.parentCallId) : void 0;
3816
+ const inheritedEventId = (_a = getContextManager().getParentSpanIds()) == null ? void 0 : _a.eventId;
3817
+ if (!state && inheritedEventId) {
3818
+ for (const candidate of this.callStates.values()) {
3819
+ if (candidate.eventId === inheritedEventId) {
3820
+ state = candidate;
3821
+ break;
3822
+ }
3823
+ }
3824
+ }
3825
+ if (!state) return void 0;
3826
+ return {
3827
+ eventId: state.eventId,
3828
+ userId: state.identity.userId,
3829
+ convoId: state.identity.convoId,
3830
+ dispatchSpan: parentTool ? state.toolSpans.get(parentTool.toolCallId) : void 0,
3831
+ turnSpans: [state.rootSpan, state.stepSpan].filter(
3832
+ (span) => span !== void 0
3833
+ ),
3834
+ recordInputs: state.recordInputs
3835
+ };
3836
+ }
3837
+ /** Spans still open for an event, so a late outcome can still be stated. */
3838
+ openSpansForEvent(eventId) {
3839
+ const spans = [];
3840
+ for (const state of this.callStates.values()) {
3841
+ if (state.eventId !== eventId) continue;
3842
+ if (state.rootSpan) spans.push(state.rootSpan);
3843
+ if (state.stepSpan) spans.push(state.stepSpan);
3844
+ }
3845
+ return spans;
3846
+ }
3847
+ /**
3848
+ * End-attributes that keep a detached child legible when it ends without an
3849
+ * answer — an error, an abort, a cancellation.
3850
+ *
3851
+ * An event is created from an LLM span only when it has a finish reason and
3852
+ * non-empty output, so a child that dies silently produces NO event, and the
3853
+ * caller's hand-off pill stays on `queued` forever — indistinguishable from a
3854
+ * job that never started. Reporting the abort reason as the run's output is
3855
+ * what makes the outcome visible. Only detached children get this: an inline
3856
+ * generation's failure is already legible from its caller's own event.
3857
+ *
3858
+ * And only the run's OUTERMOST generation gets it (see `nestedInEvent`): an
3859
+ * inner sub-call ending badly is not the run ending, and since an event is
3860
+ * created from an LLM span with output, `ai.response.text` here would state
3861
+ * the run's outcome through the span while the turn is still working.
3862
+ */
3863
+ abortOutcomeAttrs(state, reason, finishReason) {
3864
+ if (!state.handoff || state.nestedInEvent) return [];
3865
+ return [
3866
+ attrString("ai.response.text", capText2(state.accumulatedText || reason)),
3867
+ attrString("ai.response.finishReason", finishReason)
3868
+ ];
2752
3869
  }
2753
3870
  // ── helpers ──────────────────────────────────────────────────────────────
2754
3871
  getState(callId) {
@@ -2867,7 +3984,8 @@ var RaindropTelemetryIntegration = class {
2867
3984
  attrString("ai.telemetry.functionId", state.functionId),
2868
3985
  attrString("ai.toolCall.name", toolCall.toolName),
2869
3986
  attrString("ai.toolCall.id", toolCall.toolCallId),
2870
- ...inputAttrs
3987
+ ...inputAttrs,
3988
+ ...handoffSpanAttrs(state.handoff)
2871
3989
  ]
2872
3990
  });
2873
3991
  state.toolSpans.set(toolCall.toolCallId, toolSpan);
@@ -2946,7 +4064,8 @@ var RaindropTelemetryIntegration = class {
2946
4064
  attrString("ai.toolCall.name", call.toolName),
2947
4065
  attrString("ai.toolCall.id", call.toolCallId),
2948
4066
  attrString("ai.toolCall.providerExecuted", "true"),
2949
- ...inputAttrs
4067
+ ...inputAttrs,
4068
+ ...handoffSpanAttrs(state.handoff)
2950
4069
  ]
2951
4070
  });
2952
4071
  state.toolCallCount += 1;
@@ -2997,12 +4116,13 @@ var RaindropTelemetryIntegration = class {
2997
4116
  }
2998
4117
  const usage = (_d = event.totalUsage) != null ? _d : event.usage;
2999
4118
  if (usage) {
4119
+ const usageMetrics = extractUsageMetricsFromUsage(usage);
3000
4120
  outputAttrs.push(
3001
- attrInt("ai.usage.inputTokens", usage.inputTokens),
3002
- attrInt("ai.usage.outputTokens", usage.outputTokens),
3003
- attrInt("ai.usage.totalTokens", usage.totalTokens),
3004
- attrInt("ai.usage.reasoningTokens", usage.reasoningTokens),
3005
- attrInt("ai.usage.cachedInputTokens", usage.cachedInputTokens)
4121
+ attrInt("ai.usage.inputTokens", usageMetrics.inputTokens),
4122
+ attrInt("ai.usage.outputTokens", usageMetrics.outputTokens),
4123
+ attrInt("ai.usage.totalTokens", usageMetrics.totalTokens),
4124
+ attrInt("ai.usage.reasoningTokens", usageMetrics.reasoningTokens),
4125
+ attrInt("ai.usage.cachedInputTokens", usageMetrics.cachedInputTokens)
3006
4126
  );
3007
4127
  }
3008
4128
  outputAttrs.push(attrInt("ai.toolCall.count", state.toolCallCount));
@@ -3026,19 +4146,22 @@ var RaindropTelemetryIntegration = class {
3026
4146
  * is finalized by that resume — otherwise each execution would finalize the
3027
4147
  * same event ID into a separate canonical row.
3028
4148
  */
3029
- finalizeGenerateEvent(state, output, model, keepPending = false) {
4149
+ finalizeGenerateEvent(state, output, model, keepPending = false, detachedOutcome) {
3030
4150
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3031
4151
  const suppressSubagentEvent = state.subagentName !== void 0 && state.subagentToolCallSpan !== void 0;
3032
4152
  if (!this.sendEvents || suppressSubagentEvent) return;
4153
+ if (this.subagents.wasSettledExplicitly(state.eventId)) return;
3033
4154
  const callMeta = this.extractRaindropMetadata(state.metadata);
3034
4155
  const userId = (_b = callMeta.userId) != null ? _b : (_a = this.defaultContext) == null ? void 0 : _a.userId;
3035
4156
  if (!userId) return;
3036
4157
  const eventName = (_e = (_d = callMeta.eventName) != null ? _d : (_c = this.defaultContext) == null ? void 0 : _c.eventName) != null ? _e : state.operationId;
3037
4158
  const cappedOutput = capText2(output);
4159
+ const reportedOutput = state.handoff && detachedOutcome && !(cappedOutput == null ? void 0 : cappedOutput.trim()) ? detachedOutcome.fallbackOutput : cappedOutput;
3038
4160
  const input = capText2(state.inputText);
3039
4161
  const properties = {
3040
4162
  ...(_f = this.defaultContext) == null ? void 0 : _f.properties,
3041
- ...callMeta.properties
4163
+ ...callMeta.properties,
4164
+ ...state.handoff ? detachedOutcome == null ? void 0 : detachedOutcome.properties : void 0
3042
4165
  };
3043
4166
  const featureFlags = {
3044
4167
  ...((_g = this.defaultContext) == null ? void 0 : _g.featureFlags) ? normalizeFeatureFlags(this.defaultContext.featureFlags) : {},
@@ -3050,7 +4173,7 @@ var RaindropTelemetryIntegration = class {
3050
4173
  userId,
3051
4174
  convoId,
3052
4175
  input,
3053
- output: cappedOutput,
4176
+ output: reportedOutput,
3054
4177
  model,
3055
4178
  properties: Object.keys(properties).length > 0 ? properties : void 0,
3056
4179
  featureFlags: Object.keys(featureFlags).length > 0 ? featureFlags : void 0,
@@ -3442,14 +4565,6 @@ function runWithParentSpanContextSync(ctx, fn) {
3442
4565
  function isAsyncIterable(value) {
3443
4566
  return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
3444
4567
  }
3445
- function firstFiniteNumber(...values) {
3446
- for (const value of values) {
3447
- if (typeof value === "number" && Number.isFinite(value)) {
3448
- return value;
3449
- }
3450
- }
3451
- return void 0;
3452
- }
3453
4568
  function resolveUsageRecord(result) {
3454
4569
  if (!isRecord(result)) return void 0;
3455
4570
  let usage;
@@ -3465,50 +4580,7 @@ function resolveUsageRecord(result) {
3465
4580
  return isRecord(usage) ? usage : void 0;
3466
4581
  }
3467
4582
  function extractUsageMetrics(result) {
3468
- const usage = resolveUsageRecord(result);
3469
- if (!usage) return {};
3470
- const inputTokenValue = usage["inputTokens"];
3471
- const outputTokenValue = usage["outputTokens"];
3472
- const inputTokens = firstFiniteNumber(
3473
- isRecord(inputTokenValue) ? inputTokenValue["total"] : void 0,
3474
- inputTokenValue,
3475
- usage["promptTokens"],
3476
- usage["prompt_tokens"]
3477
- );
3478
- const outputTokens = firstFiniteNumber(
3479
- isRecord(outputTokenValue) ? outputTokenValue["total"] : void 0,
3480
- outputTokenValue,
3481
- usage["completionTokens"],
3482
- usage["completion_tokens"]
3483
- );
3484
- const totalTokens = firstFiniteNumber(
3485
- usage["totalTokens"],
3486
- usage["tokens"],
3487
- usage["total_tokens"],
3488
- inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0
3489
- );
3490
- const reasoningTokens = firstFiniteNumber(
3491
- isRecord(outputTokenValue) ? outputTokenValue["reasoning"] : void 0,
3492
- usage["reasoningTokens"],
3493
- usage["completionReasoningTokens"],
3494
- usage["completion_reasoning_tokens"],
3495
- usage["reasoning_tokens"],
3496
- usage["thinkingTokens"],
3497
- usage["thinking_tokens"]
3498
- );
3499
- const cachedInputTokens = firstFiniteNumber(
3500
- isRecord(inputTokenValue) ? inputTokenValue["cacheRead"] : void 0,
3501
- usage["cachedInputTokens"],
3502
- usage["promptCachedTokens"],
3503
- usage["prompt_cached_tokens"]
3504
- );
3505
- return {
3506
- inputTokens,
3507
- outputTokens,
3508
- totalTokens,
3509
- reasoningTokens,
3510
- cachedInputTokens
3511
- };
4583
+ return extractUsageMetricsFromUsage(resolveUsageRecord(result));
3512
4584
  }
3513
4585
  function isObjectOperation(operation) {
3514
4586
  return operation === "generateObject" || operation === "streamObject";
@@ -4000,6 +5072,7 @@ function wrapAISDK(aiSDK, deps) {
4000
5072
  sendTraces: ((_a = deps.options.send) == null ? void 0 : _a.traces) !== false,
4001
5073
  sendEvents: ((_b = deps.options.send) == null ? void 0 : _b.events) !== false,
4002
5074
  debug,
5075
+ settledEventIds: deps.settledEventIds,
4003
5076
  context: {
4004
5077
  userId: wrapTimeCtx.userId,
4005
5078
  eventId: wrapTimeCtx.eventId,
@@ -4899,8 +5972,13 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4899
5972
  ended = true;
4900
5973
  const msToFirstChunk = firstChunkMs !== void 0 ? firstChunkMs - startMs : void 0;
4901
5974
  const msToFinish = finishMs !== void 0 ? finishMs - startMs : void 0;
4902
- const inputTokens = extractNestedTokens(usage, "inputTokens");
4903
- const outputTokens = extractNestedTokens(usage, "outputTokens");
5975
+ const usageMetrics = extractUsageMetricsFromUsage(
5976
+ usage,
5977
+ modelInfo.provider,
5978
+ providerMetadata
5979
+ );
5980
+ const finalResponseModelId = responseModelId != null ? responseModelId : modelInfo.modelId;
5981
+ const { inputTokens, outputTokens } = usageMetrics;
4904
5982
  const avgOutTokensPerSecond = msToFinish && outputTokens !== void 0 && msToFinish > 0 ? 1e3 * outputTokens / msToFinish : void 0;
4905
5983
  ctx.traceShipper.endSpan(span, {
4906
5984
  attributes: [
@@ -4912,7 +5990,7 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4912
5990
  safeJsonWithUint8(toolCallsLocal.length ? toolCallsLocal : void 0)
4913
5991
  ),
4914
5992
  attrString("ai.response.id", responseId),
4915
- attrString("ai.response.model", responseModelId),
5993
+ attrString("ai.response.model", finalResponseModelId),
4916
5994
  attrString("ai.response.timestamp", responseTimestampIso),
4917
5995
  attrString(
4918
5996
  "ai.response.providerMetadata",
@@ -4923,9 +6001,8 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4923
6001
  attrInt("ai.usage.outputTokens", outputTokens),
4924
6002
  ...finishReason ? [attrStringArray("gen_ai.response.finish_reasons", [finishReason])] : [],
4925
6003
  attrString("gen_ai.response.id", responseId),
4926
- attrString("gen_ai.response.model", responseModelId),
4927
- attrInt("gen_ai.usage.input_tokens", inputTokens),
4928
- attrInt("gen_ai.usage.output_tokens", outputTokens),
6004
+ attrString(MODEL_USAGE_ATTRIBUTES.responseModel, finalResponseModelId),
6005
+ ...buildModelUsageAttributes(usageMetrics),
4929
6006
  ...msToFirstChunk !== void 0 ? [attrInt("ai.stream.msToFirstChunk", msToFirstChunk)] : [],
4930
6007
  ...msToFinish !== void 0 ? [attrInt("ai.stream.msToFinish", msToFinish)] : [],
4931
6008
  ...avgOutTokensPerSecond !== void 0 ? [attrDouble("ai.stream.avgOutputTokensPerSecond", avgOutTokensPerSecond)] : [],
@@ -5031,15 +6108,14 @@ function startDoGenerateSpan(operationId, options, modelInfo, parent, ctx) {
5031
6108
  attrString("operation.name", operationName),
5032
6109
  attrString("resource.name", resourceName),
5033
6110
  attrString("ai.telemetry.functionId", (_b = ctx.telemetry) == null ? void 0 : _b.functionId),
5034
- attrString("ai.model.provider", modelInfo.provider),
6111
+ ...attrsFromModelProvider(modelInfo.provider),
5035
6112
  attrString("ai.model.id", modelInfo.modelId),
5036
6113
  ...((_c = ctx.telemetry) == null ? void 0 : _c.recordInputs) === false ? [] : [
5037
6114
  attrString("ai.prompt.messages", promptJson),
5038
6115
  attrStringArray("ai.prompt.tools", toolsJson),
5039
6116
  attrString("ai.prompt.toolChoice", toolChoiceJson)
5040
6117
  ],
5041
- attrString("gen_ai.system", modelInfo.provider),
5042
- attrString("gen_ai.request.model", modelInfo.modelId),
6118
+ attrString(MODEL_USAGE_ATTRIBUTES.requestModel, modelInfo.modelId),
5043
6119
  ...attrsFromGenAiRequest(options),
5044
6120
  attrProviderOptions(options)
5045
6121
  ]
@@ -5072,8 +6148,12 @@ function endDoGenerateSpan(span, result, modelInfo, ctx) {
5072
6148
  } else {
5073
6149
  responseTimestampIso = (/* @__PURE__ */ new Date()).toISOString();
5074
6150
  }
5075
- const inputTokens = extractNestedTokens(usage, "inputTokens");
5076
- const outputTokens = extractNestedTokens(usage, "outputTokens");
6151
+ const usageMetrics = extractUsageMetricsFromUsage(
6152
+ usage,
6153
+ modelInfo.provider,
6154
+ providerMetadata
6155
+ );
6156
+ const { inputTokens, outputTokens } = usageMetrics;
5077
6157
  ctx.traceShipper.endSpan(span, {
5078
6158
  attributes: [
5079
6159
  ...((_a = ctx.telemetry) == null ? void 0 : _a.recordOutputs) === false ? [] : [
@@ -5092,9 +6172,8 @@ function endDoGenerateSpan(span, result, modelInfo, ctx) {
5092
6172
  attrInt("ai.usage.completionTokens", outputTokens),
5093
6173
  ...finishReason ? [attrStringArray("gen_ai.response.finish_reasons", [finishReason])] : [],
5094
6174
  attrString("gen_ai.response.id", responseId),
5095
- attrString("gen_ai.response.model", responseModelId),
5096
- attrInt("gen_ai.usage.input_tokens", inputTokens),
5097
- attrInt("gen_ai.usage.output_tokens", outputTokens)
6175
+ attrString(MODEL_USAGE_ATTRIBUTES.responseModel, responseModelId),
6176
+ ...buildModelUsageAttributes(usageMetrics)
5098
6177
  ]
5099
6178
  });
5100
6179
  }
@@ -5115,15 +6194,14 @@ function startDoStreamSpan(operationId, options, modelInfo, parent, ctx) {
5115
6194
  attrString("operation.name", operationName),
5116
6195
  attrString("resource.name", resourceName),
5117
6196
  attrString("ai.telemetry.functionId", (_b = ctx.telemetry) == null ? void 0 : _b.functionId),
5118
- attrString("ai.model.provider", modelInfo.provider),
6197
+ ...attrsFromModelProvider(modelInfo.provider),
5119
6198
  attrString("ai.model.id", modelInfo.modelId),
5120
6199
  ...((_c = ctx.telemetry) == null ? void 0 : _c.recordInputs) === false ? [] : [
5121
6200
  attrString("ai.prompt.messages", promptJson),
5122
6201
  attrStringArray("ai.prompt.tools", toolsJson),
5123
6202
  attrString("ai.prompt.toolChoice", toolChoiceJson)
5124
6203
  ],
5125
- attrString("gen_ai.system", modelInfo.provider),
5126
- attrString("gen_ai.request.model", modelInfo.modelId),
6204
+ attrString(MODEL_USAGE_ATTRIBUTES.requestModel, modelInfo.modelId),
5127
6205
  ...attrsFromGenAiRequest(options),
5128
6206
  attrProviderOptions(options)
5129
6207
  ]
@@ -5188,18 +6266,11 @@ function mergeAttachments(...groups) {
5188
6266
  }
5189
6267
  return merged.length ? merged : void 0;
5190
6268
  }
5191
- function extractNestedTokens(usage, key) {
5192
- if (!isRecord(usage)) return void 0;
5193
- const val = usage[key];
5194
- if (typeof val === "number") return val;
5195
- if (isRecord(val) && typeof val["total"] === "number") return val["total"];
5196
- return void 0;
5197
- }
5198
6269
 
5199
6270
  // package.json
5200
6271
  var package_default = {
5201
6272
  name: "@raindrop-ai/ai-sdk",
5202
- version: "0.2.0"};
6273
+ version: "0.3.0"};
5203
6274
 
5204
6275
  // src/internal/version.ts
5205
6276
  var libraryName = package_default.name;
@@ -5246,6 +6317,8 @@ function eventMetadata(options) {
5246
6317
  if (options.properties) result["raindrop.properties"] = JSON.stringify(options.properties);
5247
6318
  if (options.featureFlags)
5248
6319
  result["raindrop.featureFlags"] = JSON.stringify(normalizeFeatureFlags(options.featureFlags));
6320
+ if (options.subagent) Object.assign(result, detachedChildMetadata(options.subagent));
6321
+ if (options.toolEvents) result[TOOL_EVENTS_SUFFIX] = options.toolEvents;
5249
6322
  return result;
5250
6323
  }
5251
6324
  function deriveChatTurnMessageId(request) {
@@ -5328,12 +6401,22 @@ function createRaindropAISDK(opts) {
5328
6401
  transformSpan: (_j = opts.traces) == null ? void 0 : _j.transformSpan,
5329
6402
  disableDefaultRedaction: (_k = opts.traces) == null ? void 0 : _k.disableDefaultRedaction
5330
6403
  });
6404
+ const settledEventIds = /* @__PURE__ */ new Set();
6405
+ const subagentApi = new SubagentApi({
6406
+ traceShipper,
6407
+ eventShipper,
6408
+ sendTraces: tracesEnabled,
6409
+ sendEvents: eventsEnabled,
6410
+ debug: envDebug,
6411
+ settledEventIds
6412
+ });
5331
6413
  return {
5332
6414
  wrap(aiSDK, options) {
5333
6415
  return wrapAISDK(aiSDK, {
5334
6416
  options: options != null ? options : {},
5335
6417
  eventShipper,
5336
- traceShipper
6418
+ traceShipper,
6419
+ settledEventIds
5337
6420
  });
5338
6421
  },
5339
6422
  createSelfDiagnosticsTool(options = {}) {
@@ -5370,9 +6453,14 @@ function createRaindropAISDK(opts) {
5370
6453
  sendEvents: eventsEnabled,
5371
6454
  subagentWrapping,
5372
6455
  debug: envDebug,
6456
+ settledEventIds,
5373
6457
  context
5374
6458
  });
5375
6459
  },
6460
+ subagents: subagentApi,
6461
+ subagent(options = {}) {
6462
+ return subagentApi.subagent(options);
6463
+ },
5376
6464
  events: {
5377
6465
  async patch(eventId, patch) {
5378
6466
  await eventShipper.patch(eventId, patch);
@@ -5492,4 +6580,4 @@ function raindrop(options = {}) {
5492
6580
  return client.createTelemetryIntegration({ context, subagentWrapping });
5493
6581
  }
5494
6582
 
5495
- 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 };
6583
+ 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 };