@vercel/agent-runs 0.0.0-stage → 0.0.1

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.
Files changed (129) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +77 -0
  3. package/README.md +251 -2
  4. package/dist/src/adapters/agent-transport.d.ts +9 -0
  5. package/dist/src/adapters/agent-transport.d.ts.map +1 -0
  6. package/dist/src/adapters/agent-transport.js +87 -0
  7. package/dist/src/adapters/agent-transport.js.map +1 -0
  8. package/dist/src/adapters/ai-sdk-payload.d.ts +16 -0
  9. package/dist/src/adapters/ai-sdk-payload.d.ts.map +1 -0
  10. package/dist/src/adapters/ai-sdk-payload.js +30 -0
  11. package/dist/src/adapters/ai-sdk-payload.js.map +1 -0
  12. package/dist/src/adapters/ai-sdk-telemetry.d.ts +15 -0
  13. package/dist/src/adapters/ai-sdk-telemetry.d.ts.map +1 -0
  14. package/dist/src/adapters/ai-sdk-telemetry.js +151 -0
  15. package/dist/src/adapters/ai-sdk-telemetry.js.map +1 -0
  16. package/dist/src/adapters/otel-error.d.ts +4 -0
  17. package/dist/src/adapters/otel-error.d.ts.map +1 -0
  18. package/dist/src/adapters/otel-error.js +32 -0
  19. package/dist/src/adapters/otel-error.js.map +1 -0
  20. package/dist/src/adapters/otel-ids.d.ts +20 -0
  21. package/dist/src/adapters/otel-ids.d.ts.map +1 -0
  22. package/dist/src/adapters/otel-ids.js +74 -0
  23. package/dist/src/adapters/otel-ids.js.map +1 -0
  24. package/dist/src/adapters/otel.d.ts +34 -0
  25. package/dist/src/adapters/otel.d.ts.map +1 -0
  26. package/dist/src/adapters/otel.js +147 -0
  27. package/dist/src/adapters/otel.js.map +1 -0
  28. package/dist/src/adapters/serialization.d.ts +37 -0
  29. package/dist/src/adapters/serialization.d.ts.map +1 -0
  30. package/dist/src/adapters/serialization.js +318 -0
  31. package/dist/src/adapters/serialization.js.map +1 -0
  32. package/dist/src/agent-run.d.ts +85 -0
  33. package/dist/src/agent-run.d.ts.map +1 -0
  34. package/dist/src/agent-run.js +213 -0
  35. package/dist/src/agent-run.js.map +1 -0
  36. package/dist/src/ai-sdk.d.ts +8 -0
  37. package/dist/src/ai-sdk.d.ts.map +1 -0
  38. package/dist/src/ai-sdk.js +8 -0
  39. package/dist/src/ai-sdk.js.map +1 -0
  40. package/dist/src/capture.d.ts +6 -0
  41. package/dist/src/capture.d.ts.map +1 -0
  42. package/dist/src/capture.js +9 -0
  43. package/dist/src/capture.js.map +1 -0
  44. package/dist/src/core/activation.d.ts +4 -0
  45. package/dist/src/core/activation.d.ts.map +1 -0
  46. package/dist/src/core/activation.js +31 -0
  47. package/dist/src/core/activation.js.map +1 -0
  48. package/dist/src/core/attributes.d.ts +15 -0
  49. package/dist/src/core/attributes.d.ts.map +1 -0
  50. package/dist/src/core/attributes.js +76 -0
  51. package/dist/src/core/attributes.js.map +1 -0
  52. package/dist/src/core/content-policy.d.ts +29 -0
  53. package/dist/src/core/content-policy.d.ts.map +1 -0
  54. package/dist/src/core/content-policy.js +94 -0
  55. package/dist/src/core/content-policy.js.map +1 -0
  56. package/dist/src/core/context.d.ts +4 -0
  57. package/dist/src/core/context.d.ts.map +1 -0
  58. package/dist/src/core/context.js +28 -0
  59. package/dist/src/core/context.js.map +1 -0
  60. package/dist/src/core/delegation.d.ts +14 -0
  61. package/dist/src/core/delegation.d.ts.map +1 -0
  62. package/dist/src/core/delegation.js +7 -0
  63. package/dist/src/core/delegation.js.map +1 -0
  64. package/dist/src/core/mcp.d.ts +25 -0
  65. package/dist/src/core/mcp.d.ts.map +1 -0
  66. package/dist/src/core/mcp.js +33 -0
  67. package/dist/src/core/mcp.js.map +1 -0
  68. package/dist/src/core/scopes.d.ts +60 -0
  69. package/dist/src/core/scopes.d.ts.map +1 -0
  70. package/dist/src/core/scopes.js +574 -0
  71. package/dist/src/core/scopes.js.map +1 -0
  72. package/dist/src/core/snapshot.d.ts +8 -0
  73. package/dist/src/core/snapshot.d.ts.map +1 -0
  74. package/dist/src/core/snapshot.js +207 -0
  75. package/dist/src/core/snapshot.js.map +1 -0
  76. package/dist/src/core/span-kinds.d.ts +13 -0
  77. package/dist/src/core/span-kinds.d.ts.map +1 -0
  78. package/dist/src/core/span-kinds.js +338 -0
  79. package/dist/src/core/span-kinds.js.map +1 -0
  80. package/dist/src/core/types.d.ts +298 -0
  81. package/dist/src/core/types.d.ts.map +1 -0
  82. package/dist/src/core/types.js +2 -0
  83. package/dist/src/core/types.js.map +1 -0
  84. package/dist/src/core/writer.d.ts +16 -0
  85. package/dist/src/core/writer.d.ts.map +1 -0
  86. package/dist/src/core/writer.js +77 -0
  87. package/dist/src/core/writer.js.map +1 -0
  88. package/dist/src/delegation.d.ts +3 -0
  89. package/dist/src/delegation.d.ts.map +1 -0
  90. package/dist/src/delegation.js +3 -0
  91. package/dist/src/delegation.js.map +1 -0
  92. package/dist/src/index.d.ts +7 -0
  93. package/dist/src/index.d.ts.map +1 -0
  94. package/dist/src/index.js +5 -0
  95. package/dist/src/index.js.map +1 -0
  96. package/dist/src/operations.d.ts +97 -0
  97. package/dist/src/operations.d.ts.map +1 -0
  98. package/dist/src/operations.js +185 -0
  99. package/dist/src/operations.js.map +1 -0
  100. package/dist/src/otel.d.ts +13 -0
  101. package/dist/src/otel.d.ts.map +1 -0
  102. package/dist/src/otel.js +13 -0
  103. package/dist/src/otel.js.map +1 -0
  104. package/package.json +75 -3
  105. package/src/adapters/agent-transport.ts +113 -0
  106. package/src/adapters/ai-sdk-payload.ts +44 -0
  107. package/src/adapters/ai-sdk-telemetry.ts +179 -0
  108. package/src/adapters/otel-error.ts +34 -0
  109. package/src/adapters/otel-ids.ts +82 -0
  110. package/src/adapters/otel.ts +232 -0
  111. package/src/adapters/serialization.ts +338 -0
  112. package/src/agent-run.ts +341 -0
  113. package/src/ai-sdk.ts +7 -0
  114. package/src/capture.ts +13 -0
  115. package/src/core/activation.ts +42 -0
  116. package/src/core/attributes.ts +85 -0
  117. package/src/core/content-policy.ts +116 -0
  118. package/src/core/context.ts +34 -0
  119. package/src/core/delegation.ts +20 -0
  120. package/src/core/mcp.ts +60 -0
  121. package/src/core/scopes.ts +761 -0
  122. package/src/core/snapshot.ts +245 -0
  123. package/src/core/span-kinds.ts +406 -0
  124. package/src/core/types.ts +304 -0
  125. package/src/core/writer.ts +135 -0
  126. package/src/delegation.ts +2 -0
  127. package/src/index.ts +44 -0
  128. package/src/operations.ts +342 -0
  129. package/src/otel.ts +18 -0
