@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.
- package/LICENSE +202 -0
- package/NOTICE +77 -0
- package/README.md +251 -2
- package/dist/src/adapters/agent-transport.d.ts +9 -0
- package/dist/src/adapters/agent-transport.d.ts.map +1 -0
- package/dist/src/adapters/agent-transport.js +87 -0
- package/dist/src/adapters/agent-transport.js.map +1 -0
- package/dist/src/adapters/ai-sdk-payload.d.ts +16 -0
- package/dist/src/adapters/ai-sdk-payload.d.ts.map +1 -0
- package/dist/src/adapters/ai-sdk-payload.js +30 -0
- package/dist/src/adapters/ai-sdk-payload.js.map +1 -0
- package/dist/src/adapters/ai-sdk-telemetry.d.ts +15 -0
- package/dist/src/adapters/ai-sdk-telemetry.d.ts.map +1 -0
- package/dist/src/adapters/ai-sdk-telemetry.js +151 -0
- package/dist/src/adapters/ai-sdk-telemetry.js.map +1 -0
- package/dist/src/adapters/otel-error.d.ts +4 -0
- package/dist/src/adapters/otel-error.d.ts.map +1 -0
- package/dist/src/adapters/otel-error.js +32 -0
- package/dist/src/adapters/otel-error.js.map +1 -0
- package/dist/src/adapters/otel-ids.d.ts +20 -0
- package/dist/src/adapters/otel-ids.d.ts.map +1 -0
- package/dist/src/adapters/otel-ids.js +74 -0
- package/dist/src/adapters/otel-ids.js.map +1 -0
- package/dist/src/adapters/otel.d.ts +34 -0
- package/dist/src/adapters/otel.d.ts.map +1 -0
- package/dist/src/adapters/otel.js +147 -0
- package/dist/src/adapters/otel.js.map +1 -0
- package/dist/src/adapters/serialization.d.ts +37 -0
- package/dist/src/adapters/serialization.d.ts.map +1 -0
- package/dist/src/adapters/serialization.js +318 -0
- package/dist/src/adapters/serialization.js.map +1 -0
- package/dist/src/agent-run.d.ts +85 -0
- package/dist/src/agent-run.d.ts.map +1 -0
- package/dist/src/agent-run.js +213 -0
- package/dist/src/agent-run.js.map +1 -0
- package/dist/src/ai-sdk.d.ts +8 -0
- package/dist/src/ai-sdk.d.ts.map +1 -0
- package/dist/src/ai-sdk.js +8 -0
- package/dist/src/ai-sdk.js.map +1 -0
- package/dist/src/capture.d.ts +6 -0
- package/dist/src/capture.d.ts.map +1 -0
- package/dist/src/capture.js +9 -0
- package/dist/src/capture.js.map +1 -0
- package/dist/src/core/activation.d.ts +4 -0
- package/dist/src/core/activation.d.ts.map +1 -0
- package/dist/src/core/activation.js +31 -0
- package/dist/src/core/activation.js.map +1 -0
- package/dist/src/core/attributes.d.ts +15 -0
- package/dist/src/core/attributes.d.ts.map +1 -0
- package/dist/src/core/attributes.js +76 -0
- package/dist/src/core/attributes.js.map +1 -0
- package/dist/src/core/content-policy.d.ts +29 -0
- package/dist/src/core/content-policy.d.ts.map +1 -0
- package/dist/src/core/content-policy.js +94 -0
- package/dist/src/core/content-policy.js.map +1 -0
- package/dist/src/core/context.d.ts +4 -0
- package/dist/src/core/context.d.ts.map +1 -0
- package/dist/src/core/context.js +28 -0
- package/dist/src/core/context.js.map +1 -0
- package/dist/src/core/delegation.d.ts +14 -0
- package/dist/src/core/delegation.d.ts.map +1 -0
- package/dist/src/core/delegation.js +7 -0
- package/dist/src/core/delegation.js.map +1 -0
- package/dist/src/core/mcp.d.ts +25 -0
- package/dist/src/core/mcp.d.ts.map +1 -0
- package/dist/src/core/mcp.js +33 -0
- package/dist/src/core/mcp.js.map +1 -0
- package/dist/src/core/scopes.d.ts +60 -0
- package/dist/src/core/scopes.d.ts.map +1 -0
- package/dist/src/core/scopes.js +574 -0
- package/dist/src/core/scopes.js.map +1 -0
- package/dist/src/core/snapshot.d.ts +8 -0
- package/dist/src/core/snapshot.d.ts.map +1 -0
- package/dist/src/core/snapshot.js +207 -0
- package/dist/src/core/snapshot.js.map +1 -0
- package/dist/src/core/span-kinds.d.ts +13 -0
- package/dist/src/core/span-kinds.d.ts.map +1 -0
- package/dist/src/core/span-kinds.js +338 -0
- package/dist/src/core/span-kinds.js.map +1 -0
- package/dist/src/core/types.d.ts +298 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +2 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/writer.d.ts +16 -0
- package/dist/src/core/writer.d.ts.map +1 -0
- package/dist/src/core/writer.js +77 -0
- package/dist/src/core/writer.js.map +1 -0
- package/dist/src/delegation.d.ts +3 -0
- package/dist/src/delegation.d.ts.map +1 -0
- package/dist/src/delegation.js +3 -0
- package/dist/src/delegation.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/operations.d.ts +97 -0
- package/dist/src/operations.d.ts.map +1 -0
- package/dist/src/operations.js +185 -0
- package/dist/src/operations.js.map +1 -0
- package/dist/src/otel.d.ts +13 -0
- package/dist/src/otel.d.ts.map +1 -0
- package/dist/src/otel.js +13 -0
- package/dist/src/otel.js.map +1 -0
- package/package.json +75 -3
- package/src/adapters/agent-transport.ts +113 -0
- package/src/adapters/ai-sdk-payload.ts +44 -0
- package/src/adapters/ai-sdk-telemetry.ts +179 -0
- package/src/adapters/otel-error.ts +34 -0
- package/src/adapters/otel-ids.ts +82 -0
- package/src/adapters/otel.ts +232 -0
- package/src/adapters/serialization.ts +338 -0
- package/src/agent-run.ts +341 -0
- package/src/ai-sdk.ts +7 -0
- package/src/capture.ts +13 -0
- package/src/core/activation.ts +42 -0
- package/src/core/attributes.ts +85 -0
- package/src/core/content-policy.ts +116 -0
- package/src/core/context.ts +34 -0
- package/src/core/delegation.ts +20 -0
- package/src/core/mcp.ts +60 -0
- package/src/core/scopes.ts +761 -0
- package/src/core/snapshot.ts +245 -0
- package/src/core/span-kinds.ts +406 -0
- package/src/core/types.ts +304 -0
- package/src/core/writer.ts +135 -0
- package/src/delegation.ts +2 -0
- package/src/index.ts +44 -0
- package/src/operations.ts +342 -0
- 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
|
+
}
|