@tangle-network/sdk-telemetry 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +31 -24
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +290 -410
- package/dist/index.mjs.map +1 -1
- package/dist/server-timing.d.mts +4 -1
- package/dist/server-timing.d.mts.map +1 -1
- package/dist/server-timing.mjs +33 -1
- package/dist/server-timing.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -75,6 +75,272 @@ function parseJsonBlob(blob) {
|
|
|
75
75
|
return context;
|
|
76
76
|
}
|
|
77
77
|
//#endregion
|
|
78
|
+
//#region src/trace-span-projection.ts
|
|
79
|
+
const GEN_AI_PROMOTED_KEYS = Object.freeze([
|
|
80
|
+
GEN_AI_REQUEST_MODEL$1,
|
|
81
|
+
GEN_AI_RESPONSE_MODEL,
|
|
82
|
+
GEN_AI_USAGE_INPUT_TOKENS$1,
|
|
83
|
+
GEN_AI_USAGE_OUTPUT_TOKENS$1,
|
|
84
|
+
GEN_AI_OPERATION_NAME,
|
|
85
|
+
GEN_AI_CONVERSATION_ID$1
|
|
86
|
+
]);
|
|
87
|
+
function addPartUpdateAttributes(attrs, event, includePayloads) {
|
|
88
|
+
attrs["message.part.id"] = event.part.id;
|
|
89
|
+
attrs["message.part.type"] = event.part.type;
|
|
90
|
+
if (isToolPart$1(event.part)) {
|
|
91
|
+
attrs["tool.name"] = event.part.tool;
|
|
92
|
+
const status = event.part.state.status;
|
|
93
|
+
attrs["tool.status"] = status;
|
|
94
|
+
if (includePayloads && event.part.state.input) attrs["tool.input"] = JSON.stringify(event.part.state.input).slice(0, 4096);
|
|
95
|
+
if (includePayloads && status === "completed" && event.part.state.output !== void 0) attrs["tool.output"] = JSON.stringify(event.part.state.output).slice(0, 4096);
|
|
96
|
+
if (includePayloads && (status === "error" || status === "failed") && typeof event.part.state.error === "string") attrs["tool.error"] = event.part.state.error.slice(0, 4096);
|
|
97
|
+
} else if (isTextPart(event.part) || isReasoningPart(event.part)) {
|
|
98
|
+
attrs["message.part.text_length"] = event.part.text.length;
|
|
99
|
+
if (includePayloads) attrs["message.part.text"] = event.part.text.slice(0, 4096);
|
|
100
|
+
} else if (isFilePart(event.part)) {
|
|
101
|
+
attrs["file.name"] = event.part.filename;
|
|
102
|
+
if (event.part.mediaType) attrs["file.media_type"] = event.part.mediaType;
|
|
103
|
+
} else {
|
|
104
|
+
attrs["subtask.id"] = event.part.id;
|
|
105
|
+
attrs["subtask.session_id"] = event.part.sessionID;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function addMessageUpdateAttributes(attrs, event, includePayloads) {
|
|
109
|
+
const text = event.text ?? event.finalText;
|
|
110
|
+
if (typeof text === "string") {
|
|
111
|
+
attrs["message.text_length"] = text.length;
|
|
112
|
+
if (includePayloads) attrs["message.text"] = text.slice(0, 4096);
|
|
113
|
+
}
|
|
114
|
+
if (includePayloads && event.tokenUsage !== void 0) attrs["message.token_usage"] = JSON.stringify(event.tokenUsage).slice(0, 4096);
|
|
115
|
+
if (includePayloads && event.timing !== void 0) attrs["message.timing"] = JSON.stringify(event.timing).slice(0, 4096);
|
|
116
|
+
if (includePayloads && event.toolInvocations !== void 0) attrs["message.tool_invocations"] = JSON.stringify(event.toolInvocations).slice(0, 4096);
|
|
117
|
+
if (event.metadata) for (const key of GEN_AI_PROMOTED_KEYS) {
|
|
118
|
+
const value = event.metadata[key];
|
|
119
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") attrs[key] = value;
|
|
120
|
+
}
|
|
121
|
+
if (includePayloads && event.metadata !== void 0) attrs["message.metadata"] = JSON.stringify(event.metadata).slice(0, 4096);
|
|
122
|
+
}
|
|
123
|
+
function addCustomEventAttributes(attrs, event) {
|
|
124
|
+
attrs["custom.name"] = event.name;
|
|
125
|
+
if (event.name === "startup.phase") {
|
|
126
|
+
attrs["startup.id"] = typeof event.data.startupId === "string" ? event.data.startupId : void 0;
|
|
127
|
+
attrs["startup.phase"] = typeof event.data.phase === "string" ? event.data.phase : void 0;
|
|
128
|
+
attrs["startup.service"] = typeof event.data.service === "string" ? event.data.service : void 0;
|
|
129
|
+
attrs["startup.status"] = typeof event.data.status === "string" ? event.data.status : void 0;
|
|
130
|
+
}
|
|
131
|
+
for (const [key, value] of Object.entries(event.data)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") attrs[`custom.data.${key}`] = value;
|
|
132
|
+
}
|
|
133
|
+
/** Preserve JSON-valued event fields in the hosted Intelligence attribute bag. */
|
|
134
|
+
function flattenTraceMetadata(data) {
|
|
135
|
+
const attributes = {};
|
|
136
|
+
for (const [key, value] of Object.entries(data ?? {})) {
|
|
137
|
+
if (value === null || value === void 0) continue;
|
|
138
|
+
attributes[key] = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value);
|
|
139
|
+
}
|
|
140
|
+
return attributes;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* ExecutionTrace event interpretation has one owner. Formats deliberately retain
|
|
144
|
+
* their existing names, payload policies and timing rather than changing stored
|
|
145
|
+
* trace schemas. Signals, transport, redaction and native writes stay in sinks.
|
|
146
|
+
*/
|
|
147
|
+
function projectTraceEvent(event, format, includePayloads = false) {
|
|
148
|
+
if (format === "intelligence") {
|
|
149
|
+
const { timestamp, type, ...rest } = event;
|
|
150
|
+
return {
|
|
151
|
+
kind: "span",
|
|
152
|
+
name: type,
|
|
153
|
+
startTime: timestamp,
|
|
154
|
+
attributes: {
|
|
155
|
+
"tangle.event.type": type,
|
|
156
|
+
...flattenTraceMetadata(rest)
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const otel = format === "otel";
|
|
161
|
+
const attrs = { "event.type": event.type };
|
|
162
|
+
const projection = {
|
|
163
|
+
kind: otel ? "span" : "event",
|
|
164
|
+
name: "unknown",
|
|
165
|
+
startTime: event.timestamp,
|
|
166
|
+
...otel && { endTime: "durationMs" in event && typeof event.durationMs === "number" ? new Date(event.timestamp.getTime() + event.durationMs) : event.timestamp }
|
|
167
|
+
};
|
|
168
|
+
switch (event.type) {
|
|
169
|
+
case "message.part.updated": {
|
|
170
|
+
const part = event.part;
|
|
171
|
+
projection.metadata = {
|
|
172
|
+
partId: part.id,
|
|
173
|
+
sessionID: part.sessionID,
|
|
174
|
+
messageID: part.messageID
|
|
175
|
+
};
|
|
176
|
+
if (isToolPart$1(part)) {
|
|
177
|
+
const status = part.state.status;
|
|
178
|
+
const failed = status === "error" || status === "failed";
|
|
179
|
+
projection.name = `tool.${part.tool}.${otel ? status : "result"}`;
|
|
180
|
+
if (otel) {
|
|
181
|
+
if (failed) {
|
|
182
|
+
projection.error = typeof part.state.error === "string" ? part.state.error : "Tool execution failed";
|
|
183
|
+
projection.exception = true;
|
|
184
|
+
}
|
|
185
|
+
} else if (status === "running") {
|
|
186
|
+
projection.kind = "span";
|
|
187
|
+
projection.name = `tool.${part.tool}`;
|
|
188
|
+
projection.input = part.state.input ?? { callID: part.callID };
|
|
189
|
+
projection.metadata.status = status;
|
|
190
|
+
} else if (status === "completed" || failed) {
|
|
191
|
+
projection.kind = "span";
|
|
192
|
+
projection.startTime = part.state.time?.start ? new Date(part.state.time.start) : event.timestamp;
|
|
193
|
+
projection.endTime = part.state.time?.end ? new Date(part.state.time.end) : event.timestamp;
|
|
194
|
+
projection.input = part.state.input;
|
|
195
|
+
projection.output = failed ? part.state.output ?? part.state.error : part.state.output ?? "success";
|
|
196
|
+
projection.metadata.callID = part.callID;
|
|
197
|
+
projection.metadata.success = !failed;
|
|
198
|
+
if (failed) {
|
|
199
|
+
projection.level = "ERROR";
|
|
200
|
+
projection.statusMessage = part.state.error;
|
|
201
|
+
}
|
|
202
|
+
} else return void 0;
|
|
203
|
+
} else if (isTextPart(part) || isReasoningPart(part)) {
|
|
204
|
+
projection.name = `${otel ? "llm." : ""}${part.type}`;
|
|
205
|
+
if (!otel) {
|
|
206
|
+
projection.kind = "generation";
|
|
207
|
+
projection.output = part.text;
|
|
208
|
+
projection.metadata.delta = event.delta;
|
|
209
|
+
}
|
|
210
|
+
} else if (isFilePart(part)) {
|
|
211
|
+
projection.name = otel ? "message.file" : `file.${part.mediaType || "unknown"}`;
|
|
212
|
+
projection.metadata = {
|
|
213
|
+
partId: part.id,
|
|
214
|
+
filename: part.filename,
|
|
215
|
+
mediaType: part.mediaType,
|
|
216
|
+
url: part.url
|
|
217
|
+
};
|
|
218
|
+
} else if (!otel) return void 0;
|
|
219
|
+
else projection.name = "message.file";
|
|
220
|
+
if (otel) addPartUpdateAttributes(attrs, event, includePayloads);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "message.updated":
|
|
224
|
+
projection.name = "message.updated";
|
|
225
|
+
projection.metadata = {
|
|
226
|
+
text: event.text ?? event.finalText,
|
|
227
|
+
finalText: event.finalText,
|
|
228
|
+
tokenUsage: event.tokenUsage,
|
|
229
|
+
timing: event.timing,
|
|
230
|
+
toolInvocations: event.toolInvocations,
|
|
231
|
+
metadata: event.metadata
|
|
232
|
+
};
|
|
233
|
+
if (otel) addMessageUpdateAttributes(attrs, event, includePayloads);
|
|
234
|
+
break;
|
|
235
|
+
case "error":
|
|
236
|
+
projection.name = `error.${event.category}`;
|
|
237
|
+
projection.metadata = {
|
|
238
|
+
message: event.message,
|
|
239
|
+
code: event.code,
|
|
240
|
+
source: event.source
|
|
241
|
+
};
|
|
242
|
+
projection.level = "ERROR";
|
|
243
|
+
projection.error = event.message;
|
|
244
|
+
projection.exception = true;
|
|
245
|
+
attrs["error.category"] = event.category;
|
|
246
|
+
attrs["error.message"] = event.message;
|
|
247
|
+
if (event.code) attrs["error.code"] = event.code;
|
|
248
|
+
if (event.source) attrs["error.source"] = event.source;
|
|
249
|
+
break;
|
|
250
|
+
case "diagnostic":
|
|
251
|
+
projection.name = `diagnostic.${event.language || "unknown"}`;
|
|
252
|
+
projection.metadata = {
|
|
253
|
+
file: event.file,
|
|
254
|
+
errorDelta: event.errorDelta,
|
|
255
|
+
errorsAfter: event.afterSummary.errors,
|
|
256
|
+
warningsAfter: event.afterSummary.warnings
|
|
257
|
+
};
|
|
258
|
+
attrs["diagnostic.file"] = event.file;
|
|
259
|
+
if (event.language) attrs["diagnostic.language"] = event.language;
|
|
260
|
+
attrs["diagnostic.error_delta"] = event.errorDelta;
|
|
261
|
+
attrs["diagnostic.errors_after"] = event.afterSummary.errors;
|
|
262
|
+
attrs["diagnostic.warnings_after"] = event.afterSummary.warnings;
|
|
263
|
+
break;
|
|
264
|
+
case "file_change":
|
|
265
|
+
projection.name = `file.${event.changeType}`;
|
|
266
|
+
projection.metadata = {
|
|
267
|
+
file: event.file,
|
|
268
|
+
linesAdded: event.linesAdded,
|
|
269
|
+
linesRemoved: event.linesRemoved
|
|
270
|
+
};
|
|
271
|
+
attrs["file.path"] = event.file;
|
|
272
|
+
attrs["file.change_type"] = event.changeType;
|
|
273
|
+
if (event.linesAdded) attrs["file.lines_added"] = event.linesAdded;
|
|
274
|
+
if (event.linesRemoved) attrs["file.lines_removed"] = event.linesRemoved;
|
|
275
|
+
break;
|
|
276
|
+
case "build_result":
|
|
277
|
+
case "test_result": {
|
|
278
|
+
const build = event.type === "build_result";
|
|
279
|
+
const prefix = build ? "build" : "test";
|
|
280
|
+
projection.name = `${prefix}.${event.success ? "success" : "failure"}`;
|
|
281
|
+
projection.kind = "span";
|
|
282
|
+
projection.metadata = build ? {
|
|
283
|
+
command: event.command,
|
|
284
|
+
errorCount: event.errorCount,
|
|
285
|
+
warningCount: event.warningCount
|
|
286
|
+
} : {
|
|
287
|
+
command: event.command,
|
|
288
|
+
passed: event.passed,
|
|
289
|
+
failed: event.failed,
|
|
290
|
+
skipped: event.skipped
|
|
291
|
+
};
|
|
292
|
+
if (!otel) {
|
|
293
|
+
projection.endTime = event.durationMs ? new Date(event.timestamp.getTime() + event.durationMs) : void 0;
|
|
294
|
+
projection.output = event.success ? "success" : "failed";
|
|
295
|
+
projection.level = event.success ? void 0 : "ERROR";
|
|
296
|
+
}
|
|
297
|
+
if (!event.success) projection.error = build ? "Build failed" : "Tests failed";
|
|
298
|
+
attrs[`${prefix}.success`] = event.success;
|
|
299
|
+
if (event.command) attrs[`${prefix}.command`] = event.command;
|
|
300
|
+
if (event.durationMs) attrs[`${prefix}.duration_ms`] = event.durationMs;
|
|
301
|
+
const counts = build ? {
|
|
302
|
+
error_count: event.errorCount,
|
|
303
|
+
warning_count: event.warningCount
|
|
304
|
+
} : {
|
|
305
|
+
passed: event.passed,
|
|
306
|
+
failed: event.failed,
|
|
307
|
+
skipped: event.skipped
|
|
308
|
+
};
|
|
309
|
+
for (const [key, value] of Object.entries(counts)) if (value) attrs[`${prefix}.${key}`] = value;
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
case "environment":
|
|
313
|
+
projection.name = "environment.snapshot";
|
|
314
|
+
projection.metadata = {
|
|
315
|
+
workingDir: event.workingDir,
|
|
316
|
+
platform: event.platform
|
|
317
|
+
};
|
|
318
|
+
if (event.workingDir) attrs["env.working_dir"] = event.workingDir;
|
|
319
|
+
if (event.platform) attrs["env.platform"] = event.platform;
|
|
320
|
+
break;
|
|
321
|
+
case "custom":
|
|
322
|
+
projection.name = `custom.${event.name}`;
|
|
323
|
+
projection.metadata = event.data;
|
|
324
|
+
if (event.name === "startup.phase" || !otel && event.name === "http.phase") {
|
|
325
|
+
projection.kind = "span";
|
|
326
|
+
const phase = typeof event.data.phase === "string" ? event.data.phase : "unknown";
|
|
327
|
+
projection.name = `${event.name === "http.phase" ? "http" : "startup"}.${phase}`;
|
|
328
|
+
if (typeof event.data.durationMs === "number") projection.startTime = new Date(event.timestamp.getTime() - event.data.durationMs);
|
|
329
|
+
projection.endTime = event.timestamp;
|
|
330
|
+
if (event.data.status === "error") {
|
|
331
|
+
projection.level = "ERROR";
|
|
332
|
+
projection.error = typeof event.data.error === "string" ? event.data.error : "Startup phase failed";
|
|
333
|
+
}
|
|
334
|
+
if (!otel && typeof event.data.error === "string") projection.statusMessage = event.data.error;
|
|
335
|
+
}
|
|
336
|
+
if (otel) addCustomEventAttributes(attrs, event);
|
|
337
|
+
break;
|
|
338
|
+
default: if (!otel) return void 0;
|
|
339
|
+
}
|
|
340
|
+
if (otel) projection.attributes = attrs;
|
|
341
|
+
return projection;
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
78
344
|
//#region src/langfuse-sink.ts
|
|
79
345
|
/**
|
|
80
346
|
* Langfuse Sink
|
|
@@ -104,54 +370,6 @@ async function getLangfuseClient(config) {
|
|
|
104
370
|
return null;
|
|
105
371
|
}
|
|
106
372
|
}
|
|
107
|
-
function exportBuildResultSpan(trace, event) {
|
|
108
|
-
trace.span({
|
|
109
|
-
name: `build.${event.success ? "success" : "failure"}`,
|
|
110
|
-
startTime: event.timestamp,
|
|
111
|
-
endTime: event.durationMs ? new Date(event.timestamp.getTime() + event.durationMs) : void 0,
|
|
112
|
-
output: event.success ? "success" : "failed",
|
|
113
|
-
metadata: {
|
|
114
|
-
command: event.command,
|
|
115
|
-
errorCount: event.errorCount,
|
|
116
|
-
warningCount: event.warningCount
|
|
117
|
-
},
|
|
118
|
-
level: event.success ? void 0 : "ERROR"
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
function exportTestResultSpan(trace, event) {
|
|
122
|
-
trace.span({
|
|
123
|
-
name: `test.${event.success ? "success" : "failure"}`,
|
|
124
|
-
startTime: event.timestamp,
|
|
125
|
-
endTime: event.durationMs ? new Date(event.timestamp.getTime() + event.durationMs) : void 0,
|
|
126
|
-
output: event.success ? "success" : "failed",
|
|
127
|
-
metadata: {
|
|
128
|
-
command: event.command,
|
|
129
|
-
passed: event.passed,
|
|
130
|
-
failed: event.failed,
|
|
131
|
-
skipped: event.skipped
|
|
132
|
-
},
|
|
133
|
-
level: event.success ? void 0 : "ERROR"
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
/** Measured startup and HTTP phases become timed spans; other custom events stay events. */
|
|
137
|
-
function exportCustomEvent(trace, event) {
|
|
138
|
-
if (event.name === "startup.phase" || event.name === "http.phase") {
|
|
139
|
-
const durationMs = typeof event.data.durationMs === "number" ? event.data.durationMs : 0;
|
|
140
|
-
const phase = typeof event.data.phase === "string" ? event.data.phase : "unknown";
|
|
141
|
-
trace.span({
|
|
142
|
-
name: `${event.name === "http.phase" ? "http" : "startup"}.${phase}`,
|
|
143
|
-
startTime: new Date(event.timestamp.getTime() - durationMs),
|
|
144
|
-
endTime: event.timestamp,
|
|
145
|
-
metadata: event.data,
|
|
146
|
-
level: event.data.status === "error" ? "ERROR" : void 0,
|
|
147
|
-
statusMessage: typeof event.data.error === "string" ? event.data.error : void 0
|
|
148
|
-
});
|
|
149
|
-
} else trace.event({
|
|
150
|
-
name: `custom.${event.name}`,
|
|
151
|
-
startTime: event.timestamp,
|
|
152
|
-
metadata: event.data
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
373
|
/**
|
|
156
374
|
* Langfuse trace exporter using the native SDK.
|
|
157
375
|
*
|
|
@@ -202,162 +420,13 @@ var LangfuseSink = class {
|
|
|
202
420
|
console.warn("[langfuse-sink] Failed to export trace:", err);
|
|
203
421
|
}
|
|
204
422
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (isToolPart$1(part)) this.exportToolPart(trace, partEvent, part);
|
|
213
|
-
else if (isTextPart(part) || isReasoningPart(part)) trace.generation({
|
|
214
|
-
name: part.type === "reasoning" ? "reasoning" : "text",
|
|
215
|
-
startTime: partEvent.timestamp,
|
|
216
|
-
output: part.text,
|
|
217
|
-
metadata: {
|
|
218
|
-
partId: part.id,
|
|
219
|
-
sessionID: part.sessionID,
|
|
220
|
-
messageID: part.messageID,
|
|
221
|
-
delta: partEvent.delta
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
else if (isFilePart(part)) trace.event({
|
|
225
|
-
name: `file.${part.mediaType || "unknown"}`,
|
|
226
|
-
startTime: partEvent.timestamp,
|
|
227
|
-
metadata: {
|
|
228
|
-
partId: part.id,
|
|
229
|
-
filename: part.filename,
|
|
230
|
-
mediaType: part.mediaType,
|
|
231
|
-
url: part.url
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
case "message.updated":
|
|
237
|
-
trace.event({
|
|
238
|
-
name: "message.updated",
|
|
239
|
-
startTime: event.timestamp,
|
|
240
|
-
metadata: {
|
|
241
|
-
text: event.text ?? event.finalText,
|
|
242
|
-
finalText: event.finalText,
|
|
243
|
-
tokenUsage: event.tokenUsage,
|
|
244
|
-
timing: event.timing,
|
|
245
|
-
toolInvocations: event.toolInvocations,
|
|
246
|
-
metadata: event.metadata
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
break;
|
|
250
|
-
case "error":
|
|
251
|
-
trace.event({
|
|
252
|
-
name: `error.${event.category}`,
|
|
253
|
-
startTime: event.timestamp,
|
|
254
|
-
level: "ERROR",
|
|
255
|
-
metadata: {
|
|
256
|
-
message: event.message,
|
|
257
|
-
code: event.code,
|
|
258
|
-
source: event.source
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
break;
|
|
262
|
-
case "file_change":
|
|
263
|
-
trace.event({
|
|
264
|
-
name: `file.${event.changeType}`,
|
|
265
|
-
startTime: event.timestamp,
|
|
266
|
-
metadata: {
|
|
267
|
-
file: event.file,
|
|
268
|
-
linesAdded: event.linesAdded,
|
|
269
|
-
linesRemoved: event.linesRemoved
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
break;
|
|
273
|
-
case "build_result":
|
|
274
|
-
exportBuildResultSpan(trace, event);
|
|
275
|
-
break;
|
|
276
|
-
case "test_result":
|
|
277
|
-
exportTestResultSpan(trace, event);
|
|
278
|
-
break;
|
|
279
|
-
case "diagnostic":
|
|
280
|
-
trace.event({
|
|
281
|
-
name: `diagnostic.${event.language || "unknown"}`,
|
|
282
|
-
startTime: event.timestamp,
|
|
283
|
-
metadata: {
|
|
284
|
-
file: event.file,
|
|
285
|
-
errorDelta: event.errorDelta,
|
|
286
|
-
errorsAfter: event.afterSummary.errors,
|
|
287
|
-
warningsAfter: event.afterSummary.warnings
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
break;
|
|
291
|
-
case "environment":
|
|
292
|
-
trace.event({
|
|
293
|
-
name: "environment.snapshot",
|
|
294
|
-
startTime: event.timestamp,
|
|
295
|
-
metadata: {
|
|
296
|
-
workingDir: event.workingDir,
|
|
297
|
-
platform: event.platform
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
break;
|
|
301
|
-
case "custom":
|
|
302
|
-
exportCustomEvent(trace, event);
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Export a tool part from message.part.updated event.
|
|
308
|
-
* Tool parts have state progression: pending → running → completed/error
|
|
309
|
-
*/
|
|
310
|
-
exportToolPart(trace, event, part) {
|
|
311
|
-
const status = part.state.status;
|
|
312
|
-
const toolName = part.tool;
|
|
313
|
-
if (status === "running") trace.span({
|
|
314
|
-
name: `tool.${toolName}`,
|
|
315
|
-
startTime: event.timestamp,
|
|
316
|
-
input: part.state.input ?? { callID: part.callID },
|
|
317
|
-
metadata: {
|
|
318
|
-
partId: part.id,
|
|
319
|
-
sessionID: part.sessionID,
|
|
320
|
-
messageID: part.messageID,
|
|
321
|
-
status
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
else if (status === "completed") {
|
|
325
|
-
const endTime = part.state.time?.end ? new Date(part.state.time.end) : event.timestamp;
|
|
326
|
-
const startTime = part.state.time?.start ? new Date(part.state.time.start) : event.timestamp;
|
|
327
|
-
trace.span({
|
|
328
|
-
name: `tool.${toolName}.result`,
|
|
329
|
-
startTime,
|
|
330
|
-
endTime,
|
|
331
|
-
input: part.state.input,
|
|
332
|
-
output: part.state.output ?? "success",
|
|
333
|
-
metadata: {
|
|
334
|
-
partId: part.id,
|
|
335
|
-
sessionID: part.sessionID,
|
|
336
|
-
messageID: part.messageID,
|
|
337
|
-
callID: part.callID,
|
|
338
|
-
success: true
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
} else if (status === "failed" || status === "error") {
|
|
342
|
-
const endTime = part.state.time?.end ? new Date(part.state.time.end) : event.timestamp;
|
|
343
|
-
const startTime = part.state.time?.start ? new Date(part.state.time.start) : event.timestamp;
|
|
344
|
-
trace.span({
|
|
345
|
-
name: `tool.${toolName}.result`,
|
|
346
|
-
startTime,
|
|
347
|
-
endTime,
|
|
348
|
-
input: part.state.input,
|
|
349
|
-
output: part.state.output ?? part.state.error,
|
|
350
|
-
metadata: {
|
|
351
|
-
partId: part.id,
|
|
352
|
-
sessionID: part.sessionID,
|
|
353
|
-
messageID: part.messageID,
|
|
354
|
-
callID: part.callID,
|
|
355
|
-
success: false
|
|
356
|
-
},
|
|
357
|
-
level: "ERROR",
|
|
358
|
-
statusMessage: part.state.error
|
|
359
|
-
});
|
|
360
|
-
}
|
|
423
|
+
exportTraceEvent(trace, event) {
|
|
424
|
+
const projected = projectTraceEvent(event, "langfuse");
|
|
425
|
+
if (!projected) return;
|
|
426
|
+
const { kind, attributes: _attributes, error: _error, exception: _exception, ...data } = projected;
|
|
427
|
+
if (kind === "generation") trace.generation(data);
|
|
428
|
+
else if (kind === "span") trace.span(data);
|
|
429
|
+
else trace.event(data);
|
|
361
430
|
}
|
|
362
431
|
async flush() {
|
|
363
432
|
const client = await getLangfuseClient(this.config);
|
|
@@ -464,26 +533,6 @@ async function shutdownOtelProvider() {
|
|
|
464
533
|
* was configured at SDK initialization.
|
|
465
534
|
*/
|
|
466
535
|
/**
|
|
467
|
-
* GenAI semantic-convention keys promoted from a `message.updated` event's
|
|
468
|
-
* metadata bag to DISCRETE span attributes. The intelligence ingest derives
|
|
469
|
-
* model/token/cost and conversation linkage by reading these exact keys off the
|
|
470
|
-
* flat span attribute bag (`parseGenAiAttribution` / `parseConversationLinkage`
|
|
471
|
-
* in the intelligence API); leaving them inside the serialized `message.metadata`
|
|
472
|
-
* blob would attribute every auto-instrumented LLM call as model/tokens/cost
|
|
473
|
-
* unknown. Promotion is unconditional (not gated on `includePayloads`): these
|
|
474
|
-
* are attribution signal, not prompt/completion content. `genAiUsageAttributes`
|
|
475
|
-
* already guarantees the writer never emits a fabricated zero, so a key absent
|
|
476
|
-
* here stays absent on the span — "cost not computed", never "free".
|
|
477
|
-
*/
|
|
478
|
-
const GEN_AI_PROMOTED_KEYS = Object.freeze([
|
|
479
|
-
GEN_AI_REQUEST_MODEL$1,
|
|
480
|
-
GEN_AI_RESPONSE_MODEL,
|
|
481
|
-
GEN_AI_USAGE_INPUT_TOKENS$1,
|
|
482
|
-
GEN_AI_USAGE_OUTPUT_TOKENS$1,
|
|
483
|
-
GEN_AI_OPERATION_NAME,
|
|
484
|
-
GEN_AI_CONVERSATION_ID$1
|
|
485
|
-
]);
|
|
486
|
-
/**
|
|
487
536
|
* Build the parent-span attributes for a trace, with a deliberate precedence:
|
|
488
537
|
* partner-supplied `resourceAttributes` are the LOWEST layer, so the trusted
|
|
489
538
|
* identity attributes (agent.trace.id, session.id, user.id) and metadata always
|
|
@@ -522,52 +571,10 @@ async function getOtelApi() {
|
|
|
522
571
|
}
|
|
523
572
|
return otelApi;
|
|
524
573
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
attrs["tool.name"] = event.part.tool;
|
|
530
|
-
const status = event.part.state.status;
|
|
531
|
-
attrs["tool.status"] = status;
|
|
532
|
-
if (includePayloads && event.part.state.input) attrs["tool.input"] = JSON.stringify(event.part.state.input).slice(0, 4096);
|
|
533
|
-
if (includePayloads && status === "completed" && event.part.state.output !== void 0) attrs["tool.output"] = JSON.stringify(event.part.state.output).slice(0, 4096);
|
|
534
|
-
if (includePayloads && (status === "error" || status === "failed") && typeof event.part.state.error === "string") attrs["tool.error"] = event.part.state.error.slice(0, 4096);
|
|
535
|
-
} else if (isTextPart(event.part) || isReasoningPart(event.part)) {
|
|
536
|
-
attrs["message.part.text_length"] = event.part.text.length;
|
|
537
|
-
if (includePayloads) attrs["message.part.text"] = event.part.text.slice(0, 4096);
|
|
538
|
-
} else if (isFilePart(event.part)) {
|
|
539
|
-
attrs["file.name"] = event.part.filename;
|
|
540
|
-
if (event.part.mediaType) attrs["file.media_type"] = event.part.mediaType;
|
|
541
|
-
} else {
|
|
542
|
-
attrs["subtask.id"] = event.part.id;
|
|
543
|
-
attrs["subtask.session_id"] = event.part.sessionID;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
function addMessageUpdateAttributes(attrs, event, includePayloads) {
|
|
547
|
-
const text = event.text ?? event.finalText;
|
|
548
|
-
if (typeof text === "string") {
|
|
549
|
-
attrs["message.text_length"] = text.length;
|
|
550
|
-
if (includePayloads) attrs["message.text"] = text.slice(0, 4096);
|
|
551
|
-
}
|
|
552
|
-
if (includePayloads && event.tokenUsage !== void 0) attrs["message.token_usage"] = JSON.stringify(event.tokenUsage).slice(0, 4096);
|
|
553
|
-
if (includePayloads && event.timing !== void 0) attrs["message.timing"] = JSON.stringify(event.timing).slice(0, 4096);
|
|
554
|
-
if (includePayloads && event.toolInvocations !== void 0) attrs["message.tool_invocations"] = JSON.stringify(event.toolInvocations).slice(0, 4096);
|
|
555
|
-
if (event.metadata) for (const key of GEN_AI_PROMOTED_KEYS) {
|
|
556
|
-
const value = event.metadata[key];
|
|
557
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") attrs[key] = value;
|
|
558
|
-
}
|
|
559
|
-
if (includePayloads && event.metadata !== void 0) attrs["message.metadata"] = JSON.stringify(event.metadata).slice(0, 4096);
|
|
560
|
-
}
|
|
561
|
-
function addCustomEventAttributes(attrs, event) {
|
|
562
|
-
attrs["custom.name"] = event.name;
|
|
563
|
-
if (event.name === "startup.phase") {
|
|
564
|
-
attrs["startup.id"] = typeof event.data.startupId === "string" ? event.data.startupId : void 0;
|
|
565
|
-
attrs["startup.phase"] = typeof event.data.phase === "string" ? event.data.phase : void 0;
|
|
566
|
-
attrs["startup.service"] = typeof event.data.service === "string" ? event.data.service : void 0;
|
|
567
|
-
attrs["startup.status"] = typeof event.data.status === "string" ? event.data.status : void 0;
|
|
568
|
-
}
|
|
569
|
-
for (const [key, value] of Object.entries(event.data)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") attrs[`custom.data.${key}`] = value;
|
|
570
|
-
}
|
|
574
|
+
/**
|
|
575
|
+
* OpenTelemetry sink — exports via whatever OTEL SDK config was set at
|
|
576
|
+
* application startup (Langfuse, Jaeger, any OTLP backend).
|
|
577
|
+
*/
|
|
571
578
|
var OtelTelemetrySink = class {
|
|
572
579
|
name = "otel";
|
|
573
580
|
config;
|
|
@@ -618,7 +625,18 @@ var OtelTelemetrySink = class {
|
|
|
618
625
|
for (const event of trace.events) {
|
|
619
626
|
if (!event?.type) continue;
|
|
620
627
|
api.context.with(parentContext, () => {
|
|
621
|
-
|
|
628
|
+
const projected = projectTraceEvent(event, "otel", this.config.includePayloads);
|
|
629
|
+
if (!projected) return;
|
|
630
|
+
const span = tracer.startSpan(projected.name, {
|
|
631
|
+
attributes: projected.attributes,
|
|
632
|
+
startTime: projected.startTime
|
|
633
|
+
});
|
|
634
|
+
span.setStatus({
|
|
635
|
+
code: projected.error !== void 0 ? api.SpanStatusCode.ERROR : api.SpanStatusCode.OK,
|
|
636
|
+
...projected.error !== void 0 && { message: projected.error }
|
|
637
|
+
});
|
|
638
|
+
if (projected.exception) span.recordException(new Error(projected.error));
|
|
639
|
+
span.end(projected.endTime);
|
|
622
640
|
});
|
|
623
641
|
}
|
|
624
642
|
for (const signal of trace.signals) traceSpan.addEvent(`signal.${signal.signal}`, {}, signal.timestamp);
|
|
@@ -630,144 +648,6 @@ var OtelTelemetrySink = class {
|
|
|
630
648
|
traceSpan.end(trace.completedAt || /* @__PURE__ */ new Date());
|
|
631
649
|
}
|
|
632
650
|
/**
|
|
633
|
-
* Export a single trace event as an OTEL span.
|
|
634
|
-
*/
|
|
635
|
-
exportTraceEvent(tracer, api, event) {
|
|
636
|
-
const spanName = this.getSpanName(event);
|
|
637
|
-
const attributes = this.getEventAttributes(event);
|
|
638
|
-
const startTime = this.getEventStartTime(event);
|
|
639
|
-
const endTime = this.getEventEndTime(event);
|
|
640
|
-
const span = tracer.startSpan(spanName, {
|
|
641
|
-
attributes,
|
|
642
|
-
startTime
|
|
643
|
-
});
|
|
644
|
-
if (event.type === "error") {
|
|
645
|
-
span.setStatus({
|
|
646
|
-
code: api.SpanStatusCode.ERROR,
|
|
647
|
-
message: event.message
|
|
648
|
-
});
|
|
649
|
-
span.recordException(new Error(event.message));
|
|
650
|
-
} else if (event.type === "message.part.updated" && isToolPart$1(event.part)) {
|
|
651
|
-
const status = event.part.state.status;
|
|
652
|
-
if (status === "failed" || status === "error") {
|
|
653
|
-
const err = typeof event.part.state.error === "string" ? event.part.state.error : "Tool execution failed";
|
|
654
|
-
span.setStatus({
|
|
655
|
-
code: api.SpanStatusCode.ERROR,
|
|
656
|
-
message: err
|
|
657
|
-
});
|
|
658
|
-
span.recordException(new Error(err));
|
|
659
|
-
} else span.setStatus({ code: api.SpanStatusCode.OK });
|
|
660
|
-
} else if (event.type === "build_result" && !event.success) span.setStatus({
|
|
661
|
-
code: api.SpanStatusCode.ERROR,
|
|
662
|
-
message: "Build failed"
|
|
663
|
-
});
|
|
664
|
-
else if (event.type === "test_result" && !event.success) span.setStatus({
|
|
665
|
-
code: api.SpanStatusCode.ERROR,
|
|
666
|
-
message: "Tests failed"
|
|
667
|
-
});
|
|
668
|
-
else if (event.type === "custom" && event.name === "startup.phase" && event.data.status === "error") span.setStatus({
|
|
669
|
-
code: api.SpanStatusCode.ERROR,
|
|
670
|
-
message: typeof event.data.error === "string" ? event.data.error : "Startup phase failed"
|
|
671
|
-
});
|
|
672
|
-
else span.setStatus({ code: api.SpanStatusCode.OK });
|
|
673
|
-
span.end(endTime);
|
|
674
|
-
}
|
|
675
|
-
getEventStartTime(event) {
|
|
676
|
-
if (event.type === "custom" && event.name === "startup.phase") {
|
|
677
|
-
const durationMs = this.getStartupPhaseDurationMs(event);
|
|
678
|
-
if (typeof durationMs === "number") return new Date(event.timestamp.getTime() - durationMs);
|
|
679
|
-
}
|
|
680
|
-
return event.timestamp;
|
|
681
|
-
}
|
|
682
|
-
getEventEndTime(event) {
|
|
683
|
-
if (event.type === "custom" && event.name === "startup.phase") return event.timestamp;
|
|
684
|
-
if ("durationMs" in event && typeof event.durationMs === "number") return new Date(event.timestamp.getTime() + event.durationMs);
|
|
685
|
-
return event.timestamp;
|
|
686
|
-
}
|
|
687
|
-
getStartupPhaseDurationMs(event) {
|
|
688
|
-
return typeof event.data.durationMs === "number" ? event.data.durationMs : void 0;
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Get span name for a trace event.
|
|
692
|
-
*/
|
|
693
|
-
getSpanName(event) {
|
|
694
|
-
switch (event.type) {
|
|
695
|
-
case "message.part.updated":
|
|
696
|
-
if (isToolPart$1(event.part)) return `tool.${event.part.tool}.${event.part.state.status}`;
|
|
697
|
-
if (isReasoningPart(event.part)) return "llm.reasoning";
|
|
698
|
-
if (isTextPart(event.part)) return "llm.text";
|
|
699
|
-
return "message.file";
|
|
700
|
-
case "message.updated": return "message.updated";
|
|
701
|
-
case "error": return `error.${event.category}`;
|
|
702
|
-
case "diagnostic": return `diagnostic.${event.language || "unknown"}`;
|
|
703
|
-
case "file_change": return `file.${event.changeType}`;
|
|
704
|
-
case "build_result": return `build.${event.success ? "success" : "failure"}`;
|
|
705
|
-
case "test_result": return `test.${event.success ? "success" : "failure"}`;
|
|
706
|
-
case "environment": return "environment.snapshot";
|
|
707
|
-
case "custom":
|
|
708
|
-
if (event.name === "startup.phase") return `startup.${typeof event.data.phase === "string" ? event.data.phase : "unknown"}`;
|
|
709
|
-
return `custom.${event.name}`;
|
|
710
|
-
default: return "unknown";
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* Get OTEL attributes for a trace event.
|
|
715
|
-
*/
|
|
716
|
-
getEventAttributes(event) {
|
|
717
|
-
const attrs = { "event.type": event.type };
|
|
718
|
-
switch (event.type) {
|
|
719
|
-
case "message.part.updated":
|
|
720
|
-
addPartUpdateAttributes(attrs, event, this.config.includePayloads);
|
|
721
|
-
break;
|
|
722
|
-
case "message.updated":
|
|
723
|
-
addMessageUpdateAttributes(attrs, event, this.config.includePayloads);
|
|
724
|
-
break;
|
|
725
|
-
case "error":
|
|
726
|
-
attrs["error.category"] = event.category;
|
|
727
|
-
attrs["error.message"] = event.message;
|
|
728
|
-
if (event.code) attrs["error.code"] = event.code;
|
|
729
|
-
if (event.source) attrs["error.source"] = event.source;
|
|
730
|
-
break;
|
|
731
|
-
case "diagnostic":
|
|
732
|
-
attrs["diagnostic.file"] = event.file;
|
|
733
|
-
if (event.language) attrs["diagnostic.language"] = event.language;
|
|
734
|
-
attrs["diagnostic.error_delta"] = event.errorDelta;
|
|
735
|
-
attrs["diagnostic.errors_after"] = event.afterSummary.errors;
|
|
736
|
-
attrs["diagnostic.warnings_after"] = event.afterSummary.warnings;
|
|
737
|
-
break;
|
|
738
|
-
case "file_change":
|
|
739
|
-
attrs["file.path"] = event.file;
|
|
740
|
-
attrs["file.change_type"] = event.changeType;
|
|
741
|
-
if (event.linesAdded) attrs["file.lines_added"] = event.linesAdded;
|
|
742
|
-
if (event.linesRemoved) attrs["file.lines_removed"] = event.linesRemoved;
|
|
743
|
-
break;
|
|
744
|
-
case "build_result":
|
|
745
|
-
attrs["build.success"] = event.success;
|
|
746
|
-
if (event.command) attrs["build.command"] = event.command;
|
|
747
|
-
if (event.durationMs) attrs["build.duration_ms"] = event.durationMs;
|
|
748
|
-
if (event.errorCount) attrs["build.error_count"] = event.errorCount;
|
|
749
|
-
if (event.warningCount) attrs["build.warning_count"] = event.warningCount;
|
|
750
|
-
break;
|
|
751
|
-
case "test_result":
|
|
752
|
-
attrs["test.success"] = event.success;
|
|
753
|
-
if (event.command) attrs["test.command"] = event.command;
|
|
754
|
-
if (event.durationMs) attrs["test.duration_ms"] = event.durationMs;
|
|
755
|
-
if (event.passed) attrs["test.passed"] = event.passed;
|
|
756
|
-
if (event.failed) attrs["test.failed"] = event.failed;
|
|
757
|
-
if (event.skipped) attrs["test.skipped"] = event.skipped;
|
|
758
|
-
break;
|
|
759
|
-
case "environment":
|
|
760
|
-
if (event.workingDir) attrs["env.working_dir"] = event.workingDir;
|
|
761
|
-
if (event.platform) attrs["env.platform"] = event.platform;
|
|
762
|
-
break;
|
|
763
|
-
case "custom":
|
|
764
|
-
attrs["custom.name"] = event.name;
|
|
765
|
-
addCustomEventAttributes(attrs, event);
|
|
766
|
-
break;
|
|
767
|
-
}
|
|
768
|
-
return attrs;
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
651
|
* Flatten metadata object into span attributes
|
|
772
652
|
*/
|
|
773
653
|
flattenMetadata(metadata, prefix = "metadata") {
|
|
@@ -1288,6 +1168,6 @@ function createTraceCollector(config) {
|
|
|
1288
1168
|
return new TraceCollector(config);
|
|
1289
1169
|
}
|
|
1290
1170
|
//#endregion
|
|
1291
|
-
export { GEN_AI_CONVERSATION_ID, GEN_AI_INPUT_TOKEN_KEYS, GEN_AI_MODEL_KEYS, GEN_AI_OUTPUT_TOKEN_KEYS, GEN_AI_REQUEST_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, HttpTelemetrySink, LangfuseSink, MultiTelemetrySink, OtelTelemetrySink, PROVISION_TRACE_ID_HEADER, SSEChunkParser, SpanRecorder, TOKEN_USAGE_INPUT_KEYS, TOKEN_USAGE_OUTPUT_KEYS, TraceCollector, createHttpSink, createLangfuseSinkFromEnv, createOtelSink, createOtelSinkFromEnv, createStreamUsageExtractor, createTraceCollector, firstTokenCount, genAiUsageAttributes, initOtelProvider, isAdoptablePtid, isToolPart, mintPtid, parseMetadataBlob, shutdownOtelProvider, sseToSignal, sseToTraceEvent, tokenCount };
|
|
1171
|
+
export { GEN_AI_CONVERSATION_ID, GEN_AI_INPUT_TOKEN_KEYS, GEN_AI_MODEL_KEYS, GEN_AI_OUTPUT_TOKEN_KEYS, GEN_AI_REQUEST_MODEL, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, HttpTelemetrySink, LangfuseSink, MultiTelemetrySink, OtelTelemetrySink, PROVISION_TRACE_ID_HEADER, SSEChunkParser, SpanRecorder, TOKEN_USAGE_INPUT_KEYS, TOKEN_USAGE_OUTPUT_KEYS, TraceCollector, createHttpSink, createLangfuseSinkFromEnv, createOtelSink, createOtelSinkFromEnv, createStreamUsageExtractor, createTraceCollector, firstTokenCount, flattenTraceMetadata, genAiUsageAttributes, initOtelProvider, isAdoptablePtid, isToolPart, mintPtid, parseMetadataBlob, projectTraceEvent, shutdownOtelProvider, sseToSignal, sseToTraceEvent, tokenCount };
|
|
1292
1172
|
|
|
1293
1173
|
//# sourceMappingURL=index.mjs.map
|