@@ -0,0 +1,761 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { boundedTraceError } from "../adapters/serialization.js";
3
+ import { createSpanWriter, type TraceOperation } from "./writer.js";
4
+ import { usageAttributes } from "./attributes.js";
5
+ import {
6
+ completeScope,
7
+ capturedScopeData as capturedData,
8
+ prepareScope,
9
+ parentKinds,
10
+ applyAttributes,
11
+ } from "./span-kinds.js";
12
+ import { mcpLifecycle, type McpLifecycle } from "./mcp.js";
13
+ import { intersectCapture } from "./activation.js";
14
+ import { snapshotRecord, traceSnapshot, validSnapshot, boundedSerializer } from "./snapshot.js";
15
+ import { withAgentHandoff } from "./delegation.js";
16
+ import { withoutDeclinedContent } from "./content-policy.js";
17
+ import { runTraceContext } from "./context.js";
18
+ import type {
19
+ AgentTelemetry,
20
+ Attributes,
21
+ CaptureDecision,
22
+ ContentSerializer,
23
+ ExecutionContext,
24
+ Operation,
25
+ ScopeData,
26
+ ScopeIdentity,
27
+ ScopeRecord,
28
+ ScopeTerminal,
29
+ TraceErrorHandler,
30
+ TraceLink,
31
+ TraceReference,
32
+ Usage,
33
+ } from "./types.js";
34
+
35
+ const UNFINISHED_CHILDREN = 10000;
36
+ const NO_CAPTURE: CaptureDecision = { emit: false, recordInputs: false, recordOutputs: false };
37
+
38
+ interface Construction extends Partial<ScopeRecord> {
39
+ readonly key: string;
40
+ readonly deferred?: boolean;
41
+ readonly executionContext?: ExecutionContext;
42
+ /** Applies sampling to a host-supplied root reference. */
43
+ readonly sample?: boolean;
44
+ }
45
+ export interface ChildOptions {
46
+ readonly links?: readonly TraceLink[];
47
+ readonly startTimeMs?: number;
48
+ }
49
+ export interface LiveOperation extends Operation {
50
+ child(data: ScopeData, key?: string, options?: ChildOptions): Promise<LiveOperation>;
51
+ /** Returns an unsettled child with this identity, without starting one. */
52
+ find(data: ScopeData): LiveOperation | undefined;
53
+ /** Unsettled children, in start order. */
54
+ readonly children: readonly LiveOperation[];
55
+ usage(usage: Usage, key?: string): Promise<void>;
56
+ /** Carries a model call's messages up to its turn: the first input and the latest output. */
57
+ turnContent(content: { readonly input?: string; readonly output?: string }): void;
58
+ error(error?: unknown, type?: string): void;
59
+ /** Replaces the host context for this operation and its descendants. */
60
+ useContext(context: ExecutionContext): void;
61
+ /** Fills in a deferred operation's details from a later call with the same identity. */
62
+ refine(data: ScopeData, startTimeMs?: number): void;
63
+ /** Lowers capture for this operation and its children, and drops retained content it no longer allows. */
64
+ narrow(capture: CaptureDecision): void;
65
+ /** Completes this operation, or closes the open descendants of a finished one. */
66
+ release(result: ScopeTerminal): Promise<void>;
67
+ /** Finished, with no unfinished descendants. */
68
+ readonly settled: boolean;
69
+ readonly callId?: string;
70
+ readonly mcp: McpLifecycle;
71
+ record(): ScopeRecord;
72
+ }
73
+
74
+ /** Durable trees reserve stable IDs and defer spans whose lifetime can cross processes. */
75
+ const DEFERRED_WHEN_DURABLE: ReadonlySet<ScopeData["type"]> = new Set([
76
+ "activation",
77
+ "tool",
78
+ "approval",
79
+ ]);
80
+ /** Durable work a successful attempt or turn leaves open, such as a tool awaiting approval. */
81
+ const OUTLIVES_ATTEMPT: ReadonlySet<ScopeData["type"]> = new Set(["tool", "approval"]);
82
+
83
+ function segment(value: string | number): string {
84
+ return encodeURIComponent(String(value));
85
+ }
86
+
87
+ /** Children with a stable identity hydrate on re-entry; the rest are numbered. */
88
+ function semanticKey(data: ScopeData): string | undefined {
89
+ switch (data.type) {
90
+ case "step":
91
+ return `step:${segment(data.options.index)}:${segment(data.options.attempt ?? 0)}`;
92
+ case "tool":
93
+ return `tool:${segment(data.options.callId)}`;
94
+ case "approval":
95
+ return `approval:${segment(data.options.requestId)}`;
96
+ default:
97
+ return undefined;
98
+ }
99
+ }
100
+
101
+ function terminalOf(result: ScopeTerminal & { errorType?: string; result?: unknown }) {
102
+ const failed = result.failed || result.outcome === "failed";
103
+ return {
104
+ ...result,
105
+ model: "result" in result ? result.result : result.model,
106
+ failed,
107
+ errorCode: "errorType" in result ? result.errorType : result.errorCode,
108
+ } as ScopeTerminal;
109
+ }
110
+
111
+ export function createScopes(input: {
112
+ readonly telemetry: AgentTelemetry;
113
+ readonly serializer: ContentSerializer;
114
+ readonly durable: boolean;
115
+ readonly onError?: TraceErrorHandler;
116
+ }) {
117
+ const telemetry = input.telemetry;
118
+ const serializer = boundedSerializer(input.serializer, input.onError);
119
+ const engine = createSpanWriter({ telemetry, onError: input.onError });
120
+ const report: TraceErrorHandler = (error, context) => {
121
+ try {
122
+ input.onError?.(error, context);
123
+ } catch {}
124
+ };
125
+
126
+ function snapshotContent(value: unknown): unknown {
127
+ const json = serializer.json(value);
128
+ try {
129
+ return json === undefined ? undefined : JSON.parse(json);
130
+ } catch {
131
+ return undefined;
132
+ }
133
+ }
134
+
135
+ function reserve(
136
+ data: ScopeData,
137
+ key: string,
138
+ parent: TraceReference | undefined,
139
+ prepared: ReturnType<typeof prepareScope>,
140
+ capture: CaptureDecision,
141
+ ): TraceReference | undefined {
142
+ const ids = telemetry.ids;
143
+ if (!input.durable || ids === undefined) return undefined;
144
+ // Re-executed model calls and memory reads are new work and get new IDs.
145
+ if (data.type === "model" || data.type === "memory") return undefined;
146
+ if (data.type === "activation" && parent !== undefined)
147
+ return {
148
+ traceId: parent.traceId,
149
+ spanId: ids.spanId(key),
150
+ traceFlags: capture.emit ? parent.traceFlags & 1 : 0,
151
+ };
152
+ if (data.type === "activation") {
153
+ const traceId = ids.traceId(key);
154
+ return {
155
+ traceId,
156
+ spanId: ids.spanId(key),
157
+ traceFlags: capture.emit && (telemetry.samples?.(traceId, prepared) ?? true) ? 1 : 0,
158
+ };
159
+ }
160
+ return parent === undefined
161
+ ? undefined
162
+ : { ...parent, spanId: ids.spanId(key), isRemote: false };
163
+ }
164
+
165
+ async function construct(
166
+ identity: ScopeIdentity,
167
+ capture: CaptureDecision,
168
+ data: ScopeData,
169
+ parent: LiveOperation | undefined,
170
+ attempt: ScopeRecord["attempt"],
171
+ binding: Construction,
172
+ onChange: () => Promise<void>,
173
+ ): Promise<LiveOperation> {
174
+ try {
175
+ return await constructOperation(identity, capture, data, parent, attempt, binding, onChange);
176
+ } catch (error) {
177
+ report(error, { phase: "start", operation: data.type, reference: binding.reference });
178
+ return constructOperation(
179
+ identity,
180
+ NO_CAPTURE,
181
+ capturedData(data, NO_CAPTURE),
182
+ parent,
183
+ attempt,
184
+ {
185
+ ...binding,
186
+ sample: false,
187
+ reference: {
188
+ traceId:
189
+ binding.reference?.traceId ??
190
+ parent?.reference.traceId ??
191
+ binding.parent?.traceId ??
192
+ randomBytes(16).toString("hex"),
193
+ spanId: binding.reference?.spanId ?? randomBytes(8).toString("hex"),
194
+ traceFlags: 0,
195
+ },
196
+ },
197
+ onChange,
198
+ );
199
+ }
200
+ }
201
+
202
+ async function constructOperation(
203
+ identity: ScopeIdentity,
204
+ capture: CaptureDecision,
205
+ data: ScopeData,
206
+ parent: LiveOperation | undefined,
207
+ attempt: ScopeRecord["attempt"],
208
+ binding: Construction,
209
+ onChange: () => Promise<void>,
210
+ ): Promise<LiveOperation> {
211
+ if (
212
+ binding.terminal?.error !== null &&
213
+ typeof binding.terminal?.error === "object" &&
214
+ !(binding.terminal.error instanceof Error) &&
215
+ "message" in binding.terminal.error &&
216
+ typeof binding.terminal.error.message === "string"
217
+ ) {
218
+ const error = new Error(binding.terminal.error.message);
219
+ error.name = binding.terminal.errorCode ?? "Error";
220
+ binding = { ...binding, terminal: { ...binding.terminal, error } };
221
+ }
222
+ const key = binding.key;
223
+ let startTimeMs = binding.startTimeMs ?? Date.now();
224
+ // A restored record keeps its saved parent; a nested activation names its own.
225
+ const parentReference = binding.parent ?? parent?.reference;
226
+ if (capture.emit) capture = intersectCapture(capture, telemetry.active()?.capture);
227
+ let actualCapture = capture;
228
+ let actualData = capturedData(data, actualCapture);
229
+ let prepared = prepareScope(
230
+ {
231
+ identity,
232
+ data: actualData,
233
+ attempt,
234
+ capture: actualCapture,
235
+ key,
236
+ parent: parentReference,
237
+ startTimeMs,
238
+ links: binding.links,
239
+ },
240
+ serializer,
241
+ );
242
+ prepared = { ...prepared, attributes: { ...prepared.attributes, ...binding.attributes } };
243
+ actualData = snapshotRecord(
244
+ {
245
+ key,
246
+ identity,
247
+ data: actualData,
248
+ capture: actualCapture,
249
+ reference: binding.reference ?? {
250
+ traceId: "0".repeat(32),
251
+ spanId: "0".repeat(16),
252
+ traceFlags: 0,
253
+ },
254
+ startTimeMs,
255
+ },
256
+ serializer,
257
+ ).data;
258
+ const deferred =
259
+ binding.deferred ?? (input.durable && DEFERRED_WHEN_DURABLE.has(actualData.type));
260
+ let host = binding.executionContext;
261
+ let reference =
262
+ binding.reference ?? reserve(data, key, parentReference, prepared, actualCapture);
263
+ // A nested turn joins its parent's sampling decision; a root turn asks the sampler.
264
+ if (binding.sample === true && reference !== undefined)
265
+ reference = {
266
+ ...reference,
267
+ traceFlags:
268
+ (reference.traceFlags & 1) !== 0 &&
269
+ actualCapture.emit &&
270
+ (parentReference !== undefined ||
271
+ (telemetry.samples?.(reference.traceId, prepared) ?? true))
272
+ ? 1
273
+ : 0,
274
+ };
275
+ let operation: TraceOperation | undefined = deferred
276
+ ? undefined
277
+ : engine.start(prepared, actualCapture, host, reference);
278
+ const retainedReference = reference ?? operation?.reference;
279
+ if (retainedReference === undefined)
280
+ throw new Error("A deferred trace scope requires a reserved reference.");
281
+ const sampled = (retainedReference.traceFlags & 1) !== 0;
282
+ actualCapture = {
283
+ emit: actualCapture.emit && sampled,
284
+ recordInputs: actualCapture.emit && sampled && actualCapture.recordInputs,
285
+ recordOutputs: actualCapture.emit && sampled && actualCapture.recordOutputs,
286
+ };
287
+ actualData = capturedData(actualData, actualCapture);
288
+ let finished = binding.finished ?? false;
289
+ let terminalResult = binding.terminal;
290
+ const children = new Map<string, LiveOperation>();
291
+ const pending = new Set<Promise<void>>();
292
+ let childSequence = binding.childSequence ?? 0;
293
+ let totalUsage = binding.usage;
294
+ const usageKeys = new Set(binding.usageKeys ?? []);
295
+ const enrichment: Record<string, Attributes[string]> = {};
296
+ const callId = actualData.type === "tool" ? actualData.options.callId : undefined;
297
+
298
+ function changed(): void {
299
+ void onChange();
300
+ }
301
+
302
+ /** Content arriving now is held to any narrower run ceiling in force. */
303
+ function current(): CaptureDecision {
304
+ return intersectCapture(actualCapture, telemetry.active()?.capture);
305
+ }
306
+
307
+ async function child(
308
+ childData: ScopeData,
309
+ childKey: string,
310
+ options: ChildOptions = {},
311
+ ): Promise<LiveOperation> {
312
+ const existing = children.get(childKey);
313
+ if (input.durable && existing !== undefined && !existing.settled) {
314
+ existing.refine(childData, options.startTimeMs);
315
+ return existing;
316
+ }
317
+ if (finished) throw new Error("The operation is not permitted in this trace scope.");
318
+ for (const [previousKey, previous] of children)
319
+ if (previous.settled) children.delete(previousKey);
320
+ let childCapture = actualCapture;
321
+ if (children.size >= UNFINISHED_CHILDREN) {
322
+ report(new Error("Trace unfinished-child limit reached."), {
323
+ phase: "start",
324
+ operation: childData.type,
325
+ reference: retainedReference,
326
+ });
327
+ childCapture = NO_CAPTURE;
328
+ }
329
+ if (childData.type === "model" && operation !== undefined)
330
+ applyAttributes(operation, { "agent.model.id": childData.options.modelId });
331
+ const next = await construct(
332
+ identity,
333
+ childCapture,
334
+ childData,
335
+ runtime,
336
+ childData.type === "step"
337
+ ? { index: childData.options.index, attempt: childData.options.attempt ?? 0 }
338
+ : attempt,
339
+ {
340
+ key: childKey,
341
+ executionContext: host,
342
+ links: options.links,
343
+ startTimeMs: options.startTimeMs,
344
+ },
345
+ onChange,
346
+ );
347
+ if (childCapture.emit || children.size < UNFINISHED_CHILDREN) children.set(childKey, next);
348
+ await onChange();
349
+ return next;
350
+ }
351
+
352
+ async function closeChildren(result: ScopeTerminal, all: boolean): Promise<void> {
353
+ const container = actualData.type === "step" || actualData.type === "activation";
354
+ for (const next of children.values())
355
+ if (all || !container || !OUTLIVES_ATTEMPT.has(next.type))
356
+ await next.release({ failed: result.failed, error: result.error, outcome: "abandoned" });
357
+ }
358
+
359
+ async function finish(result: ScopeTerminal): Promise<void> {
360
+ if (pending.size > 0) await Promise.allSettled(pending);
361
+ if (finished) return;
362
+ const capture = current();
363
+ if (!capture.recordOutputs) result = { ...result, error: undefined };
364
+ // A call whose work already failed cannot finish as completed.
365
+ if (terminalResult?.failed)
366
+ result = {
367
+ ...result,
368
+ outcome: result.outcome === "completed" ? "failed" : result.outcome,
369
+ failed: true,
370
+ error: terminalResult.error,
371
+ errorCode: result.errorCode ?? terminalResult.errorCode,
372
+ };
373
+ finished = true;
374
+ terminalResult = result;
375
+ await closeChildren(result, result.failed === true || !input.durable);
376
+ operation ??= engine.start(prepared, actualCapture, host, retainedReference);
377
+ const terminal =
378
+ actualData.type === "activation" && result.usage === undefined
379
+ ? { ...result, usage: totalUsage }
380
+ : result;
381
+ try {
382
+ completeScope(
383
+ operation,
384
+ actualData,
385
+ terminal,
386
+ { ...capture, emit: actualCapture.emit },
387
+ startTimeMs,
388
+ serializer,
389
+ );
390
+ } catch (error) {
391
+ report(error, {
392
+ phase: "complete",
393
+ operation: actualData.type,
394
+ reference: retainedReference,
395
+ });
396
+ }
397
+ applyAttributes(operation, enrichment);
398
+ if (actualData.type === "model" && result.model !== undefined) {
399
+ await parent?.usage(result.model.usage, key);
400
+ const content = result.model.content;
401
+ if (capture.recordOutputs && actualCapture.recordOutputs && content !== undefined) {
402
+ const output = serializer.outputMessages(content, result.model.finishReason);
403
+ if (output !== undefined) parent?.turnContent({ output });
404
+ }
405
+ }
406
+ operation.end(result.endTimeMs);
407
+ await onChange();
408
+ }
409
+
410
+ const runtime: LiveOperation = {
411
+ waitUntil(completion) {
412
+ pending.add(completion);
413
+ parent?.waitUntil(completion);
414
+ void completion.then(
415
+ () => pending.delete(completion),
416
+ () => pending.delete(completion),
417
+ );
418
+ },
419
+ snapshot: () => traceSnapshot(runtime.record()),
420
+ attributes: (attributes) => runtime.update({ attributes }),
421
+ modelCall: (options, key) => runtime.child({ type: "model", options }, key),
422
+ fail(error) {
423
+ runtime.error(error);
424
+ return runtime.complete({ outcome: "failed", failed: true });
425
+ },
426
+ update(update) {
427
+ if (finished) return;
428
+ const attributes = (withoutDeclinedContent(update.attributes ?? {}, current()) ??
429
+ update.attributes) as Attributes;
430
+ if (actualData.type === "activation")
431
+ actualData = {
432
+ ...actualData,
433
+ options: {
434
+ ...actualData.options,
435
+ attributes: { ...actualData.options.attributes, ...attributes },
436
+ },
437
+ };
438
+ prepared = {
439
+ ...prepared,
440
+ attributes: { ...prepared.attributes, ...attributes },
441
+ links: update.links ?? prepared.links,
442
+ };
443
+ if (operation !== undefined) applyAttributes(operation, attributes);
444
+ changed();
445
+ },
446
+ useContext(context) {
447
+ host = context;
448
+ for (const next of children.values()) next.useContext(context);
449
+ },
450
+ refine(next, earlierStart) {
451
+ if (finished || operation !== undefined) return;
452
+ if (earlierStart !== undefined && earlierStart < startTimeMs) startTimeMs = earlierStart;
453
+ if (next.type === "tool" && actualData.type === "tool") {
454
+ const known = actualData.options;
455
+ actualData = capturedData(
456
+ {
457
+ type: "tool",
458
+ options: {
459
+ ...known,
460
+ kind: known.kind ?? next.options.kind,
461
+ // Earlier arguments were recorded with consent; new ones need it now.
462
+ arguments:
463
+ known.arguments ??
464
+ (current().recordInputs ? snapshotContent(next.options.arguments) : undefined),
465
+ },
466
+ },
467
+ actualCapture,
468
+ );
469
+ }
470
+ const again = prepareScope(
471
+ {
472
+ identity,
473
+ data: actualData,
474
+ attempt,
475
+ capture: actualCapture,
476
+ key,
477
+ parent: parentReference,
478
+ startTimeMs,
479
+ links: prepared.links,
480
+ },
481
+ serializer,
482
+ );
483
+ const defined = Object.fromEntries(
484
+ Object.entries(again.attributes).filter(([, value]) => value !== undefined),
485
+ );
486
+ prepared = {
487
+ ...prepared,
488
+ name: again.name,
489
+ kind: again.kind,
490
+ startTimeMs,
491
+ attributes: { ...prepared.attributes, ...defined },
492
+ };
493
+ changed();
494
+ },
495
+ narrow(next) {
496
+ const narrowed = intersectCapture(actualCapture, next);
497
+ actualCapture = { ...narrowed, emit: actualCapture.emit };
498
+ actualData = capturedData(actualData, actualCapture);
499
+ const kept = (withoutDeclinedContent(prepared.attributes, actualCapture) ??
500
+ prepared.attributes) as Attributes;
501
+ prepared = {
502
+ ...prepared,
503
+ attributes:
504
+ actualData.type === "activation"
505
+ ? {
506
+ ...kept,
507
+ "agent.trace.content.input": actualCapture.recordInputs,
508
+ "agent.trace.content.output": actualCapture.recordOutputs,
509
+ }
510
+ : kept,
511
+ };
512
+ const permitted = withoutDeclinedContent(enrichment, actualCapture) ?? enrichment;
513
+ for (const key of Object.keys(enrichment)) if (!(key in permitted)) delete enrichment[key];
514
+ if (!actualCapture.recordOutputs && terminalResult?.error !== undefined)
515
+ terminalResult = { ...terminalResult, error: undefined };
516
+ operation?.narrow(actualCapture);
517
+ for (const child of children.values()) child.narrow(actualCapture);
518
+ changed();
519
+ },
520
+ async release(result) {
521
+ if (finished) await closeChildren(result, result.failed === true || !input.durable);
522
+ else await finish(terminalOf(result));
523
+ },
524
+ record() {
525
+ return snapshotRecord(
526
+ {
527
+ version: 1,
528
+ finished,
529
+ terminal: terminalResult,
530
+ usage: totalUsage,
531
+ usageKeys: [...usageKeys],
532
+ attributes: { ...prepared.attributes, ...enrichment },
533
+ childSequence,
534
+ children: [...children.values()]
535
+ .filter((child) => !child.settled)
536
+ .map((child) => child.record()),
537
+ key,
538
+ identity,
539
+ data: actualData,
540
+ capture: actualCapture,
541
+ reference: retainedReference,
542
+ parent: parentReference,
543
+ startTimeMs,
544
+ attempt,
545
+ links: prepared.links,
546
+ },
547
+ serializer,
548
+ );
549
+ },
550
+ get parent() {
551
+ return parentReference;
552
+ },
553
+ get startTimeMs() {
554
+ return startTimeMs;
555
+ },
556
+ child(data, childKey, options) {
557
+ if (!parentKinds(data).includes(actualData.type))
558
+ throw new Error("The operation is not permitted in this trace scope.");
559
+ const name =
560
+ childKey === undefined
561
+ ? ((input.durable ? semanticKey(data) : undefined) ?? `${data.type}:${childSequence++}`)
562
+ : `${data.type}:${segment(childKey)}`;
563
+ return child(data, `${key}/${name}`, options);
564
+ },
565
+ find(data) {
566
+ const name = semanticKey(data);
567
+ const found = name === undefined ? undefined : children.get(`${key}/${name}`);
568
+ return found === undefined || found.settled ? undefined : found;
569
+ },
570
+ get children() {
571
+ return [...children.values()].filter((next) => !next.settled);
572
+ },
573
+ type: actualData.type,
574
+ reference: retainedReference,
575
+ callId,
576
+ get capture() {
577
+ return actualCapture;
578
+ },
579
+ mcp: mcpLifecycle({
580
+ serializer,
581
+ ...actualCapture,
582
+ write(attributes) {
583
+ const permitted = (withoutDeclinedContent(attributes, current()) ??
584
+ attributes) as Attributes;
585
+ Object.assign(enrichment, permitted);
586
+ if (operation !== undefined) applyAttributes(operation, permitted);
587
+ },
588
+ error(error, type) {
589
+ runtime.error(error, type);
590
+ },
591
+ }),
592
+ get finished() {
593
+ return finished;
594
+ },
595
+ get settled() {
596
+ return finished && [...children.values()].every((next) => next.settled);
597
+ },
598
+ run(execute, ceiling) {
599
+ if (finished) throw new Error("The trace scope has already finished.");
600
+ // The ceiling governs work inside this run, not what the operation already recorded.
601
+ const scoped = intersectCapture(
602
+ intersectCapture(actualCapture, ceiling),
603
+ telemetry.active()?.capture,
604
+ );
605
+ const delegation =
606
+ actualData.type === "tool" &&
607
+ (actualData.options.kind === "subagent-call" ||
608
+ actualData.options.kind === "remote-agent-call")
609
+ ? actualData.options
610
+ : undefined;
611
+ const callback =
612
+ delegation === undefined
613
+ ? execute
614
+ : () =>
615
+ withAgentHandoff(
616
+ {
617
+ caller: retainedReference,
618
+ conversationId: identity.conversationId,
619
+ parentRunId: identity.runId,
620
+ parentCallId: delegation.callId,
621
+ agentName: delegation.name,
622
+ capture: scoped,
623
+ },
624
+ execute,
625
+ );
626
+ return runTraceContext(
627
+ telemetry,
628
+ { type: runtime.type, reference: retainedReference, capture: scoped, mcp: runtime.mcp },
629
+ callback,
630
+ host,
631
+ );
632
+ },
633
+ async complete(result = terminalResult ?? {}) {
634
+ if (finished) return;
635
+ await finish(terminalOf(result));
636
+ },
637
+ async usage(usage, callKey) {
638
+ if (callKey !== undefined && usageKeys.has(callKey)) return;
639
+ if (callKey !== undefined) usageKeys.add(callKey);
640
+ if (operation !== undefined) applyAttributes(operation, usageAttributes(usage));
641
+ totalUsage = {
642
+ inputTokens:
643
+ usage.inputTokens === undefined
644
+ ? totalUsage?.inputTokens
645
+ : (totalUsage?.inputTokens ?? 0) + usage.inputTokens,
646
+ outputTokens:
647
+ usage.outputTokens === undefined
648
+ ? totalUsage?.outputTokens
649
+ : (totalUsage?.outputTokens ?? 0) + usage.outputTokens,
650
+ costUsd:
651
+ usage.costUsd === undefined
652
+ ? totalUsage?.costUsd
653
+ : (totalUsage?.costUsd ?? 0) + usage.costUsd,
654
+ };
655
+ await parent?.usage(usage, callKey);
656
+ },
657
+ turnContent(content) {
658
+ if (actualData.type !== "activation") return parent?.turnContent(content);
659
+ if (finished) return;
660
+ const attributes: Record<string, Attributes[string]> = {};
661
+ if (
662
+ content.input !== undefined &&
663
+ prepared.attributes["gen_ai.input.messages"] === undefined
664
+ )
665
+ attributes["gen_ai.input.messages"] = content.input;
666
+ if (content.output !== undefined) attributes["gen_ai.output.messages"] = content.output;
667
+ if (Object.keys(attributes).length > 0) runtime.update({ attributes });
668
+ },
669
+ error(error, errorType) {
670
+ terminalResult = {
671
+ ...terminalResult,
672
+ failed: true,
673
+ error: current().recordOutputs
674
+ ? error instanceof Error
675
+ ? boundedTraceError(error)
676
+ : snapshotContent(error)
677
+ : undefined,
678
+ errorCode: errorType ?? (error instanceof Error ? error.name : undefined),
679
+ };
680
+ if (operation !== undefined) operation.fail(terminalResult.error, terminalResult.errorCode);
681
+ changed();
682
+ },
683
+ };
684
+ for (const saved of binding.children ?? [])
685
+ children.set(
686
+ saved.key,
687
+ await construct(
688
+ saved.identity,
689
+ intersectCapture(saved.capture, actualCapture),
690
+ saved.data,
691
+ runtime,
692
+ saved.attempt,
693
+ { ...saved, deferred: true, executionContext: host },
694
+ onChange,
695
+ ),
696
+ );
697
+ if (!finished && operation !== undefined && actualData.type === "step")
698
+ operation.addEvent("step.started", undefined, startTimeMs);
699
+ const inputMessages = prepared.attributes["gen_ai.input.messages"];
700
+ if (!finished && actualData.type === "model" && typeof inputMessages === "string")
701
+ parent?.turnContent({ input: inputMessages });
702
+ return runtime;
703
+ }
704
+
705
+ return {
706
+ /** Starts a turn tree. Durable turns reserve their IDs from `key`. */
707
+ start(facts: {
708
+ identity: ScopeIdentity;
709
+ key: string;
710
+ capture: CaptureDecision;
711
+ data: ScopeData;
712
+ parent?: TraceReference;
713
+ reference?: TraceReference;
714
+ startTimeMs?: number;
715
+ links?: readonly TraceLink[];
716
+ context?: ExecutionContext;
717
+ onChange?: () => Promise<void>;
718
+ }): Promise<LiveOperation> {
719
+ return construct(
720
+ facts.identity,
721
+ facts.capture,
722
+ facts.data,
723
+ undefined,
724
+ undefined,
725
+ {
726
+ key: facts.key,
727
+ parent: facts.parent,
728
+ reference: facts.reference,
729
+ sample: facts.reference !== undefined && facts.data.type === "activation",
730
+ startTimeMs: facts.startTimeMs,
731
+ links: facts.links,
732
+ executionContext: facts.context,
733
+ },
734
+ facts.onChange ?? (() => Promise.resolve()),
735
+ );
736
+ },
737
+ /** Rebuilds a tree from a checkpoint, or returns `undefined` for an unusable one. */
738
+ async restore(
739
+ snapshot: unknown,
740
+ options: {
741
+ capture?: CaptureDecision;
742
+ context?: ExecutionContext;
743
+ onChange?: () => Promise<void>;
744
+ } = {},
745
+ ): Promise<LiveOperation | undefined> {
746
+ if (!validSnapshot(snapshot)) {
747
+ report(new Error("Invalid tracing checkpoint."), { phase: "restore" });
748
+ return undefined;
749
+ }
750
+ return construct(
751
+ snapshot.identity,
752
+ intersectCapture(snapshot.capture, options.capture),
753
+ snapshot.data,
754
+ undefined,
755
+ snapshot.attempt,
756
+ { ...snapshot, deferred: true, executionContext: options.context },
757
+ options.onChange ?? (() => Promise.resolve()),
758
+ );
759
+ },
760
+ };
761
+ }