@uselemma/tracing 7.6.0 → 7.7.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.
package/dist/vercel-ai.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.vercelAI = vercelAI;
4
4
  const client_1 = require("./client");
5
5
  const tool_result_1 = require("./tool-result");
6
+ const CONCURRENT_REUSE_ERROR = "vercelAI() is already tracing a run. Create a new vercelAI() integration per concurrent AI SDK operation.";
6
7
  function addMs(startedAt, durationMs) {
7
8
  return typeof durationMs === "number"
8
9
  ? new Date(startedAt.getTime() + durationMs)
@@ -33,6 +34,18 @@ function stringifyContent(content) {
33
34
  return text;
34
35
  return JSON.stringify(content);
35
36
  }
37
+ function structuredAssistantOutput(text, content) {
38
+ if (typeof text === "string")
39
+ return text;
40
+ if (!content)
41
+ return undefined;
42
+ const hasNonText = content.some((part) => part &&
43
+ typeof part === "object" &&
44
+ part.type !== "text");
45
+ if (hasNonText)
46
+ return content;
47
+ return stringifyContent(content);
48
+ }
36
49
  function errorToolFields(error, recordOutputs) {
37
50
  return recordOutputs
38
51
  ? { error, status: "ERROR" }
@@ -70,25 +83,89 @@ function resolveDurationMs(startedAt, endedAt, reportedMs) {
70
83
  // step cannot outlast a too-short reported model-only duration.
71
84
  return Math.max(reportedMs, wallMs);
72
85
  }
73
- function v6Input(event) {
74
- if ("messages" in event && event.messages)
86
+ function messageContent(message) {
87
+ if (!message || typeof message !== "object")
88
+ return message;
89
+ const record = message;
90
+ if ("content" in record)
91
+ return record.content;
92
+ return message;
93
+ }
94
+ function lookupString(sources, keys) {
95
+ for (const source of sources) {
96
+ if (!source)
97
+ continue;
98
+ for (const key of keys) {
99
+ const value = source[key];
100
+ if (typeof value === "string" && value)
101
+ return value;
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+ /** Normalize v6 system/prompt/messages into a chat message list when possible. */
107
+ function v6NormalizedMessages(event) {
108
+ if (Array.isArray(event.messages))
75
109
  return event.messages;
76
- return "prompt" in event ? event.prompt : undefined;
110
+ const messages = [];
111
+ if ("system" in event && typeof event.system === "string" && event.system) {
112
+ messages.push({ role: "system", content: event.system });
113
+ }
114
+ if ("prompt" in event && event.prompt !== undefined) {
115
+ if (Array.isArray(event.prompt)) {
116
+ return messages.length > 0
117
+ ? [...messages, ...event.prompt]
118
+ : event.prompt;
119
+ }
120
+ if (typeof event.prompt === "string") {
121
+ messages.push({ role: "user", content: event.prompt });
122
+ }
123
+ }
124
+ return messages.length > 0 ? messages : undefined;
125
+ }
126
+ function v6Input(event) {
127
+ const normalized = v6NormalizedMessages(event);
128
+ if (normalized)
129
+ return normalized;
130
+ if ("prompt" in event)
131
+ return event.prompt;
132
+ return undefined;
133
+ }
134
+ /**
135
+ * Prefer the current user turn for the Lemma root input.
136
+ * Accepts a bare string, message array, or prompt/system start payload.
137
+ */
138
+ function rootTraceInput(input) {
139
+ if (typeof input === "string")
140
+ return input;
141
+ if (!Array.isArray(input) || input.length === 0)
142
+ return input;
143
+ for (let i = input.length - 1; i >= 0; i--) {
144
+ const message = input[i];
145
+ if (!message || typeof message !== "object")
146
+ continue;
147
+ const role = message.role;
148
+ if (role === "user")
149
+ return messageContent(message);
150
+ }
151
+ return messageContent(input[input.length - 1]);
77
152
  }
78
- function traceInput(event) {
153
+ function eventTraceInput(event) {
79
154
  if (!event)
80
155
  return undefined;
156
+ if ("messages" in event || "prompt" in event || "system" in event) {
157
+ if (!("callId" in event) || "model" in event) {
158
+ return v6Input(event);
159
+ }
160
+ }
81
161
  if ("messages" in event && event.messages)
82
162
  return event.messages;
83
163
  if ("prompt" in event)
84
164
  return event.prompt;
85
165
  return undefined;
86
166
  }
87
- function traceMetadata(options, event) {
88
- return {
89
- ...options.metadata,
90
- ...(event && "metadata" in event ? (event.metadata ?? {}) : {}),
91
- };
167
+ function eventMetadata(event) {
168
+ return event && "metadata" in event ? (event.metadata ?? undefined) : undefined;
92
169
  }
93
170
  function traceName(options, event) {
94
171
  if (options.agentName)
@@ -98,31 +175,61 @@ function traceName(options, event) {
98
175
  : undefined;
99
176
  return functionId || "vercel-ai-agent";
100
177
  }
101
- function v6InputMessages(event) {
102
- const input = v6Input(event);
103
- return Array.isArray(input) ? input : undefined;
104
- }
105
178
  function v6Output(event) {
106
- if (typeof event.text === "string")
107
- return event.text;
108
- if (event.content)
109
- return stringifyContent(event.content);
110
- return undefined;
179
+ return structuredAssistantOutput(event.text, event.content);
180
+ }
181
+ function endOutput(event) {
182
+ return structuredAssistantOutput(event.text, event.content);
183
+ }
184
+ function errorMessage(error) {
185
+ if (typeof error === "string" && error)
186
+ return error;
187
+ if (error instanceof Error && error.message)
188
+ return error.message;
189
+ try {
190
+ return JSON.stringify(error);
191
+ }
192
+ catch {
193
+ return String(error);
194
+ }
111
195
  }
112
196
  function vercelAI(options = {}) {
113
197
  let lemma = options.lemma;
114
- const modelCalls = new Map();
115
- const v7Steps = new Map();
116
- const v6Steps = new Map();
117
- const v6Starts = [];
118
- const generationSpanIdsByCallId = new Map();
119
- const generationSpanIdsByToolCallId = new Map();
120
- const generationHandlesById = new Map();
121
- const toolExecutions = new Map();
198
+ const pending = new Set();
199
+ let phase = "idle";
200
+ let modelCalls = new Map();
201
+ let v7Steps = new Map();
202
+ let v6Steps = new Map();
203
+ let v6Starts = [];
204
+ let generationSpanIdsByCallId = new Map();
205
+ let generationSpanIdsByToolCallId = new Map();
206
+ let generationHandlesById = new Map();
207
+ let toolExecutions = new Map();
208
+ let latestV6GenerationId;
122
209
  let managedTrace;
123
210
  let recordedV6Step = false;
124
- let endedExplicitTrace = false;
125
- let endedManagedTrace = false;
211
+ let sawV7StepZero = false;
212
+ let runStartedAt;
213
+ let runError;
214
+ let endingTrace;
215
+ function resetRunState() {
216
+ phase = "idle";
217
+ modelCalls = new Map();
218
+ v7Steps = new Map();
219
+ v6Steps = new Map();
220
+ v6Starts = [];
221
+ generationSpanIdsByCallId = new Map();
222
+ generationSpanIdsByToolCallId = new Map();
223
+ generationHandlesById = new Map();
224
+ toolExecutions = new Map();
225
+ latestV6GenerationId = undefined;
226
+ managedTrace = undefined;
227
+ recordedV6Step = false;
228
+ sawV7StepZero = false;
229
+ runStartedAt = undefined;
230
+ runError = undefined;
231
+ endingTrace = undefined;
232
+ }
126
233
  function trackGeneration(handle) {
127
234
  generationHandlesById.set(handle.id, handle);
128
235
  }
@@ -140,55 +247,188 @@ function vercelAI(options = {}) {
140
247
  });
141
248
  return lemma;
142
249
  }
250
+ function resolveThreadId(metadata) {
251
+ const key = options.threadIdKey ?? "threadId";
252
+ return lookupString([metadata, options.metadata], [key]);
253
+ }
254
+ function resolveUserId(metadata) {
255
+ const key = options.userIdKey ?? "userId";
256
+ return lookupString([metadata, options.metadata], [key]);
257
+ }
258
+ function mergedMetadata(metadata) {
259
+ return {
260
+ ...options.metadata,
261
+ ...(metadata ?? {}),
262
+ };
263
+ }
264
+ function beginActivity() {
265
+ if (phase === "idle") {
266
+ phase = "active";
267
+ runStartedAt = new Date();
268
+ }
269
+ }
270
+ function beginNewRun() {
271
+ if (phase === "active" || phase === "ending") {
272
+ throw new Error(CONCURRENT_REUSE_ERROR);
273
+ }
274
+ resetRunState();
275
+ phase = "active";
276
+ runStartedAt = new Date();
277
+ }
278
+ function hasExplicitEndableTrace() {
279
+ const trace = options.trace;
280
+ return Boolean(trace && typeof trace.end === "function");
281
+ }
282
+ function applyIdentity(trace, metadata) {
283
+ const threadId = resolveThreadId(metadata);
284
+ const userId = resolveUserId(metadata);
285
+ if (threadId)
286
+ trace.threadId(threadId);
287
+ if (userId)
288
+ trace.userId(userId);
289
+ }
290
+ function rootErrorPayload(message) {
291
+ // Preserve ERROR status while redacting message bodies when outputs are off.
292
+ return options.recordOutputs === false ? "error" : message;
293
+ }
143
294
  function resolveTrace(event) {
144
- if (options.trace)
295
+ // Trailing AI SDK callbacks can arrive while terminal delivery is in flight.
296
+ // Attach them to the still-open trace instead of throwing.
297
+ if (phase === "ending") {
298
+ const trace = options.trace ?? managedTrace;
299
+ return trace
300
+ ? {
301
+ trace,
302
+ source: options.trace ? "explicit" : "managed",
303
+ }
304
+ : null;
305
+ }
306
+ beginActivity();
307
+ const metadata = eventMetadata(event);
308
+ if (options.trace) {
309
+ applyIdentity(options.trace, metadata);
145
310
  return { trace: options.trace, source: "explicit" };
311
+ }
146
312
  if (!managedTrace) {
313
+ const rawInput = options.recordInputs === false ? undefined : eventTraceInput(event);
147
314
  managedTrace = getLemma().trace({
148
315
  name: traceName(options, event),
149
- input: options.recordInputs === false ? undefined : traceInput(event),
150
- metadata: traceMetadata(options, event),
316
+ input: options.recordInputs === false
317
+ ? undefined
318
+ : rootTraceInput(rawInput),
319
+ metadata: mergedMetadata(metadata),
320
+ threadId: resolveThreadId(metadata),
321
+ userId: resolveUserId(metadata),
322
+ startedAt: runStartedAt,
151
323
  });
152
324
  }
325
+ else {
326
+ applyIdentity(managedTrace, metadata);
327
+ if (options.recordInputs !== false && event) {
328
+ const rawInput = eventTraceInput(event);
329
+ if (rawInput !== undefined) {
330
+ // Keep root input as the latest known current-turn value.
331
+ managedTrace.input(rootTraceInput(rawInput));
332
+ }
333
+ }
334
+ }
153
335
  return { trace: managedTrace, source: "managed" };
154
336
  }
155
- function endOutput(event) {
156
- if (typeof event.text === "string")
157
- return event.text;
158
- if (event.content)
159
- return stringifyContent(event.content);
160
- return undefined;
337
+ function trackPending(promise) {
338
+ pending.add(promise);
339
+ void promise.finally(() => pending.delete(promise));
340
+ return promise;
341
+ }
342
+ function ensureManagedTrace(event) {
343
+ if (options.trace || managedTrace)
344
+ return;
345
+ beginActivity();
346
+ managedTrace = getLemma().trace({
347
+ name: traceName(options, event),
348
+ metadata: mergedMetadata(eventMetadata(event)),
349
+ threadId: resolveThreadId(eventMetadata(event)),
350
+ userId: resolveUserId(eventMetadata(event)),
351
+ startedAt: runStartedAt,
352
+ });
161
353
  }
162
354
  async function endOwnedTrace(event) {
355
+ if (phase === "idle")
356
+ return;
357
+ if (phase === "ending")
358
+ return;
359
+ phase = "ending";
163
360
  const trace = options.trace;
164
361
  let ownedTrace;
362
+ let explicit = false;
165
363
  if (trace && typeof trace.end === "function") {
166
- ownedTrace = { trace: { end: trace.end.bind(trace) }, explicit: true };
167
- }
168
- else if (managedTrace) {
169
- ownedTrace = { trace: managedTrace, explicit: false };
170
- }
171
- if (!ownedTrace)
172
- return;
173
- if (ownedTrace.explicit) {
174
- if (endedExplicitTrace)
175
- return;
176
- endedExplicitTrace = true;
364
+ explicit = true;
365
+ ownedTrace = {
366
+ end: trace.end.bind(trace),
367
+ fail: trace.fail.bind(trace),
368
+ output: trace.output.bind(trace),
369
+ };
177
370
  }
178
371
  else {
179
- if (endedManagedTrace)
180
- return;
181
- endedManagedTrace = true;
372
+ ensureManagedTrace(event);
373
+ if (managedTrace) {
374
+ ownedTrace = managedTrace;
375
+ }
182
376
  }
183
- const output = endOutput(event);
184
- if (options.recordOutputs === false || output === undefined) {
185
- await ownedTrace.trace.end();
186
- return;
377
+ const identityTrace = (options.trace ?? managedTrace);
378
+ if (identityTrace) {
379
+ applyIdentity(identityTrace, eventMetadata(event));
380
+ }
381
+ const eventError = "error" in event && event.error != null
382
+ ? errorMessage(event.error)
383
+ : undefined;
384
+ if (eventError && !runError) {
385
+ runError = eventError;
187
386
  }
188
- await ownedTrace.trace.end({ output });
387
+ const endedAt = new Date();
388
+ const startedAt = runStartedAt ?? endedAt;
389
+ const durationMs = Math.max(0, endedAt.getTime() - startedAt.getTime());
390
+ const successOutput = endOutput(event);
391
+ endingTrace = ownedTrace;
392
+ const endPromise = (async () => {
393
+ try {
394
+ if (!ownedTrace)
395
+ return;
396
+ // Yield so a racing fail()/trailing callback in this turn can settle.
397
+ await Promise.resolve();
398
+ const terminalError = runError;
399
+ // Explicit handles already own startedAt from construction; only pass
400
+ // durationMs for managed traces we started with runStartedAt.
401
+ const timing = explicit
402
+ ? { endedAt }
403
+ : { durationMs, endedAt };
404
+ if (terminalError) {
405
+ ownedTrace.fail(rootErrorPayload(terminalError));
406
+ ownedTrace.output(undefined);
407
+ await ownedTrace.end(timing);
408
+ return;
409
+ }
410
+ if (options.recordOutputs === false || successOutput === undefined) {
411
+ await ownedTrace.end(timing);
412
+ }
413
+ else {
414
+ await ownedTrace.end({
415
+ output: successOutput,
416
+ ...timing,
417
+ });
418
+ }
419
+ }
420
+ finally {
421
+ // Fully reset so the same integration object can be reused sequentially.
422
+ resetRunState();
423
+ }
424
+ })();
425
+ await trackPending(endPromise);
189
426
  }
190
427
  function recordV6Generation(event, stored) {
191
- const { trace } = resolveTrace(stored?.event);
428
+ const resolved = resolveTrace(stored?.event);
429
+ if (!resolved)
430
+ return;
431
+ const { trace } = resolved;
192
432
  const startedAt = stored?.startedAt ?? new Date();
193
433
  const endedAt = new Date();
194
434
  const durationMs = resolveDurationMs(startedAt, endedAt, undefined);
@@ -197,12 +437,17 @@ function vercelAI(options = {}) {
197
437
  ? options.generationName(event)
198
438
  : (options.generationName ?? "vercel-ai-generation");
199
439
  const output = v6Output(event);
440
+ const generationError = "error" in event && event.error != null
441
+ ? errorMessage(event.error)
442
+ : undefined;
200
443
  const generation = {
201
444
  name,
202
445
  input: options.recordInputs === false
203
446
  ? undefined
204
447
  : stored?.event && v6Input(stored.event),
205
- output: options.recordOutputs === false ? undefined : output,
448
+ output: options.recordOutputs === false || generationError
449
+ ? undefined
450
+ : output,
206
451
  metadata: options.metadata,
207
452
  model: event.model.modelId,
208
453
  startedAt,
@@ -211,15 +456,19 @@ function vercelAI(options = {}) {
211
456
  llmProvider: event.model.provider,
212
457
  llmInputMessages: options.recordInputs === false || !stored?.event
213
458
  ? undefined
214
- : v6InputMessages(stored.event),
215
- llmOutputMessages: options.recordOutputs === false || output === undefined
459
+ : v6NormalizedMessages(stored.event),
460
+ llmOutputMessages: options.recordOutputs === false ||
461
+ output === undefined ||
462
+ generationError
216
463
  ? undefined
217
464
  : [{ role: "assistant", content: output }],
218
465
  llmTools: stored?.event.tools,
466
+ status: generationError ? "ERROR" : undefined,
467
+ error: options.recordOutputs === false ? undefined : generationError,
219
468
  };
220
469
  if (stored?.handle) {
221
470
  stored.handle.end(generation);
222
- generationSpanIdsByCallId.set("v6-latest", stored.handle.id);
471
+ latestV6GenerationId = stored.handle.id;
223
472
  trackGeneration(stored.handle);
224
473
  return;
225
474
  }
@@ -227,10 +476,19 @@ function vercelAI(options = {}) {
227
476
  id,
228
477
  ...generation,
229
478
  });
230
- generationSpanIdsByCallId.set("v6-latest", id);
479
+ latestV6GenerationId = id;
231
480
  }
232
481
  function startV7Generation(event) {
233
- const { trace } = resolveTrace(event);
482
+ if (event.stepNumber === 0) {
483
+ if (sawV7StepZero && phase === "active") {
484
+ throw new Error(CONCURRENT_REUSE_ERROR);
485
+ }
486
+ sawV7StepZero = true;
487
+ }
488
+ const resolved = resolveTrace(event);
489
+ if (!resolved)
490
+ return;
491
+ const { trace } = resolved;
234
492
  const name = typeof options.generationName === "function"
235
493
  ? options.generationName({
236
494
  callId: event.callId,
@@ -256,8 +514,11 @@ function vercelAI(options = {}) {
256
514
  generationSpanIdsByCallId.set(event.callId, handle.id);
257
515
  trackGeneration(handle);
258
516
  }
259
- function startV6Generation(event) {
260
- const { trace } = resolveTrace(event);
517
+ function startV6Generation(event, key) {
518
+ const resolved = resolveTrace(event);
519
+ if (!resolved)
520
+ return undefined;
521
+ const { trace } = resolved;
261
522
  const startedAt = new Date();
262
523
  const name = typeof options.generationName === "function"
263
524
  ? options.generationName({
@@ -273,44 +534,68 @@ function vercelAI(options = {}) {
273
534
  model: event.model.modelId,
274
535
  startedAt,
275
536
  llmProvider: event.model.provider,
276
- llmInputMessages: options.recordInputs === false ? undefined : v6InputMessages(event),
537
+ llmInputMessages: options.recordInputs === false
538
+ ? undefined
539
+ : v6NormalizedMessages(event),
277
540
  llmTools: event.tools,
278
541
  });
279
- generationSpanIdsByCallId.set("v6-latest", handle.id);
542
+ latestV6GenerationId = handle.id;
280
543
  trackGeneration(handle);
281
- return { event, startedAt, handle };
544
+ return { event, startedAt, handle, key };
282
545
  }
283
546
  function endV7Generation(event) {
284
- const stored = v7Steps.get(v7StepKey(event.callId, event.stepNumber));
285
- v7Steps.delete(v7StepKey(event.callId, event.stepNumber));
547
+ const key = v7StepKey(event.callId, event.stepNumber);
548
+ const stored = v7Steps.get(key);
549
+ v7Steps.delete(key);
286
550
  if (!stored)
287
551
  return;
288
552
  const endedAt = new Date();
289
553
  const durationMs = resolveDurationMs(stored.startedAt, endedAt, event.performance?.stepTimeMs ?? event.performance?.responseTimeMs);
290
- const output = typeof event.text === "string"
291
- ? event.text
292
- : event.content
293
- ? stringifyContent(event.content)
294
- : undefined;
554
+ const output = structuredAssistantOutput(event.text, event.content);
555
+ const generationError = event.error != null ? errorMessage(event.error) : undefined;
295
556
  for (const toolCall of event.toolCalls ?? []) {
296
557
  if (toolCall.toolCallId) {
297
558
  generationSpanIdsByToolCallId.set(toolCall.toolCallId, stored.handle.id);
298
559
  }
299
560
  }
300
561
  stored.handle.end({
301
- output: options.recordOutputs === false ? undefined : output,
562
+ output: options.recordOutputs === false || generationError
563
+ ? undefined
564
+ : output,
302
565
  model: event.model.modelId,
303
566
  durationMs,
304
567
  endedAt: addMs(stored.startedAt, durationMs),
305
568
  llmProvider: event.model.provider,
306
- llmOutputMessages: options.recordOutputs === false || output === undefined
569
+ llmOutputMessages: options.recordOutputs === false ||
570
+ output === undefined ||
571
+ generationError
307
572
  ? undefined
308
573
  : [{ role: "assistant", content: output }],
574
+ status: generationError ? "ERROR" : undefined,
575
+ error: options.recordOutputs === false ? undefined : generationError,
309
576
  });
310
577
  }
311
- return {
578
+ function resolveToolParentId(callId, toolCallId) {
579
+ return ((toolCallId && generationSpanIdsByToolCallId.get(toolCallId)) ||
580
+ (callId ? generationSpanIdsByCallId.get(callId) : undefined) ||
581
+ latestV6GenerationId);
582
+ }
583
+ const integration = {
312
584
  onLanguageModelCallStart(event) {
313
- const { trace } = resolveTrace(event);
585
+ if (phase === "active" &&
586
+ generationSpanIdsByCallId.size > 0 &&
587
+ !generationSpanIdsByCallId.has(event.callId)) {
588
+ const belongsToActiveStep = [...v7Steps.values()].some((step) => step.event.callId === event.callId);
589
+ // Multi-step v7 registers the next callId on step start before the
590
+ // model callback. Any other new callId is concurrent reuse.
591
+ if (!belongsToActiveStep) {
592
+ throw new Error(CONCURRENT_REUSE_ERROR);
593
+ }
594
+ }
595
+ const resolved = resolveTrace(event);
596
+ if (!resolved)
597
+ return;
598
+ const { trace } = resolved;
314
599
  if (generationSpanIdsByCallId.has(event.callId)) {
315
600
  modelCalls.set(event.callId, { event, startedAt: new Date() });
316
601
  return;
@@ -340,32 +625,32 @@ function vercelAI(options = {}) {
340
625
  trackGeneration(handle);
341
626
  },
342
627
  onLanguageModelCallEnd(event) {
628
+ if (phase !== "active" && phase !== "ending")
629
+ return;
343
630
  const stored = modelCalls.get(event.callId);
344
631
  modelCalls.delete(event.callId);
345
632
  if (!stored?.handle)
346
633
  return;
347
634
  const endedAt = new Date();
348
635
  const durationMs = resolveDurationMs(stored.startedAt, endedAt, event.performance.responseTimeMs);
636
+ const output = structuredAssistantOutput(undefined, event.content);
349
637
  stored.handle.end({
350
- output: options.recordOutputs === false
351
- ? undefined
352
- : stringifyContent(event.content),
638
+ output: options.recordOutputs === false ? undefined : output,
353
639
  model: event.modelId,
354
640
  durationMs,
355
641
  endedAt: addMs(stored.startedAt, durationMs),
356
642
  llmProvider: event.provider,
357
- llmOutputMessages: options.recordOutputs === false
643
+ llmOutputMessages: options.recordOutputs === false || output === undefined
358
644
  ? undefined
359
- : [{ role: "assistant", content: stringifyContent(event.content) }],
645
+ : [{ role: "assistant", content: output }],
360
646
  });
361
647
  },
362
648
  onToolExecutionStart(event) {
363
- const { trace } = resolveTrace();
364
- const parentId = (event.toolCall.toolCallId &&
365
- generationSpanIdsByToolCallId.get(event.toolCall.toolCallId)) ||
366
- (event.callId
367
- ? generationSpanIdsByCallId.get(event.callId)
368
- : generationSpanIdsByCallId.get("v6-latest"));
649
+ const resolved = resolveTrace();
650
+ if (!resolved)
651
+ return;
652
+ const { trace } = resolved;
653
+ const parentId = resolveToolParentId(event.callId, event.toolCall.toolCallId);
369
654
  const name = typeof options.toolName === "function"
370
655
  ? options.toolName({
371
656
  ...event,
@@ -380,6 +665,7 @@ function vercelAI(options = {}) {
380
665
  input: options.recordInputs === false ? undefined : event.toolCall.input,
381
666
  metadata: options.metadata,
382
667
  startedAt,
668
+ toolName: event.toolCall.toolName,
383
669
  });
384
670
  if (event.toolCall.toolCallId) {
385
671
  toolExecutions.set(event.toolCall.toolCallId, {
@@ -390,10 +676,13 @@ function vercelAI(options = {}) {
390
676
  }
391
677
  },
392
678
  onToolCallStart(event) {
393
- this.onToolExecutionStart?.(event);
679
+ integration.onToolExecutionStart?.(event);
394
680
  },
395
681
  onToolExecutionEnd(event) {
396
- const { trace } = resolveTrace();
682
+ const resolved = resolveTrace();
683
+ if (!resolved)
684
+ return;
685
+ const { trace } = resolved;
397
686
  const name = typeof options.toolName === "function"
398
687
  ? options.toolName(event)
399
688
  : (options.toolName ?? event.toolCall.toolName);
@@ -409,6 +698,7 @@ function vercelAI(options = {}) {
409
698
  storedTool.handle.end({
410
699
  durationMs,
411
700
  endedAt: addMs(storedTool.startedAt, durationMs),
701
+ toolName: event.toolCall.toolName,
412
702
  ...toolOutput(event, options.recordOutputs !== false),
413
703
  });
414
704
  coverParent(storedTool.parentId, addMs(storedTool.startedAt, durationMs));
@@ -417,14 +707,11 @@ function vercelAI(options = {}) {
417
707
  const endedAt = new Date();
418
708
  const startedAt = subtractMs(endedAt, event.toolExecutionMs);
419
709
  const durationMs = resolveDurationMs(startedAt, endedAt, event.toolExecutionMs);
420
- const parentId = (event.toolCall.toolCallId &&
421
- generationSpanIdsByToolCallId.get(event.toolCall.toolCallId)) ||
422
- (event.callId
423
- ? generationSpanIdsByCallId.get(event.callId)
424
- : generationSpanIdsByCallId.get("v6-latest"));
710
+ const parentId = resolveToolParentId(event.callId, event.toolCall.toolCallId);
425
711
  trace.recordTool({
426
712
  name,
427
713
  parentId,
714
+ toolName: event.toolCall.toolName,
428
715
  input: options.recordInputs === false ? undefined : event.toolCall.input,
429
716
  metadata: options.metadata,
430
717
  durationMs,
@@ -435,8 +722,10 @@ function vercelAI(options = {}) {
435
722
  coverParent(parentId, endedAt);
436
723
  },
437
724
  onStart(event) {
725
+ beginNewRun();
438
726
  recordedV6Step = false;
439
- v6Starts.push({ event, startedAt: new Date() });
727
+ const key = `start:${v6Starts.length}:${Date.now()}`;
728
+ v6Starts.push({ event, startedAt: new Date(), key });
440
729
  resolveTrace(event);
441
730
  },
442
731
  onStepStart(event) {
@@ -444,32 +733,60 @@ function vercelAI(options = {}) {
444
733
  startV7Generation(event);
445
734
  return;
446
735
  }
447
- v6Steps.set(event.stepNumber, startV6Generation(event));
736
+ const key = `step:${event.stepNumber}`;
737
+ if (v6Steps.has(key)) {
738
+ // Duplicate step start for the same step number — ignore.
739
+ return;
740
+ }
741
+ const stored = startV6Generation(event, key);
742
+ if (stored)
743
+ v6Steps.set(key, stored);
448
744
  },
449
745
  onStepEnd(event) {
746
+ if (phase !== "active" && phase !== "ending")
747
+ return;
450
748
  endV7Generation(event);
451
749
  },
452
750
  onStepFinish(event) {
751
+ if (phase !== "active" && phase !== "ending")
752
+ return;
453
753
  recordedV6Step = true;
454
- const stored = v6Steps.get(event.stepNumber);
455
- v6Steps.delete(event.stepNumber);
754
+ const key = `step:${event.stepNumber}`;
755
+ const stored = v6Steps.get(key);
756
+ v6Steps.delete(key);
456
757
  recordV6Generation(event, stored);
457
758
  },
458
759
  async onFinish(event) {
459
- if (recordedV6Step) {
460
- v6Starts.shift();
461
- recordedV6Step = false;
462
- await endOwnedTrace(event);
463
- return;
760
+ if (phase === "idle") {
761
+ // Terminal-only path for an explicit trace handle with no prior events.
762
+ if (!hasExplicitEndableTrace())
763
+ return;
764
+ beginActivity();
765
+ }
766
+ if (phase === "active") {
767
+ if (recordedV6Step) {
768
+ v6Starts.shift();
769
+ recordedV6Step = false;
770
+ }
771
+ else {
772
+ recordV6Generation(event, v6Starts.shift());
773
+ }
464
774
  }
465
- recordV6Generation(event, v6Starts.shift());
466
775
  await endOwnedTrace(event);
467
776
  },
468
777
  async onEnd(event) {
778
+ if (phase === "idle") {
779
+ if (!hasExplicitEndableTrace())
780
+ return;
781
+ beginActivity();
782
+ }
469
783
  await endOwnedTrace(event);
470
784
  },
471
785
  onToolCallFinish(event) {
472
- const { trace } = resolveTrace();
786
+ const resolved = resolveTrace();
787
+ if (!resolved)
788
+ return;
789
+ const { trace } = resolved;
473
790
  const name = typeof options.toolName === "function"
474
791
  ? options.toolName(event)
475
792
  : (options.toolName ?? event.toolCall.toolName);
@@ -486,6 +803,7 @@ function vercelAI(options = {}) {
486
803
  storedTool.handle.end({
487
804
  durationMs,
488
805
  endedAt: toolEndedAt,
806
+ toolName: event.toolCall.toolName,
489
807
  ...v6ToolOutput(event, options.recordOutputs !== false),
490
808
  });
491
809
  coverParent(storedTool.parentId, toolEndedAt);
@@ -494,12 +812,11 @@ function vercelAI(options = {}) {
494
812
  const endedAt = new Date();
495
813
  const durationMs = resolveDurationMs(subtractMs(endedAt, event.durationMs), endedAt, event.durationMs);
496
814
  const startedAt = subtractMs(endedAt, durationMs);
497
- const parentId = (event.toolCall.toolCallId &&
498
- generationSpanIdsByToolCallId.get(event.toolCall.toolCallId)) ||
499
- generationSpanIdsByCallId.get("v6-latest");
815
+ const parentId = resolveToolParentId(undefined, event.toolCall.toolCallId);
500
816
  trace.recordTool({
501
817
  name,
502
818
  parentId,
819
+ toolName: event.toolCall.toolName,
503
820
  input: options.recordInputs === false ? undefined : event.toolCall.input,
504
821
  metadata: options.metadata,
505
822
  durationMs,
@@ -509,6 +826,31 @@ function vercelAI(options = {}) {
509
826
  });
510
827
  coverParent(parentId, endedAt);
511
828
  },
829
+ async fail(error) {
830
+ const message = errorMessage(error);
831
+ runError = message;
832
+ if (phase === "ending") {
833
+ // Best-effort: apply before the in-flight terminal send reads error state.
834
+ endingTrace?.fail(rootErrorPayload(message));
835
+ endingTrace?.output(undefined);
836
+ return;
837
+ }
838
+ if (phase === "idle") {
839
+ beginActivity();
840
+ }
841
+ await endOwnedTrace({ error });
842
+ },
843
+ async flush() {
844
+ await Promise.all(Array.from(pending));
845
+ },
846
+ async shutdown() {
847
+ if (phase === "active") {
848
+ await endOwnedTrace({});
849
+ }
850
+ await integration.flush();
851
+ resetRunState();
852
+ },
512
853
  };
854
+ return integration;
513
855
  }
514
856
  //# sourceMappingURL=vercel-ai.js.map