@vercel/agent-runs 0.0.0-stage → 0.0.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.
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,245 @@
1
+ import type { ScopeRecord } from "./types.js";
2
+ import type { ContentSerializer } from "./types.js";
3
+ import { capturedScopeData } from "./span-kinds.js";
4
+ const SERIALIZED_BYTES = 32768;
5
+ const SNAPSHOT_BYTES = 65536;
6
+ const UNFINISHED_CHILDREN = 10000;
7
+ import { withoutDeclinedContent } from "./content-policy.js";
8
+
9
+ import type { TraceSnapshot } from "./types.js";
10
+ export function boundedSerializer(
11
+ serializer: ContentSerializer,
12
+ onError?: import("./types.js").TraceErrorHandler,
13
+ ): ContentSerializer {
14
+ function invoke(method: keyof ContentSerializer, args: unknown[]): string | undefined {
15
+ return bounded(() => Reflect.apply(serializer[method], serializer, args) as string | undefined);
16
+ }
17
+ function bounded(serialize: () => string | undefined): string | undefined {
18
+ try {
19
+ const value = serialize();
20
+ return value === undefined || new TextEncoder().encode(value).length > SERIALIZED_BYTES
21
+ ? undefined
22
+ : value;
23
+ } catch (error) {
24
+ try {
25
+ onError?.(error, { phase: "serialize" });
26
+ } catch {}
27
+ return undefined;
28
+ }
29
+ }
30
+ return {
31
+ json: (value) => invoke("json", [value]),
32
+ text: (value) => invoke("text", [value]),
33
+ inputMessages: (value) => {
34
+ let omitted = 0;
35
+ const json = bounded(() => {
36
+ const content = serializer.inputMessages(value);
37
+ omitted = content?.omitted ?? 0;
38
+ return content?.json;
39
+ });
40
+ return json === undefined ? undefined : { json, omitted };
41
+ },
42
+ instructions: (value) => invoke("instructions", [value]),
43
+ toolDefinitions: (value) => invoke("toolDefinitions", [value]),
44
+ outputMessages: (value, reason) => invoke("outputMessages", [value, reason]),
45
+ toolResults: (value) => invoke("toolResults", [value]),
46
+ };
47
+ }
48
+
49
+ /** Each node is capped on its own; settled children leave the tree. */
50
+ function nodeBytes(record: ScopeRecord): number {
51
+ return new TextEncoder().encode(JSON.stringify({ ...record, children: undefined })).length;
52
+ }
53
+
54
+ export function traceSnapshot(value: unknown): TraceSnapshot {
55
+ return JSON.parse(JSON.stringify(value)) as TraceSnapshot;
56
+ }
57
+
58
+ export function validSnapshot(
59
+ value: unknown,
60
+ maxBytes: number = SNAPSHOT_BYTES,
61
+ depth = 0,
62
+ ): value is ScopeRecord {
63
+ try {
64
+ if (depth > 16) return false;
65
+ if (typeof value !== "object" || value === null) return false;
66
+ const record = value as ScopeRecord;
67
+ if (
68
+ record.children !== undefined &&
69
+ (!Array.isArray(record.children) ||
70
+ record.children.length > UNFINISHED_CHILDREN ||
71
+ !record.children.every((child) => validSnapshot(child, maxBytes, depth + 1)))
72
+ )
73
+ return false;
74
+ if (record.version !== 1) return false;
75
+ if (record.finished !== undefined && typeof record.finished !== "boolean") return false;
76
+ if (
77
+ record.childSequence !== undefined &&
78
+ (!Number.isSafeInteger(record.childSequence) || record.childSequence < 0)
79
+ )
80
+ return false;
81
+ if (
82
+ typeof record.key !== "string" ||
83
+ typeof record.startTimeMs !== "number" ||
84
+ !Number.isFinite(record.startTimeMs)
85
+ )
86
+ return false;
87
+ if (record.reference.traceFlags !== 0 && record.reference.traceFlags !== 1) return false;
88
+ if (
89
+ record.reference.tracestate !== undefined &&
90
+ (typeof record.reference.tracestate !== "string" || record.reference.tracestate.length > 512)
91
+ )
92
+ return false;
93
+ if (
94
+ record.parent !== undefined &&
95
+ (!/^[a-f0-9]{32}$/u.test(record.parent.traceId) ||
96
+ !/^[a-f0-9]{16}$/u.test(record.parent.spanId))
97
+ )
98
+ return false;
99
+ if (
100
+ ![record.identity.conversationId, record.identity.runId, record.identity.turnId].every(
101
+ (v) => typeof v === "string",
102
+ )
103
+ )
104
+ return false;
105
+ const options = record.data.options as Record<string, unknown>;
106
+ const fields = {
107
+ activation: [],
108
+ step: [],
109
+ model: ["provider", "modelId"],
110
+ tool: ["callId", "name"],
111
+ approval: ["requestId", "callId", "toolName"],
112
+ memory: ["operation", "phase", "slot", "storeId"],
113
+ };
114
+ if (!fields[record.data.type].every((key) => typeof options[key] === "string")) return false;
115
+ if (record.data.type === "activation" && !Number.isSafeInteger(options.sequence)) return false;
116
+ if (record.data.type === "step" && !Number.isSafeInteger(options.index)) return false;
117
+ if (
118
+ record.data.type === "memory" &&
119
+ options.operation !== "search_memory" &&
120
+ options.operation !== "upsert_memory"
121
+ )
122
+ return false;
123
+ if (
124
+ !/^[a-f0-9]{32}$/u.test(record.reference.traceId) ||
125
+ !/^[a-f0-9]{16}$/u.test(record.reference.spanId) ||
126
+ typeof record.reference.traceFlags !== "number"
127
+ )
128
+ return false;
129
+ if (
130
+ ![record.capture.emit, record.capture.recordInputs, record.capture.recordOutputs].every(
131
+ (v) => typeof v === "boolean",
132
+ )
133
+ )
134
+ return false;
135
+ if (
136
+ !["activation", "step", "model", "tool", "approval", "memory"].includes(record.data.type) ||
137
+ typeof record.data.options !== "object" ||
138
+ record.data.options === null
139
+ )
140
+ return false;
141
+ if (nodeBytes(record) > maxBytes) return false;
142
+ return true;
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ export function snapshotRecord(record: ScopeRecord, serializer: ContentSerializer): ScopeRecord {
149
+ const terminal = record.terminal;
150
+ record = {
151
+ ...record,
152
+ children: record.children?.map((child) =>
153
+ snapshotRecord(
154
+ {
155
+ ...child,
156
+ capture: {
157
+ emit: child.capture.emit && record.capture.emit,
158
+ recordInputs: child.capture.recordInputs && record.capture.recordInputs,
159
+ recordOutputs: child.capture.recordOutputs && record.capture.recordOutputs,
160
+ },
161
+ },
162
+ serializer,
163
+ ),
164
+ ),
165
+ };
166
+ record = {
167
+ ...record,
168
+ terminal:
169
+ terminal === undefined || record.finished
170
+ ? undefined
171
+ : {
172
+ ...terminal,
173
+ errorCode:
174
+ terminal.errorCode ??
175
+ (terminal.error instanceof Error ? terminal.error.name : undefined),
176
+ error: record.capture.recordOutputs
177
+ ? terminal.error instanceof Error
178
+ ? { name: terminal.error.name, message: serializer.text(terminal.error.message) }
179
+ : terminal.error
180
+ : undefined,
181
+ output: record.capture.recordOutputs ? terminal.output : undefined,
182
+ response: record.capture.recordOutputs ? terminal.response : undefined,
183
+ records: record.capture.recordInputs ? terminal.records : undefined,
184
+ model:
185
+ terminal.model === undefined
186
+ ? undefined
187
+ : {
188
+ ...terminal.model,
189
+ content: record.capture.recordOutputs ? terminal.model.content : undefined,
190
+ },
191
+ },
192
+ };
193
+ record = {
194
+ ...record,
195
+ attributes: (withoutDeclinedContent(record.attributes ?? {}, record.capture) ??
196
+ record.attributes) as ScopeRecord["attributes"],
197
+ };
198
+ const data = capturedScopeData(record.data, record.capture);
199
+ let json: string | undefined;
200
+ try {
201
+ json = serializer.json(data);
202
+ } catch {}
203
+ const fallback = () => {
204
+ let structural = capturedScopeData(data, {
205
+ emit: record.capture.emit,
206
+ recordInputs: false,
207
+ recordOutputs: false,
208
+ });
209
+ if (structural.type === "step")
210
+ structural = { ...structural, options: { ...structural.options, runtimeContext: undefined } };
211
+ if (structural.type === "model")
212
+ structural = { ...structural, options: { ...structural.options, runtimeContext: undefined } };
213
+ return JSON.parse(
214
+ JSON.stringify({
215
+ ...record,
216
+ version: 1,
217
+ data: structural,
218
+ terminal:
219
+ record.terminal === undefined
220
+ ? undefined
221
+ : {
222
+ outcome: record.terminal.outcome,
223
+ failed: record.terminal.failed,
224
+ errorCode: record.terminal.errorCode,
225
+ usage: record.terminal.usage,
226
+ },
227
+ attributes:
228
+ withoutDeclinedContent(record.attributes ?? {}, {
229
+ recordInputs: false,
230
+ recordOutputs: false,
231
+ }) ?? record.attributes,
232
+ }),
233
+ ) as ScopeRecord;
234
+ };
235
+ if (json === undefined || new TextEncoder().encode(json).length > SERIALIZED_BYTES)
236
+ return fallback();
237
+ try {
238
+ const snapshot = JSON.parse(
239
+ JSON.stringify({ ...record, version: 1, data: JSON.parse(json) }),
240
+ ) as ScopeRecord;
241
+ return nodeBytes(snapshot) <= SNAPSHOT_BYTES ? snapshot : fallback();
242
+ } catch {
243
+ return fallback();
244
+ }
245
+ }
@@ -0,0 +1,406 @@
1
+ import {
2
+ identityAttributes,
3
+ usageAttributes,
4
+ frameworkAttributes,
5
+ namingAttributes,
6
+ runtimeContextAttributes,
7
+ } from "./attributes.js";
8
+ export const invocationName = (name?: string) =>
9
+ name === undefined ? "invoke_agent" : `invoke_agent ${name}`;
10
+ const modelName = (name: string) => `chat ${name}`;
11
+ const toolName = (name: string) => `execute_tool ${name}`;
12
+ const SPAN_NAMES = {
13
+ approval: "agent.approval",
14
+ step: "agent.step",
15
+ } as const;
16
+ const CONTENT_FIELDS = {
17
+ toolArguments: "gen_ai.tool.call.arguments",
18
+ toolResult: "gen_ai.tool.call.result",
19
+ approvalRequest: "agent.approval.request",
20
+ approvalResponse: "agent.approval.response",
21
+ memoryRecords: "gen_ai.memory.records",
22
+ } as const;
23
+ export function applyAttributes(
24
+ span: { setAttribute(key: string, value: Exclude<Attributes[string], undefined>): unknown },
25
+ attributes: Attributes,
26
+ ): void {
27
+ for (const [key, value] of Object.entries(attributes))
28
+ if (value !== undefined) span.setAttribute(key, value);
29
+ }
30
+ import type { ContentSerializer, ModelResult } from "./types.js";
31
+ import { withoutDeclinedContent } from "./content-policy.js";
32
+ import type { ScopeData, ScopeIdentity, ScopeRecord, ScopeTerminal } from "./types.js";
33
+ import type { Attributes, CaptureDecision, PreparedSpan } from "./types.js";
34
+ import type { TraceOperation } from "./writer.js";
35
+
36
+ interface KindContext {
37
+ identity: ScopeIdentity;
38
+ attributes: Attributes;
39
+ attempt: { turnId: string; index: number; attempt: number };
40
+ capture: CaptureDecision;
41
+ serializer: ContentSerializer;
42
+ }
43
+ type Kind<K extends ScopeData["type"]> = {
44
+ parents: readonly ScopeData["type"][];
45
+ prepare(
46
+ data: Extract<ScopeData, { type: K }>,
47
+ context: KindContext,
48
+ ): Pick<PreparedSpan, "name" | "kind" | "attributes">;
49
+ complete?(
50
+ span: TraceOperation,
51
+ data: Extract<ScopeData, { type: K }>,
52
+ result: ScopeTerminal,
53
+ context: CompletionContext,
54
+ ): void;
55
+ redact?(data: Extract<ScopeData, { type: K }>, capture: CaptureDecision): ScopeData;
56
+ };
57
+ interface CompletionContext {
58
+ capture: CaptureDecision;
59
+ serializer: ContentSerializer;
60
+ outcome: string;
61
+ startTimeMs: number;
62
+ }
63
+ function attemptAttributes(context: KindContext): Attributes {
64
+ return {
65
+ "agent.turn.id": context.attempt.turnId,
66
+ "agent.step.index": context.attempt.index,
67
+ "agent.step.attempt": context.attempt.attempt,
68
+ };
69
+ }
70
+ function permitted(
71
+ attributes: Attributes | undefined,
72
+ capture: CaptureDecision,
73
+ ): Attributes | undefined {
74
+ return (withoutDeclinedContent(attributes ?? {}, capture) ?? attributes) as
75
+ | Attributes
76
+ | undefined;
77
+ }
78
+ function toolInput(arguments_: unknown, context: KindContext): Attributes {
79
+ return {
80
+ [CONTENT_FIELDS.toolArguments]: context.capture.recordInputs
81
+ ? context.serializer.json(arguments_)
82
+ : undefined,
83
+ };
84
+ }
85
+ function toolOutput(span: TraceOperation, result: ScopeTerminal, context: CompletionContext): void {
86
+ if (context.capture.recordOutputs)
87
+ applyAttributes(span, { [CONTENT_FIELDS.toolResult]: context.serializer.json(result.output) });
88
+ }
89
+ const kinds: { [K in ScopeData["type"]]: Kind<K> } = {
90
+ activation: {
91
+ parents: [],
92
+ prepare({ options }, context) {
93
+ const { identity, capture } = context;
94
+ return {
95
+ name: invocationName(identity.agentName),
96
+ attributes: {
97
+ ...context.attributes,
98
+ ...frameworkAttributes(identity.framework),
99
+ ...namingAttributes(invocationName(identity.agentName), "invoke_agent"),
100
+ "agent.channel.kind": options.channel?.kind,
101
+ "agent.session.origin": options.channel?.origin,
102
+ "agent.name": identity.agentName,
103
+ "gen_ai.agent.name": identity.agentName,
104
+ "gen_ai.operation.name": "invoke_agent",
105
+ "agent.turn.id": identity.turnId,
106
+ "agent.turn.sequence": options.sequence,
107
+ "agent.run.type": options.subagent ? "subagent" : "session",
108
+ "agent.parent_call.id": options.parentCallId,
109
+ "agent.parent_run.id": options.parentRunId,
110
+ "agent.subagent.name": options.subagentName,
111
+ "agent.trace.content.input": capture.recordInputs,
112
+ "agent.trace.content.output": capture.recordOutputs,
113
+ ...permitted(options.attributes, capture),
114
+ },
115
+ };
116
+ },
117
+ complete(span, _data, result, context) {
118
+ span.addEvent("turn.started", undefined, context.startTimeMs);
119
+ if (result.outcomeUnknown !== true || result.failed === true)
120
+ span.addEvent(`turn.${context.outcome}`, undefined, result.endTimeMs);
121
+ },
122
+ redact(data, capture) {
123
+ return {
124
+ ...data,
125
+ options: { ...data.options, attributes: permitted(data.options.attributes, capture) },
126
+ };
127
+ },
128
+ },
129
+ step: {
130
+ parents: ["activation"],
131
+ prepare({ options }, context) {
132
+ return {
133
+ name: SPAN_NAMES.step,
134
+ attributes: {
135
+ ...context.attributes,
136
+ ...frameworkAttributes(context.identity.framework),
137
+ ...attemptAttributes(context),
138
+ ...namingAttributes(SPAN_NAMES.step, "workflow"),
139
+ "gen_ai.operation.name": "workflow",
140
+ "agent.name": context.identity.agentName,
141
+ "agent.channel.kind":
142
+ options.channel?.kind === "unknown" ? undefined : options.channel?.kind,
143
+ "agent.session.origin":
144
+ options.channel?.kind === "unknown" ? undefined : options.channel?.origin,
145
+ ...runtimeContextAttributes(options.runtimeContext),
146
+ },
147
+ };
148
+ },
149
+ complete(span, _data, result) {
150
+ span.addEvent(result.failed ? "step.failed" : "step.completed", undefined, result.endTimeMs);
151
+ },
152
+ },
153
+ model: {
154
+ parents: ["step"],
155
+ prepare({ options }, context) {
156
+ const inputMessages =
157
+ context.capture.recordInputs && options.messages !== undefined
158
+ ? context.serializer.inputMessages(options.messages)
159
+ : undefined;
160
+ return {
161
+ name: modelName(options.modelId),
162
+ kind: "CLIENT",
163
+ attributes: {
164
+ ...context.attributes,
165
+ ...namingAttributes(modelName(options.modelId), "chat"),
166
+ "gen_ai.agent.name": context.identity.agentName,
167
+ "gen_ai.operation.name": "chat",
168
+ "gen_ai.provider.name": options.provider,
169
+ "gen_ai.request.model": options.modelId,
170
+ ...runtimeContextAttributes(options.runtimeContext),
171
+ ...(context.capture.recordInputs && options.messages !== undefined
172
+ ? {
173
+ "gen_ai.input.messages": inputMessages?.json,
174
+ "agent.input.messages.omitted": inputMessages?.omitted || undefined,
175
+ "gen_ai.system_instructions": context.serializer.instructions(options.instructions),
176
+ }
177
+ : undefined),
178
+ "gen_ai.tool.definitions": context.capture.recordInputs
179
+ ? context.serializer.toolDefinitions(options.tools)
180
+ : undefined,
181
+ },
182
+ };
183
+ },
184
+ complete(span, _data, result, context) {
185
+ if (result.model !== undefined)
186
+ applyAttributes(
187
+ span,
188
+ modelResultAttributes(result.model, context.serializer, context.capture.recordOutputs),
189
+ );
190
+ },
191
+ redact(data, capture) {
192
+ return capture.recordInputs
193
+ ? data
194
+ : {
195
+ ...data,
196
+ options: {
197
+ ...data.options,
198
+ messages: undefined,
199
+ instructions: undefined,
200
+ tools: undefined,
201
+ },
202
+ };
203
+ },
204
+ },
205
+ tool: {
206
+ parents: ["step", "tool"],
207
+ prepare({ options }, context) {
208
+ const delegates = options.kind === "subagent-call" || options.kind === "remote-agent-call";
209
+ return {
210
+ name: toolName(options.name),
211
+ kind: options.kind === "remote-agent-call" ? "CLIENT" : "INTERNAL",
212
+ attributes: {
213
+ ...context.attributes,
214
+ ...frameworkAttributes(context.identity.framework),
215
+ ...attemptAttributes(context),
216
+ ...namingAttributes(toolName(options.name), "execute_tool"),
217
+ "gen_ai.agent.name": delegates ? options.name : context.identity.agentName,
218
+ "gen_ai.operation.name": "execute_tool",
219
+ "gen_ai.tool.call.id": options.callId,
220
+ "gen_ai.tool.name": options.name,
221
+ "gen_ai.tool.type": "function",
222
+ "agent.action.kind": options.kind,
223
+ "agent.action.name": options.kind === undefined ? undefined : options.name,
224
+ "agent.invocation.role": delegates ? "caller" : undefined,
225
+ ...toolInput(options.arguments, context),
226
+ },
227
+ };
228
+ },
229
+ complete(span, _data, result, context) {
230
+ if (result.outcome !== undefined)
231
+ applyAttributes(span, { "agent.action.outcome": result.outcome });
232
+ toolOutput(span, result, context);
233
+ },
234
+ redact(data, capture) {
235
+ return capture.recordInputs
236
+ ? data
237
+ : { ...data, options: { ...data.options, arguments: undefined } };
238
+ },
239
+ },
240
+ approval: {
241
+ parents: ["tool"],
242
+ prepare({ options }, context) {
243
+ return {
244
+ name: SPAN_NAMES.approval,
245
+ attributes: {
246
+ ...context.attributes,
247
+ ...frameworkAttributes(context.identity.framework),
248
+ ...attemptAttributes(context),
249
+ ...namingAttributes(SPAN_NAMES.approval, "workflow"),
250
+ "gen_ai.operation.name": "workflow",
251
+ "gen_ai.tool.call.id": options.callId,
252
+ "agent.action.name": options.toolName,
253
+ "agent.approval.kind": "tool-approval",
254
+ "agent.approval.request_id": options.requestId,
255
+ [CONTENT_FIELDS.approvalRequest]: context.capture.recordInputs
256
+ ? context.serializer.json(options.request)
257
+ : undefined,
258
+ },
259
+ };
260
+ },
261
+ complete(span, _data, result, context) {
262
+ if (context.capture.recordOutputs)
263
+ applyAttributes(span, {
264
+ [CONTENT_FIELDS.approvalResponse]: context.serializer.json(result.response),
265
+ });
266
+ },
267
+ redact(data, capture) {
268
+ return capture.recordInputs
269
+ ? data
270
+ : { ...data, options: { ...data.options, request: undefined } };
271
+ },
272
+ },
273
+ memory: {
274
+ parents: ["activation", "step", "tool"],
275
+ prepare({ options }, context) {
276
+ return {
277
+ name: options.operation,
278
+ kind: "CLIENT",
279
+ attributes: {
280
+ ...context.attributes,
281
+ ...namingAttributes(options.operation),
282
+ "gen_ai.operation.name": options.operation,
283
+ "gen_ai.memory.store.id": options.storeId,
284
+ "agent.memory.phase": options.phase,
285
+ "agent.memory.slot": options.slot,
286
+ "agent.turn.id": context.identity.turnId,
287
+ },
288
+ };
289
+ },
290
+ complete(span, _data, result, context) {
291
+ if (result.recordCount !== undefined)
292
+ applyAttributes(span, { "gen_ai.memory.record.count": result.recordCount });
293
+ if (context.capture.recordInputs)
294
+ applyAttributes(span, {
295
+ [CONTENT_FIELDS.memoryRecords]: context.serializer.json(result.records),
296
+ });
297
+ },
298
+ },
299
+ };
300
+ function kind<K extends ScopeData["type"]>(data: Extract<ScopeData, { type: K }>): Kind<K> {
301
+ return kinds[data.type] as Kind<K>;
302
+ }
303
+ export function parentKinds(data: ScopeData): readonly ScopeData["type"][] {
304
+ return kind(data).parents;
305
+ }
306
+ export function prepareScope(
307
+ record: Omit<ScopeRecord, "reference">,
308
+ serializer: ContentSerializer,
309
+ ): PreparedSpan {
310
+ const context: KindContext = {
311
+ identity: record.identity,
312
+ attributes: identityAttributes(record.identity),
313
+ attempt: {
314
+ turnId: record.identity.turnId,
315
+ index: record.attempt?.index ?? 0,
316
+ attempt: record.attempt?.attempt ?? 0,
317
+ },
318
+ capture: record.capture,
319
+ serializer,
320
+ };
321
+ return {
322
+ type: record.data.type,
323
+ operationId: record.key,
324
+ kind: "INTERNAL",
325
+ ...kind(record.data).prepare(record.data, context),
326
+ parent: record.parent,
327
+ root: record.data.type === "activation" && record.parent === undefined,
328
+ startTimeMs: record.startTimeMs,
329
+ links: record.links,
330
+ };
331
+ }
332
+ export function completeScope(
333
+ span: TraceOperation,
334
+ data: ScopeData,
335
+ result: ScopeTerminal,
336
+ capture: CaptureDecision,
337
+ startTimeMs: number,
338
+ serializer: ContentSerializer,
339
+ ): void {
340
+ const outcome = result.outcome ?? (result.failed ? "failed" : "completed");
341
+ // A failure is always recorded; otherwise an unknown outcome leaves the span without one.
342
+ const known = result.outcomeUnknown !== true || result.failed === true;
343
+ if ((data.type === "activation" || data.type === "approval") && known)
344
+ applyAttributes(span, {
345
+ [data.type === "activation" ? "agent.turn.outcome" : "agent.approval.outcome"]: outcome,
346
+ });
347
+ if (result.usage !== undefined)
348
+ applyAttributes(
349
+ span,
350
+ usageAttributes(result.usage, data.type === "activation" || data.type === "model"),
351
+ );
352
+ kind(data).complete?.(span, data, result, { capture, startTimeMs, serializer, outcome });
353
+ if (result.failed) span.fail(result.error, result.errorCode);
354
+ }
355
+ export function capturedScopeData(data: ScopeData, capture: CaptureDecision): ScopeData {
356
+ if (!capture.emit) capture = { emit: false, recordInputs: false, recordOutputs: false };
357
+ return kind(data).redact?.(data, capture) ?? data;
358
+ }
359
+ function modelResultAttributes(
360
+ input: ModelResult,
361
+ serializer: ContentSerializer,
362
+ recordOutputs: boolean,
363
+ ): Attributes {
364
+ const attributes: Record<string, Attributes[string]> = {
365
+ ...usageAttributes(input.usage, true),
366
+ "gen_ai.response.id": input.responseId,
367
+ "gen_ai.response.model": input.responseModelId,
368
+ "gen_ai.response.finish_reasons": [input.finishReason],
369
+ };
370
+ if (!recordOutputs) return attributes;
371
+ attributes["ai.response.finish_reason"] = input.finishReason;
372
+ const content = input.content;
373
+ if (content === undefined) return attributes;
374
+ attributes["gen_ai.output.messages"] = serializer.outputMessages(content, input.finishReason);
375
+ attributes["ai.response.reasoning"] = serializer.text(
376
+ content
377
+ .filter((part) => part.type === "reasoning")
378
+ .map((part) => part.text)
379
+ .filter((text) => text.trim().length > 0)
380
+ .join("\n"),
381
+ );
382
+ attributes["ai.response.text"] = serializer.text(
383
+ content
384
+ .filter((part) => part.type === "text")
385
+ .map((part) => part.text)
386
+ .join(""),
387
+ );
388
+ const calls = content
389
+ .filter((part) => part.type === "tool-call")
390
+ .map((part) => ({ callId: part.callId, input: part.input, toolName: part.toolName }));
391
+ if (calls.length > 0) attributes["ai.response.tool_calls"] = serializer.json(calls);
392
+ const results = content
393
+ .filter((part) => part.type === "tool-result" || part.type === "tool-error")
394
+ .map((part) =>
395
+ part.type === "tool-result"
396
+ ? { callId: part.callId, input: part.input, output: part.output, toolName: part.toolName }
397
+ : {
398
+ callId: part.callId,
399
+ input: part.input,
400
+ error: part.error instanceof Error ? part.error.message : part.error,
401
+ toolName: part.toolName,
402
+ },
403
+ );
404
+ if (results.length > 0) attributes["ai.response.tool_results"] = serializer.toolResults(results);
405
+ return attributes;
406
+ }