within-sdk 1.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.
- package/LICENSE +1 -0
- package/README.md +77 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +243 -0
- package/dist/middleware.d.ts +40 -0
- package/dist/middleware.js +562 -0
- package/dist/modules/compatibility.d.ts +18 -0
- package/dist/modules/compatibility.js +99 -0
- package/dist/modules/constants.d.ts +12 -0
- package/dist/modules/constants.js +12 -0
- package/dist/modules/context-parameters.d.ts +13 -0
- package/dist/modules/context-parameters.js +74 -0
- package/dist/modules/diagnostics.d.ts +7 -0
- package/dist/modules/diagnostics.js +12 -0
- package/dist/modules/eventQueue.d.ts +32 -0
- package/dist/modules/eventQueue.js +234 -0
- package/dist/modules/exceptions.d.ts +13 -0
- package/dist/modules/exceptions.js +730 -0
- package/dist/modules/exporters/datadog.d.ts +17 -0
- package/dist/modules/exporters/datadog.js +166 -0
- package/dist/modules/exporters/otlp.d.ts +13 -0
- package/dist/modules/exporters/otlp.js +140 -0
- package/dist/modules/exporters/posthog.d.ts +32 -0
- package/dist/modules/exporters/posthog.js +272 -0
- package/dist/modules/exporters/sentry.d.ts +27 -0
- package/dist/modules/exporters/sentry.js +386 -0
- package/dist/modules/exporters/trace-context.d.ts +8 -0
- package/dist/modules/exporters/trace-context.js +27 -0
- package/dist/modules/index.d.ts +8 -0
- package/dist/modules/index.js +8 -0
- package/dist/modules/internal.d.ts +33 -0
- package/dist/modules/internal.js +195 -0
- package/dist/modules/logging.d.ts +2 -0
- package/dist/modules/logging.js +74 -0
- package/dist/modules/mcp-sdk-compat.d.ts +32 -0
- package/dist/modules/mcp-sdk-compat.js +111 -0
- package/dist/modules/privacy.d.ts +15 -0
- package/dist/modules/privacy.js +179 -0
- package/dist/modules/redaction.d.ts +11 -0
- package/dist/modules/redaction.js +81 -0
- package/dist/modules/sanitization.d.ts +9 -0
- package/dist/modules/sanitization.js +111 -0
- package/dist/modules/session.d.ts +22 -0
- package/dist/modules/session.js +109 -0
- package/dist/modules/telemetry.d.ts +8 -0
- package/dist/modules/telemetry.js +53 -0
- package/dist/modules/tools.d.ts +25 -0
- package/dist/modules/tools.js +119 -0
- package/dist/modules/tracing.d.ts +4 -0
- package/dist/modules/tracing.js +263 -0
- package/dist/modules/tracingV2.d.ts +2 -0
- package/dist/modules/tracingV2.js +300 -0
- package/dist/modules/truncation.d.ts +24 -0
- package/dist/modules/truncation.js +303 -0
- package/dist/modules/validation.d.ts +6 -0
- package/dist/modules/validation.js +51 -0
- package/dist/network.d.ts +83 -0
- package/dist/network.js +411 -0
- package/dist/proxy.d.ts +31 -0
- package/dist/proxy.js +158 -0
- package/dist/thirdparty/ksuid/base-convert-int-array.js +49 -0
- package/dist/thirdparty/ksuid/base62.js +24 -0
- package/dist/thirdparty/ksuid/index.d.ts +30 -0
- package/dist/thirdparty/ksuid/index.js +201 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.js +5 -0
- package/dist/validate.d.ts +53 -0
- package/dist/validate.js +139 -0
- package/package.json +43 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Event, Exporter } from "../../types.js";
|
|
2
|
+
export interface DatadogExporterConfig {
|
|
3
|
+
type: "datadog";
|
|
4
|
+
apiKey: string;
|
|
5
|
+
site: string;
|
|
6
|
+
service: string;
|
|
7
|
+
env?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class DatadogExporter implements Exporter {
|
|
10
|
+
private logsUrl;
|
|
11
|
+
private metricsUrl;
|
|
12
|
+
private config;
|
|
13
|
+
constructor(config: DatadogExporterConfig);
|
|
14
|
+
export(event: Event): Promise<void>;
|
|
15
|
+
private eventToLog;
|
|
16
|
+
private eventToMetrics;
|
|
17
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { writeToLog } from "../logging.js";
|
|
2
|
+
import { traceContext } from "./trace-context.js";
|
|
3
|
+
import { WITHIN_SOURCE } from "../constants.js";
|
|
4
|
+
export class DatadogExporter {
|
|
5
|
+
logsUrl;
|
|
6
|
+
metricsUrl;
|
|
7
|
+
config;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
// Build API endpoints based on site
|
|
11
|
+
const site = config.site.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
12
|
+
this.logsUrl = `https://http-intake.logs.${site}/api/v2/logs`;
|
|
13
|
+
this.metricsUrl = `https://api.${site}/api/v1/series`;
|
|
14
|
+
}
|
|
15
|
+
async export(event) {
|
|
16
|
+
writeToLog("DatadogExporter: Sending event immediately to Datadog");
|
|
17
|
+
// Convert event to log and metrics
|
|
18
|
+
const log = this.eventToLog(event);
|
|
19
|
+
const metrics = this.eventToMetrics(event);
|
|
20
|
+
// Debug: Log the metrics URL and count (never the serialized payload)
|
|
21
|
+
writeToLog(`DatadogExporter: Metrics URL: ${this.metricsUrl}`);
|
|
22
|
+
writeToLog(`DatadogExporter: Sending ${metrics.length} metric series`);
|
|
23
|
+
// Send logs with response checking
|
|
24
|
+
const logsPromise = fetch(this.logsUrl, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
"DD-API-KEY": this.config.apiKey,
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify([log]),
|
|
31
|
+
})
|
|
32
|
+
.then(async (response) => {
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
writeToLog(`Datadog logs failed - Status: ${response.status}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
writeToLog(`Datadog logs success - Status: ${response.status}`);
|
|
38
|
+
}
|
|
39
|
+
return response;
|
|
40
|
+
})
|
|
41
|
+
.catch((err) => {
|
|
42
|
+
writeToLog(`Datadog logs network error: ${err}`);
|
|
43
|
+
});
|
|
44
|
+
// Send metrics with response checking
|
|
45
|
+
const metricsPromise = fetch(this.metricsUrl, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"DD-API-KEY": this.config.apiKey,
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify({ series: metrics }),
|
|
52
|
+
})
|
|
53
|
+
.then(async (response) => {
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
writeToLog(`Datadog metrics failed - Status: ${response.status}`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
writeToLog(`Datadog metrics success - Status: ${response.status}`);
|
|
59
|
+
}
|
|
60
|
+
return response;
|
|
61
|
+
})
|
|
62
|
+
.catch((err) => {
|
|
63
|
+
writeToLog(`Datadog metrics network error: ${err}`);
|
|
64
|
+
});
|
|
65
|
+
// Wait for both to complete
|
|
66
|
+
await Promise.all([logsPromise, metricsPromise]);
|
|
67
|
+
}
|
|
68
|
+
eventToLog(event) {
|
|
69
|
+
const tags = [];
|
|
70
|
+
// Add basic tags
|
|
71
|
+
if (this.config.env)
|
|
72
|
+
tags.push(`env:${this.config.env}`);
|
|
73
|
+
if (event.eventType)
|
|
74
|
+
tags.push(`event_type:${event.eventType.replace(/\//g, ".")}`);
|
|
75
|
+
if (event.resourceName)
|
|
76
|
+
tags.push(`resource:${event.resourceName}`);
|
|
77
|
+
if (event.isError)
|
|
78
|
+
tags.push("error:true");
|
|
79
|
+
tags.push(`source:${WITHIN_SOURCE}`);
|
|
80
|
+
// Add customer-defined tags to ddtags (namespaced to avoid collisions with reserved Datadog tags)
|
|
81
|
+
if (event.tags) {
|
|
82
|
+
for (const [key, value] of Object.entries(event.tags)) {
|
|
83
|
+
const sanitizedKey = key.toLowerCase().replace(/[\s:,]+/g, "_");
|
|
84
|
+
const sanitizedValue = value.replace(/,/g, "_");
|
|
85
|
+
tags.push(`within.${sanitizedKey}:${sanitizedValue}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const log = {
|
|
89
|
+
message: `${event.eventType || "unknown"} - ${event.resourceName || "unknown"}`,
|
|
90
|
+
service: this.config.service,
|
|
91
|
+
ddsource: WITHIN_SOURCE,
|
|
92
|
+
ddtags: tags.join(","),
|
|
93
|
+
timestamp: event.timestamp ? event.timestamp.getTime() : Date.now(),
|
|
94
|
+
status: event.isError ? "error" : "info",
|
|
95
|
+
dd: {
|
|
96
|
+
trace_id: traceContext.getDatadogTraceId(event.sessionId),
|
|
97
|
+
span_id: traceContext.getDatadogSpanId(event.id),
|
|
98
|
+
},
|
|
99
|
+
mcp: {
|
|
100
|
+
session_id: event.sessionId,
|
|
101
|
+
event_id: event.id,
|
|
102
|
+
event_type: event.eventType,
|
|
103
|
+
resource: event.resourceName,
|
|
104
|
+
duration_ms: event.duration,
|
|
105
|
+
user_intent: event.userIntent,
|
|
106
|
+
actor_id: event.identifyActorGivenId,
|
|
107
|
+
actor_name: event.identifyActorName,
|
|
108
|
+
client_name: event.clientName,
|
|
109
|
+
client_version: event.clientVersion,
|
|
110
|
+
server_name: event.serverName,
|
|
111
|
+
server_version: event.serverVersion,
|
|
112
|
+
is_error: event.isError,
|
|
113
|
+
error: event.error,
|
|
114
|
+
tags: event.tags,
|
|
115
|
+
properties: event.properties,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
// Add error at root level if it exists
|
|
119
|
+
if (event.isError && event.error) {
|
|
120
|
+
log.error = {
|
|
121
|
+
message: typeof event.error === "string"
|
|
122
|
+
? event.error
|
|
123
|
+
: JSON.stringify(event.error),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return log;
|
|
127
|
+
}
|
|
128
|
+
eventToMetrics(event) {
|
|
129
|
+
const metrics = [];
|
|
130
|
+
const timestamp = Math.floor((event.timestamp?.getTime() || Date.now()) / 1000);
|
|
131
|
+
const tags = [`service:${this.config.service}`];
|
|
132
|
+
// Add optional tags
|
|
133
|
+
if (this.config.env)
|
|
134
|
+
tags.push(`env:${this.config.env}`);
|
|
135
|
+
if (event.eventType)
|
|
136
|
+
tags.push(`event_type:${event.eventType.replace(/\//g, ".")}`);
|
|
137
|
+
if (event.resourceName)
|
|
138
|
+
tags.push(`resource:${event.resourceName}`);
|
|
139
|
+
// Event count metric
|
|
140
|
+
metrics.push({
|
|
141
|
+
metric: "mcp.events.count",
|
|
142
|
+
type: "count",
|
|
143
|
+
points: [[timestamp, 1]],
|
|
144
|
+
tags,
|
|
145
|
+
});
|
|
146
|
+
// Duration metric (only if duration exists)
|
|
147
|
+
if (event.duration) {
|
|
148
|
+
metrics.push({
|
|
149
|
+
metric: "mcp.event.duration",
|
|
150
|
+
type: "gauge",
|
|
151
|
+
points: [[timestamp, event.duration]],
|
|
152
|
+
tags,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// Error count metric
|
|
156
|
+
if (event.isError) {
|
|
157
|
+
metrics.push({
|
|
158
|
+
metric: "mcp.errors.count",
|
|
159
|
+
type: "count",
|
|
160
|
+
points: [[timestamp, 1]],
|
|
161
|
+
tags,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return metrics;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Event, Exporter } from "../../types.js";
|
|
2
|
+
export interface OTLPExporterConfig {
|
|
3
|
+
type: "otlp";
|
|
4
|
+
endpoint: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare class OTLPExporter implements Exporter {
|
|
8
|
+
private endpoint;
|
|
9
|
+
private headers;
|
|
10
|
+
constructor(config: OTLPExporterConfig);
|
|
11
|
+
export(event: Event): Promise<void>;
|
|
12
|
+
private convertToOTLPSpan;
|
|
13
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { writeToLog } from "../logging.js";
|
|
2
|
+
import { traceContext } from "./trace-context.js";
|
|
3
|
+
import { WITHIN_SOURCE } from "../constants.js";
|
|
4
|
+
export class OTLPExporter {
|
|
5
|
+
endpoint;
|
|
6
|
+
headers;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
// Auto-append /v1/traces per OTLP spec if not already present
|
|
9
|
+
const url = config.endpoint.replace(/\/+$/, "");
|
|
10
|
+
this.endpoint = url.endsWith("/v1/traces") ? url : `${url}/v1/traces`;
|
|
11
|
+
this.headers = {
|
|
12
|
+
"Content-Type": "application/json", // Using JSON for now for easier debugging
|
|
13
|
+
...config.headers,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async export(event) {
|
|
17
|
+
try {
|
|
18
|
+
// Convert Within event to OTLP trace format
|
|
19
|
+
const span = this.convertToOTLPSpan(event);
|
|
20
|
+
// Create OTLP JSON format
|
|
21
|
+
const otlpRequest = {
|
|
22
|
+
resourceSpans: [
|
|
23
|
+
{
|
|
24
|
+
resource: {
|
|
25
|
+
attributes: [
|
|
26
|
+
{
|
|
27
|
+
key: "service.name",
|
|
28
|
+
value: { stringValue: event.serverName || "mcp-server" },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: "service.version",
|
|
32
|
+
value: { stringValue: event.serverVersion || "unknown" },
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
scopeSpans: [
|
|
37
|
+
{
|
|
38
|
+
scope: {
|
|
39
|
+
name: "within-sdk",
|
|
40
|
+
version: event.sdkVersion || "unknown",
|
|
41
|
+
},
|
|
42
|
+
spans: [span],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
// Use JSON format for now
|
|
49
|
+
const body = JSON.stringify(otlpRequest);
|
|
50
|
+
// Use fetch to send the data
|
|
51
|
+
const response = await fetch(this.endpoint, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: this.headers,
|
|
54
|
+
body,
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw new Error(`OTLP export failed: ${response.status} ${response.statusText}`);
|
|
58
|
+
}
|
|
59
|
+
writeToLog(`Successfully exported event to OTLP: ${event.id}`);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new Error(`OTLP export error: ${error}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
convertToOTLPSpan(event) {
|
|
66
|
+
const startTimeNanos = event.timestamp
|
|
67
|
+
? BigInt(event.timestamp.getTime()) * BigInt(1_000_000)
|
|
68
|
+
: BigInt(Date.now()) * BigInt(1_000_000);
|
|
69
|
+
const endTimeNanos = event.duration
|
|
70
|
+
? startTimeNanos + BigInt(event.duration) * BigInt(1_000_000)
|
|
71
|
+
: startTimeNanos;
|
|
72
|
+
return {
|
|
73
|
+
traceId: traceContext.getTraceId(event.sessionId),
|
|
74
|
+
spanId: traceContext.getSpanId(event.id),
|
|
75
|
+
name: event.eventType || "mcp.event",
|
|
76
|
+
kind: 2, // SPAN_KIND_SERVER
|
|
77
|
+
startTimeUnixNano: startTimeNanos.toString(),
|
|
78
|
+
endTimeUnixNano: endTimeNanos.toString(),
|
|
79
|
+
attributes: [
|
|
80
|
+
{
|
|
81
|
+
key: "source",
|
|
82
|
+
value: { stringValue: WITHIN_SOURCE },
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "mcp.event_type",
|
|
86
|
+
value: { stringValue: event.eventType || "" },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: "mcp.session_id",
|
|
90
|
+
value: { stringValue: event.sessionId || "" },
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: "mcp.vendor_slug",
|
|
94
|
+
value: { stringValue: event.vendorSlug || event.projectId || "" },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: "mcp.resource_name",
|
|
98
|
+
value: { stringValue: event.resourceName || "" },
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: "mcp.user_intent",
|
|
102
|
+
value: { stringValue: event.userIntent || "" },
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
key: "mcp.actor_id",
|
|
106
|
+
value: { stringValue: event.identifyActorGivenId || "" },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "mcp.actor_name",
|
|
110
|
+
value: { stringValue: event.identifyActorName || "" },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: "mcp.client_name",
|
|
114
|
+
value: { stringValue: event.clientName || "" },
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
key: "mcp.client_version",
|
|
118
|
+
value: { stringValue: event.clientVersion || "" },
|
|
119
|
+
},
|
|
120
|
+
// Add customer-defined tags as individual attributes
|
|
121
|
+
...Object.entries(event.tags || {}).map(([key, value]) => ({
|
|
122
|
+
key: `within.tag.${key}`,
|
|
123
|
+
value: { stringValue: value },
|
|
124
|
+
})),
|
|
125
|
+
// Add customer-defined properties as JSON
|
|
126
|
+
...(event.properties
|
|
127
|
+
? [
|
|
128
|
+
{
|
|
129
|
+
key: "within.properties",
|
|
130
|
+
value: { stringValue: JSON.stringify(event.properties) },
|
|
131
|
+
},
|
|
132
|
+
]
|
|
133
|
+
: []),
|
|
134
|
+
].filter((attr) => attr.value.stringValue), // Remove empty attributes
|
|
135
|
+
status: {
|
|
136
|
+
code: event.isError ? 2 : 1, // ERROR : OK
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Event, Exporter } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generates a deterministic UUIDv7 from a prefixed KSUID (e.g. ses_xxx).
|
|
4
|
+
* Uses the KSUID's embedded timestamp for the UUIDv7 timestamp portion
|
|
5
|
+
* and a SHA-256 hash of the full ID for the random bits.
|
|
6
|
+
*/
|
|
7
|
+
export declare function toUUIDv7(prefixedId: string): string;
|
|
8
|
+
export interface PostHogExporterConfig {
|
|
9
|
+
type: "posthog";
|
|
10
|
+
apiKey: string;
|
|
11
|
+
host?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Emits `$ai_span` events for tool calls alongside regular capture events,
|
|
14
|
+
* integrating with PostHog's AI observability views. Each tool call is its own
|
|
15
|
+
* trace (`$ai_trace_id`), grouped into sessions via `$ai_session_id`.
|
|
16
|
+
* Customer-defined `eventTags` are spread directly onto `$ai_span` properties
|
|
17
|
+
* and can override any default, including reserved `$ai_*` fields.
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
enableAITracing?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare class PostHogExporter implements Exporter {
|
|
23
|
+
private batchUrl;
|
|
24
|
+
private apiKey;
|
|
25
|
+
private config;
|
|
26
|
+
constructor(config: PostHogExporterConfig);
|
|
27
|
+
export(event: Event): Promise<void>;
|
|
28
|
+
private buildCaptureEvent;
|
|
29
|
+
private buildExceptionEvent;
|
|
30
|
+
private buildAISpanEvent;
|
|
31
|
+
private mapEventType;
|
|
32
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { writeToLog } from "../logging.js";
|
|
3
|
+
import { WITHIN_SOURCE, WithinEventType } from "../constants.js";
|
|
4
|
+
import KSUID from "../../thirdparty/ksuid/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Generates a deterministic UUIDv7 from a prefixed KSUID (e.g. ses_xxx).
|
|
7
|
+
* Uses the KSUID's embedded timestamp for the UUIDv7 timestamp portion
|
|
8
|
+
* and a SHA-256 hash of the full ID for the random bits.
|
|
9
|
+
*/
|
|
10
|
+
export function toUUIDv7(prefixedId) {
|
|
11
|
+
// Strip prefix (ses_, evt_, etc.) and parse KSUID
|
|
12
|
+
const ksuidStr = prefixedId.replace(/^[a-z]+_/, "");
|
|
13
|
+
let timestampMs;
|
|
14
|
+
try {
|
|
15
|
+
const ksuid = KSUID.parse(ksuidStr);
|
|
16
|
+
timestampMs = ksuid.date.getTime();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Fallback: if KSUID parsing fails, use current time
|
|
20
|
+
timestampMs = Date.now();
|
|
21
|
+
}
|
|
22
|
+
// Hash the full ID for deterministic random bits
|
|
23
|
+
const hash = createHash("sha256").update(prefixedId).digest();
|
|
24
|
+
const buf = Buffer.alloc(16);
|
|
25
|
+
// Bytes 0-5: 48-bit Unix timestamp in milliseconds
|
|
26
|
+
buf.writeUIntBE(timestampMs, 0, 6);
|
|
27
|
+
// Byte 6: version 7 (0111) + high 4 bits of rand_a from hash
|
|
28
|
+
buf[6] = 0x70 | (hash[0] & 0x0f);
|
|
29
|
+
// Byte 7: low 8 bits of rand_a from hash
|
|
30
|
+
buf[7] = hash[1];
|
|
31
|
+
// Byte 8: variant 10 + high 6 bits of rand_b from hash
|
|
32
|
+
buf[8] = 0x80 | (hash[2] & 0x3f);
|
|
33
|
+
// Bytes 9-15: remaining rand_b from hash
|
|
34
|
+
buf[9] = hash[3];
|
|
35
|
+
buf[10] = hash[4];
|
|
36
|
+
buf[11] = hash[5];
|
|
37
|
+
buf[12] = hash[6];
|
|
38
|
+
buf[13] = hash[7];
|
|
39
|
+
buf[14] = hash[8];
|
|
40
|
+
buf[15] = hash[9];
|
|
41
|
+
const hex = buf.toString("hex");
|
|
42
|
+
return [
|
|
43
|
+
hex.substring(0, 8),
|
|
44
|
+
hex.substring(8, 12),
|
|
45
|
+
hex.substring(12, 16),
|
|
46
|
+
hex.substring(16, 20),
|
|
47
|
+
hex.substring(20, 32),
|
|
48
|
+
].join("-");
|
|
49
|
+
}
|
|
50
|
+
function getDistinctId(event) {
|
|
51
|
+
return event.identifyActorGivenId || event.sessionId || "anonymous";
|
|
52
|
+
}
|
|
53
|
+
function getTimestamp(event) {
|
|
54
|
+
return event.timestamp
|
|
55
|
+
? event.timestamp.toISOString()
|
|
56
|
+
: new Date().toISOString();
|
|
57
|
+
}
|
|
58
|
+
export class PostHogExporter {
|
|
59
|
+
batchUrl;
|
|
60
|
+
apiKey;
|
|
61
|
+
config;
|
|
62
|
+
constructor(config) {
|
|
63
|
+
this.config = config;
|
|
64
|
+
const host = (config.host || "https://us.i.posthog.com").replace(/\/$/, "");
|
|
65
|
+
this.batchUrl = `${host}/batch`;
|
|
66
|
+
this.apiKey = config.apiKey;
|
|
67
|
+
writeToLog(`PostHogExporter: Initialized with endpoint ${this.batchUrl}`);
|
|
68
|
+
}
|
|
69
|
+
async export(event) {
|
|
70
|
+
try {
|
|
71
|
+
const batch = [];
|
|
72
|
+
// Always send the regular event
|
|
73
|
+
batch.push(this.buildCaptureEvent(event));
|
|
74
|
+
// Send $exception event alongside if this is an error
|
|
75
|
+
if (event.isError && event.error) {
|
|
76
|
+
batch.push(this.buildExceptionEvent(event));
|
|
77
|
+
}
|
|
78
|
+
// Send $ai_span for tool calls when AI tracing is enabled
|
|
79
|
+
if (this.config.enableAITracing &&
|
|
80
|
+
event.eventType === WithinEventType.mcpToolsCall) {
|
|
81
|
+
batch.push(this.buildAISpanEvent(event));
|
|
82
|
+
}
|
|
83
|
+
writeToLog(`PostHogExporter: Sending ${batch.length} event(s) for ${event.id}`);
|
|
84
|
+
const response = await fetch(this.batchUrl, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
api_key: this.apiKey,
|
|
91
|
+
batch,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const errorBody = await response.text();
|
|
96
|
+
writeToLog(`PostHog export failed - Status: ${response.status}, Body: ${errorBody}`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
writeToLog(`PostHog export success - Event: ${event.id}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
writeToLog(`PostHog export error: ${error}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
buildCaptureEvent(event) {
|
|
107
|
+
const distinctId = getDistinctId(event);
|
|
108
|
+
const eventName = this.mapEventType(event.eventType);
|
|
109
|
+
const timestamp = getTimestamp(event);
|
|
110
|
+
const properties = {
|
|
111
|
+
$session_id: toUUIDv7(event.sessionId),
|
|
112
|
+
source: WITHIN_SOURCE,
|
|
113
|
+
};
|
|
114
|
+
if (event.resourceName) {
|
|
115
|
+
properties.resource_name = event.resourceName;
|
|
116
|
+
if (event.eventType === WithinEventType.mcpToolsCall) {
|
|
117
|
+
properties.tool_name = event.resourceName;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (event.duration !== undefined) {
|
|
121
|
+
properties.duration_ms = event.duration;
|
|
122
|
+
}
|
|
123
|
+
if (event.serverName)
|
|
124
|
+
properties.server_name = event.serverName;
|
|
125
|
+
if (event.serverVersion)
|
|
126
|
+
properties.server_version = event.serverVersion;
|
|
127
|
+
if (event.clientName)
|
|
128
|
+
properties.client_name = event.clientName;
|
|
129
|
+
if (event.clientVersion)
|
|
130
|
+
properties.client_version = event.clientVersion;
|
|
131
|
+
if (event.projectId)
|
|
132
|
+
properties.project_id = event.projectId;
|
|
133
|
+
if (event.userIntent)
|
|
134
|
+
properties.user_intent = event.userIntent;
|
|
135
|
+
if (event.isError !== undefined)
|
|
136
|
+
properties.is_error = event.isError;
|
|
137
|
+
if (event.parameters !== undefined) {
|
|
138
|
+
properties.parameters = event.parameters;
|
|
139
|
+
}
|
|
140
|
+
if (event.response !== undefined) {
|
|
141
|
+
properties.response = event.response;
|
|
142
|
+
}
|
|
143
|
+
// Set person properties from identity data
|
|
144
|
+
const $set = {};
|
|
145
|
+
if (event.identifyActorName)
|
|
146
|
+
$set.name = event.identifyActorName;
|
|
147
|
+
if (event.identifyActorData) {
|
|
148
|
+
Object.assign($set, event.identifyActorData);
|
|
149
|
+
}
|
|
150
|
+
if (Object.keys($set).length > 0) {
|
|
151
|
+
properties.$set = $set;
|
|
152
|
+
}
|
|
153
|
+
// Spread customer-defined tags directly (can override Within defaults)
|
|
154
|
+
if (event.tags) {
|
|
155
|
+
for (const [key, value] of Object.entries(event.tags)) {
|
|
156
|
+
properties[key] = value;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Spread customer-defined properties directly (can override Within defaults)
|
|
160
|
+
if (event.properties) {
|
|
161
|
+
for (const [key, value] of Object.entries(event.properties)) {
|
|
162
|
+
properties[key] = value;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
event: eventName,
|
|
167
|
+
distinct_id: distinctId,
|
|
168
|
+
properties,
|
|
169
|
+
timestamp,
|
|
170
|
+
type: "capture",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
buildExceptionEvent(event) {
|
|
174
|
+
const distinctId = getDistinctId(event);
|
|
175
|
+
const timestamp = getTimestamp(event);
|
|
176
|
+
const properties = {
|
|
177
|
+
$exception_source: "backend",
|
|
178
|
+
$session_id: toUUIDv7(event.sessionId),
|
|
179
|
+
};
|
|
180
|
+
if (event.error) {
|
|
181
|
+
if (event.error.message) {
|
|
182
|
+
properties.$exception_message = event.error.message;
|
|
183
|
+
}
|
|
184
|
+
if (event.error.type) {
|
|
185
|
+
properties.$exception_type = event.error.type;
|
|
186
|
+
}
|
|
187
|
+
if (event.error.stack) {
|
|
188
|
+
properties.$exception_stacktrace = event.error.stack;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Add tool/resource context
|
|
192
|
+
if (event.resourceName) {
|
|
193
|
+
properties.resource_name = event.resourceName;
|
|
194
|
+
if (event.eventType === WithinEventType.mcpToolsCall) {
|
|
195
|
+
properties.tool_name = event.resourceName;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (event.serverName)
|
|
199
|
+
properties.server_name = event.serverName;
|
|
200
|
+
if (event.serverVersion)
|
|
201
|
+
properties.server_version = event.serverVersion;
|
|
202
|
+
if (event.clientName)
|
|
203
|
+
properties.client_name = event.clientName;
|
|
204
|
+
if (event.clientVersion)
|
|
205
|
+
properties.client_version = event.clientVersion;
|
|
206
|
+
return {
|
|
207
|
+
event: "$exception",
|
|
208
|
+
distinct_id: distinctId,
|
|
209
|
+
properties,
|
|
210
|
+
timestamp,
|
|
211
|
+
type: "capture",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
buildAISpanEvent(event) {
|
|
215
|
+
const distinctId = getDistinctId(event);
|
|
216
|
+
const timestamp = getTimestamp(event);
|
|
217
|
+
const properties = {
|
|
218
|
+
$ai_session_id: `within_${event.sessionId}`,
|
|
219
|
+
$ai_trace_id: toUUIDv7(event.sessionId),
|
|
220
|
+
$ai_span_id: toUUIDv7(event.id),
|
|
221
|
+
$ai_span_name: event.resourceName || "unknown_tool",
|
|
222
|
+
$ai_is_error: event.isError || false,
|
|
223
|
+
$session_id: toUUIDv7(event.sessionId),
|
|
224
|
+
source: WITHIN_SOURCE,
|
|
225
|
+
};
|
|
226
|
+
if (event.duration !== undefined) {
|
|
227
|
+
properties.$ai_latency = event.duration / 1000;
|
|
228
|
+
}
|
|
229
|
+
if (event.isError && event.error) {
|
|
230
|
+
properties.$ai_error = event.error;
|
|
231
|
+
}
|
|
232
|
+
if (event.parameters !== undefined) {
|
|
233
|
+
properties.$ai_input_state = event.parameters;
|
|
234
|
+
}
|
|
235
|
+
if (event.response !== undefined) {
|
|
236
|
+
properties.$ai_output_state = event.response;
|
|
237
|
+
}
|
|
238
|
+
if (event.serverName)
|
|
239
|
+
properties.server_name = event.serverName;
|
|
240
|
+
if (event.clientName)
|
|
241
|
+
properties.client_name = event.clientName;
|
|
242
|
+
// Spread customer tags directly (can override Within defaults)
|
|
243
|
+
if (event.tags) {
|
|
244
|
+
for (const [key, value] of Object.entries(event.tags)) {
|
|
245
|
+
properties[key] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Spread customer properties directly (can override Within defaults)
|
|
249
|
+
if (event.properties) {
|
|
250
|
+
for (const [key, value] of Object.entries(event.properties)) {
|
|
251
|
+
properties[key] = value;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
event: "$ai_span",
|
|
256
|
+
distinct_id: distinctId,
|
|
257
|
+
properties,
|
|
258
|
+
timestamp,
|
|
259
|
+
type: "capture",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
mapEventType(eventType) {
|
|
263
|
+
// Map Within event types to PostHog event names
|
|
264
|
+
const mapping = {
|
|
265
|
+
[WithinEventType.mcpToolsCall]: "mcp_tool_call",
|
|
266
|
+
[WithinEventType.mcpToolsList]: "mcp_tools_list",
|
|
267
|
+
[WithinEventType.mcpInitialize]: "mcp_initialize",
|
|
268
|
+
};
|
|
269
|
+
return (mapping[eventType] ||
|
|
270
|
+
`mcp_${eventType.replace(/^mcp:/, "").replace(/\//g, "_")}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Event, Exporter } from "../../types.js";
|
|
2
|
+
export interface SentryExporterConfig {
|
|
3
|
+
type: "sentry";
|
|
4
|
+
dsn: string;
|
|
5
|
+
environment?: string;
|
|
6
|
+
release?: string;
|
|
7
|
+
enableTracing?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class SentryExporter implements Exporter {
|
|
10
|
+
private endpoint;
|
|
11
|
+
private authHeader;
|
|
12
|
+
private config;
|
|
13
|
+
private parsedDSN;
|
|
14
|
+
constructor(config: SentryExporterConfig);
|
|
15
|
+
private parseDSN;
|
|
16
|
+
export(event: Event): Promise<void>;
|
|
17
|
+
private eventToLog;
|
|
18
|
+
private buildLogAttributes;
|
|
19
|
+
private createLogEnvelope;
|
|
20
|
+
private eventToTransaction;
|
|
21
|
+
private buildTags;
|
|
22
|
+
private buildExtra;
|
|
23
|
+
private buildContexts;
|
|
24
|
+
private eventToErrorEvent;
|
|
25
|
+
private createTransactionEnvelope;
|
|
26
|
+
private createErrorEnvelope;
|
|
27
|
+
}
|