@posthog/ai 8.10.0 → 8.10.2
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/adk/index.cjs +908 -1198
- package/dist/adk/index.cjs.map +1 -1
- package/dist/adk/index.d.ts +109 -109
- package/dist/adk/index.mjs +907 -1196
- package/dist/adk/index.mjs.map +1 -1
- package/dist/anthropic/index.cjs +927 -1111
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.ts +34 -33
- package/dist/anthropic/index.mjs +899 -1102
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +863 -1108
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.d.ts +38 -35
- package/dist/gemini/index.mjs +858 -1103
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +1218 -1546
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +170 -163
- package/dist/index.mjs +1216 -1544
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +851 -1029
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.d.ts +75 -75
- package/dist/langchain/index.mjs +850 -1027
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/langchain/middleware/index.cjs +1016 -1225
- package/dist/langchain/middleware/index.cjs.map +1 -1
- package/dist/langchain/middleware/index.d.ts +29 -25
- package/dist/langchain/middleware/index.mjs +1015 -1223
- package/dist/langchain/middleware/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1990 -2523
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.ts +106 -104
- package/dist/openai/index.mjs +1985 -2518
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +745 -827
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.d.ts +48 -47
- package/dist/openai-agents/index.mjs +744 -825
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/otel/index.cjs +427 -486
- package/dist/otel/index.cjs.map +1 -1
- package/dist/otel/index.d.ts +36 -35
- package/dist/otel/index.mjs +426 -484
- package/dist/otel/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +992 -1343
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.d.ts +21 -16
- package/dist/vercel/index.mjs +991 -1341
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +15 -14
package/dist/otel/index.cjs
CHANGED
|
@@ -1,525 +1,466 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _opentelemetry_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
3
|
+
let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
4
|
+
//#region src/sanitization/base64_recognizer.ts
|
|
6
5
|
const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
|
|
7
6
|
const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
7
|
+
var Base64Recognizer = class {
|
|
8
|
+
recognize(value, minLength) {
|
|
9
|
+
const dataUrl = DATA_URL_PREFIX_RE.exec(value);
|
|
10
|
+
if (dataUrl) return {
|
|
11
|
+
kind: "data-url",
|
|
12
|
+
mediaType: dataUrl[1]
|
|
13
|
+
};
|
|
14
|
+
if (value.length < minLength) return { kind: "none" };
|
|
15
|
+
const confidencePrefix = value.slice(0, minLength);
|
|
16
|
+
if (BASE64_ALPHABET_RE.test(confidencePrefix)) return { kind: "raw" };
|
|
17
|
+
else return { kind: "none" };
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/sanitization/media_type_context.ts
|
|
22
|
+
const MIME_HINT_KEYS = [
|
|
23
|
+
"mediaType",
|
|
24
|
+
"media_type",
|
|
25
|
+
"mimeType",
|
|
26
|
+
"mime_type"
|
|
27
|
+
];
|
|
28
|
+
const STRONG_CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
29
|
+
"data",
|
|
30
|
+
"file_data",
|
|
31
|
+
"fileData",
|
|
32
|
+
"image_url",
|
|
33
|
+
"imageUrl",
|
|
34
|
+
"video_url",
|
|
35
|
+
"videoUrl",
|
|
36
|
+
"audio",
|
|
37
|
+
"audio_data",
|
|
38
|
+
"audioData",
|
|
39
|
+
"inline_data",
|
|
40
|
+
"inlineData",
|
|
41
|
+
"source",
|
|
42
|
+
"result"
|
|
43
|
+
]);
|
|
44
|
+
const STRONG_CONTEXT_TYPES = /* @__PURE__ */ new Set([
|
|
45
|
+
"image",
|
|
46
|
+
"image_url",
|
|
47
|
+
"input_image",
|
|
48
|
+
"audio",
|
|
49
|
+
"input_audio",
|
|
50
|
+
"video",
|
|
51
|
+
"video_url",
|
|
52
|
+
"file",
|
|
53
|
+
"input_file",
|
|
54
|
+
"document",
|
|
55
|
+
"media",
|
|
56
|
+
"file-data"
|
|
57
|
+
]);
|
|
58
|
+
const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
|
|
59
|
+
"file",
|
|
60
|
+
"input_file",
|
|
61
|
+
"document",
|
|
62
|
+
"media",
|
|
63
|
+
"file-data"
|
|
64
|
+
]);
|
|
65
|
+
const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
|
|
66
|
+
"wav",
|
|
67
|
+
"mp3",
|
|
68
|
+
"ogg",
|
|
69
|
+
"flac",
|
|
70
|
+
"m4a",
|
|
71
|
+
"aac",
|
|
72
|
+
"webm"
|
|
73
|
+
]);
|
|
74
|
+
var MediaTypeContext = class MediaTypeContext {
|
|
75
|
+
static {
|
|
76
|
+
this.EMPTY = new MediaTypeContext(void 0, void 0);
|
|
77
|
+
}
|
|
78
|
+
constructor(parent, key, explicitMediaType) {
|
|
79
|
+
this.parent = parent;
|
|
80
|
+
this.key = key;
|
|
81
|
+
this.explicitMediaType = explicitMediaType;
|
|
82
|
+
}
|
|
83
|
+
inferMediaType() {
|
|
84
|
+
return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
|
|
85
|
+
}
|
|
86
|
+
inferFromSiblingMime() {
|
|
87
|
+
if (this.explicitMediaType) return this.explicitMediaType;
|
|
88
|
+
if (!this.parent) return void 0;
|
|
89
|
+
for (const hint of MIME_HINT_KEYS) {
|
|
90
|
+
const v = this.parent[hint];
|
|
91
|
+
if (typeof v === "string") return v;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
inferFromSiblingFormat() {
|
|
95
|
+
if (!this.parent) return void 0;
|
|
96
|
+
const fmt = this.parent.format;
|
|
97
|
+
if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return `audio/${fmt.toLowerCase()}`;
|
|
98
|
+
}
|
|
99
|
+
inferFromParentType() {
|
|
100
|
+
if (!this.parent) return void 0;
|
|
101
|
+
const t = this.parent.type;
|
|
102
|
+
if (typeof t !== "string") return void 0;
|
|
103
|
+
if (t === "image" || t === "image_url" || t === "input_image") return "image";
|
|
104
|
+
if (t === "audio" || t === "input_audio") return "audio";
|
|
105
|
+
if (t === "video" || t === "video_url") return "video";
|
|
106
|
+
if (FILE_FAMILY_TYPES.has(t)) return "application/octet-stream";
|
|
107
|
+
}
|
|
108
|
+
inferFromKey() {
|
|
109
|
+
if (!this.key) return void 0;
|
|
110
|
+
const key = this.key.toLowerCase();
|
|
111
|
+
if (key.includes("audio")) return "audio";
|
|
112
|
+
if (key.includes("video")) return "video";
|
|
113
|
+
if (key.includes("image")) return "image";
|
|
114
|
+
if (key.includes("file") || key.includes("document")) return "application/octet-stream";
|
|
115
|
+
}
|
|
116
|
+
hasExplicitBinaryMediaType() {
|
|
117
|
+
if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
|
|
118
|
+
const mediaType = this.inferFromSiblingMime();
|
|
119
|
+
return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
|
|
120
|
+
}
|
|
121
|
+
signalsBinary() {
|
|
122
|
+
if (this.explicitMediaType) return true;
|
|
123
|
+
if (this.parent) {
|
|
124
|
+
for (const hint of MIME_HINT_KEYS) if (typeof this.parent[hint] === "string") return true;
|
|
125
|
+
const fmt = this.parent.format;
|
|
126
|
+
if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
|
|
127
|
+
const t = this.parent.type;
|
|
128
|
+
if (typeof t === "string" && STRONG_CONTEXT_TYPES.has(t)) return true;
|
|
129
|
+
}
|
|
130
|
+
if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/sanitization/binary_content_redactor.ts
|
|
103
136
|
const STRONG_CONTEXT_MIN_LENGTH = 64;
|
|
104
137
|
const WEAK_CONTEXT_MIN_LENGTH = 1024;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return value;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
placeholderFor(mediaType) {
|
|
150
|
-
if (!mediaType) return '[base64 redacted]';
|
|
151
|
-
if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
|
|
152
|
-
return `[base64 ${mediaType} redacted]`;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Deliberately always redacts here — the OTLP export path has no per-client passthrough gate.
|
|
138
|
+
var BinaryContentRedactor = class {
|
|
139
|
+
constructor(recognizer = new Base64Recognizer()) {
|
|
140
|
+
this.recognizer = recognizer;
|
|
141
|
+
this.visited = /* @__PURE__ */ new WeakSet();
|
|
142
|
+
}
|
|
143
|
+
redact(value, mediaType) {
|
|
144
|
+
this.visited = /* @__PURE__ */ new WeakSet();
|
|
145
|
+
return this.walk(value, mediaType ? new MediaTypeContext(void 0, void 0, mediaType) : MediaTypeContext.EMPTY);
|
|
146
|
+
}
|
|
147
|
+
walk(value, ctx) {
|
|
148
|
+
if (value === null || value === void 0) return value;
|
|
149
|
+
if (typeof value === "string") return this.redactString(value, ctx);
|
|
150
|
+
if (typeof value !== "object") return value;
|
|
151
|
+
if (typeof Uint8Array !== "undefined" && value instanceof Uint8Array) return this.placeholderFor(ctx.inferMediaType());
|
|
152
|
+
if (this.visited.has(value)) return null;
|
|
153
|
+
this.visited.add(value);
|
|
154
|
+
if (Array.isArray(value)) return value.map((item) => this.walk(item, ctx));
|
|
155
|
+
const obj = value;
|
|
156
|
+
const out = {};
|
|
157
|
+
for (const k of Object.keys(obj)) out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
redactString(value, ctx) {
|
|
161
|
+
const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
|
|
162
|
+
const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
|
|
163
|
+
const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
|
|
164
|
+
const recognition = this.recognizer.recognize(recognitionValue, minLength);
|
|
165
|
+
switch (recognition.kind) {
|
|
166
|
+
case "data-url": return this.placeholderFor(recognition.mediaType);
|
|
167
|
+
case "raw": return this.placeholderFor(ctx.inferMediaType());
|
|
168
|
+
case "none": return value;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
placeholderFor(mediaType) {
|
|
172
|
+
if (!mediaType) return "[base64 redacted]";
|
|
173
|
+
if (mediaType === "application/octet-stream") return "[base64 file redacted]";
|
|
174
|
+
return `[base64 ${mediaType} redacted]`;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/otel/redact.ts
|
|
157
179
|
const redactor = new BinaryContentRedactor();
|
|
158
180
|
function redactSpan(span) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
configurable: true
|
|
175
|
-
}
|
|
176
|
-
});
|
|
181
|
+
const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes;
|
|
182
|
+
const events = span.events ? redactEvents(span.events) : span.events;
|
|
183
|
+
if (attributes === span.attributes && events === span.events) return span;
|
|
184
|
+
return Object.create(span, {
|
|
185
|
+
attributes: {
|
|
186
|
+
value: attributes,
|
|
187
|
+
enumerable: true,
|
|
188
|
+
configurable: true
|
|
189
|
+
},
|
|
190
|
+
events: {
|
|
191
|
+
value: events,
|
|
192
|
+
enumerable: true,
|
|
193
|
+
configurable: true
|
|
194
|
+
}
|
|
195
|
+
});
|
|
177
196
|
}
|
|
178
197
|
function redactAttributes(attributes) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
return changed ? out : attributes;
|
|
198
|
+
let changed = false;
|
|
199
|
+
const out = {};
|
|
200
|
+
for (const key of Object.keys(attributes)) {
|
|
201
|
+
const value = attributes[key];
|
|
202
|
+
const redacted = value === void 0 ? value : redactAttributeValue(value);
|
|
203
|
+
if (redacted !== value) changed = true;
|
|
204
|
+
out[key] = redacted;
|
|
205
|
+
}
|
|
206
|
+
return changed ? out : attributes;
|
|
190
207
|
}
|
|
191
208
|
function redactEvents(events) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
attributes
|
|
205
|
-
};
|
|
206
|
-
});
|
|
207
|
-
return changed ? out : events;
|
|
209
|
+
let changed = false;
|
|
210
|
+
const out = events.map((event) => {
|
|
211
|
+
if (!event.attributes) return event;
|
|
212
|
+
const attributes = redactAttributes(event.attributes);
|
|
213
|
+
if (attributes === event.attributes) return event;
|
|
214
|
+
changed = true;
|
|
215
|
+
return {
|
|
216
|
+
...event,
|
|
217
|
+
attributes
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
return changed ? out : events;
|
|
208
221
|
}
|
|
209
222
|
function redactAttributeValue(value) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
return redacted;
|
|
224
|
-
});
|
|
225
|
-
return changed ? out : value;
|
|
226
|
-
}
|
|
227
|
-
return value;
|
|
223
|
+
if (typeof value === "string") return redactString(value);
|
|
224
|
+
if (isStringArray(value)) {
|
|
225
|
+
let changed = false;
|
|
226
|
+
const out = value.map((item) => {
|
|
227
|
+
if (typeof item !== "string") return item;
|
|
228
|
+
const redacted = redactString(item);
|
|
229
|
+
if (redacted !== item) changed = true;
|
|
230
|
+
return redacted;
|
|
231
|
+
});
|
|
232
|
+
return changed ? out : value;
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
228
235
|
}
|
|
229
236
|
function isStringArray(value) {
|
|
230
|
-
|
|
237
|
+
return Array.isArray(value) && value.every((item) => typeof item !== "number" && typeof item !== "boolean");
|
|
231
238
|
}
|
|
232
239
|
function redactString(value) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
return redactor.redact(value);
|
|
240
|
+
const trimmed = value.trimStart();
|
|
241
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
242
|
+
const redacted = redactJson(value);
|
|
243
|
+
if (redacted !== void 0) return redacted;
|
|
244
|
+
}
|
|
245
|
+
return redactor.redact(value);
|
|
241
246
|
}
|
|
242
247
|
function redactJson(value) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const redactedStr = JSON.stringify(redactor.redact(parsed));
|
|
253
|
-
// Preserve the original string (and its formatting) when nothing was redacted.
|
|
254
|
-
return redactedStr === JSON.stringify(parsed) ? value : redactedStr;
|
|
248
|
+
let parsed;
|
|
249
|
+
try {
|
|
250
|
+
parsed = JSON.parse(value);
|
|
251
|
+
} catch {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (parsed === null || typeof parsed !== "object") return;
|
|
255
|
+
const redactedStr = JSON.stringify(redactor.redact(parsed));
|
|
256
|
+
return redactedStr === JSON.stringify(parsed) ? value : redactedStr;
|
|
255
257
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/otel/spans.ts
|
|
260
|
+
const AI_SPAN_PREFIXES = [
|
|
261
|
+
"gen_ai.",
|
|
262
|
+
"llm.",
|
|
263
|
+
"ai.",
|
|
264
|
+
"traceloop."
|
|
265
|
+
];
|
|
259
266
|
/**
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
267
|
+
* Returns `true` when the span is AI-related — its name or any attribute
|
|
268
|
+
* key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.
|
|
269
|
+
*/
|
|
263
270
|
function isAISpan(span) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (attributes) {
|
|
269
|
-
return Object.keys(attributes).some(key => AI_SPAN_PREFIXES.some(prefix => key.startsWith(prefix)));
|
|
270
|
-
}
|
|
271
|
-
return false;
|
|
271
|
+
if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) return true;
|
|
272
|
+
const attributes = span.attributes;
|
|
273
|
+
if (attributes) return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)));
|
|
274
|
+
return false;
|
|
272
275
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
|
|
291
|
-
} catch {
|
|
292
|
-
return undefined;
|
|
293
|
-
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/gatewayWarning.ts
|
|
278
|
+
const POSTHOG_AI_GATEWAY_HOSTS = [
|
|
279
|
+
"gateway.posthog.com",
|
|
280
|
+
"gateway.us.posthog.com",
|
|
281
|
+
"gateway.eu.posthog.com",
|
|
282
|
+
"ai-gateway.us.posthog.com",
|
|
283
|
+
"ai-gateway.eu.posthog.com"
|
|
284
|
+
];
|
|
285
|
+
const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
|
|
286
|
+
const extractHost = (baseURL) => {
|
|
287
|
+
try {
|
|
288
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
|
|
289
|
+
return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
|
|
290
|
+
} catch {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
294
293
|
};
|
|
295
|
-
const isPostHogAiGatewayUrl = baseURL => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const host = extractHost(baseURL);
|
|
300
|
-
return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
|
|
294
|
+
const isPostHogAiGatewayUrl = (baseURL) => {
|
|
295
|
+
if (!baseURL) return false;
|
|
296
|
+
const host = extractHost(baseURL);
|
|
297
|
+
return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
|
|
301
298
|
};
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const warnIfPostHogAiGateway = baseURL => {
|
|
306
|
-
if (!isPostHogAiGatewayUrl(baseURL)) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
console.warn('[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' + 'Both capture $ai_generation, so every call is double-counted and double-billed. ' + `Use one or the other — see ${GATEWAY_DOCS_URL}.`);
|
|
299
|
+
const warnIfPostHogAiGateway = (baseURL) => {
|
|
300
|
+
if (!isPostHogAiGatewayUrl(baseURL)) return;
|
|
301
|
+
console.warn(`[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. Both capture \$ai_generation, so every call is double-counted and double-billed. Use one or the other — see ${GATEWAY_DOCS_URL}.`);
|
|
310
302
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {
|
|
322
|
-
const value = attributes[key];
|
|
323
|
-
if (typeof value === 'string' && isPostHogAiGatewayUrl(value)) {
|
|
324
|
-
warnIfPostHogAiGateway(value);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
303
|
+
const OTEL_GATEWAY_URL_ATTRIBUTES = ["server.address", "url.full"];
|
|
304
|
+
const warnIfPostHogAiGatewayOtelAttributes = (attributes) => {
|
|
305
|
+
if (!attributes) return;
|
|
306
|
+
for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {
|
|
307
|
+
const value = attributes[key];
|
|
308
|
+
if (typeof value === "string" && isPostHogAiGatewayUrl(value)) {
|
|
309
|
+
warnIfPostHogAiGateway(value);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
328
313
|
};
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
// published subpath does not require the exporter package's transitive dependencies.
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/otel/exporter.ts
|
|
316
|
+
const DEFAULT_OTEL_HOST$1 = "https://us.i.posthog.com";
|
|
333
317
|
const EXPORT_SUCCESS = 0;
|
|
334
318
|
function normalizeToken$1(value) {
|
|
335
|
-
|
|
319
|
+
return typeof value === "string" ? value.trim() : "";
|
|
336
320
|
}
|
|
337
321
|
function normalizeHost$1(value) {
|
|
338
|
-
|
|
339
|
-
return normalizedValue || DEFAULT_OTEL_HOST$1;
|
|
322
|
+
return (typeof value === "string" ? value.trim() : "") || DEFAULT_OTEL_HOST$1;
|
|
340
323
|
}
|
|
341
|
-
|
|
342
324
|
/**
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
});
|
|
400
|
-
this.disabled = disabled;
|
|
401
|
-
if (this.disabled) {
|
|
402
|
-
console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.');
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
export(spans, resultCallback) {
|
|
406
|
-
if (this.disabled) {
|
|
407
|
-
// Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.
|
|
408
|
-
// Reporting failure would make OpenTelemetry treat every span as an export error.
|
|
409
|
-
resultCallback({
|
|
410
|
-
code: EXPORT_SUCCESS
|
|
411
|
-
});
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const aiSpans = spans.filter(isAISpan);
|
|
415
|
-
if (aiSpans.length === 0) {
|
|
416
|
-
resultCallback({
|
|
417
|
-
code: EXPORT_SUCCESS
|
|
418
|
-
});
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
for (const span of aiSpans) {
|
|
422
|
-
warnIfPostHogAiGatewayOtelAttributes(span.attributes);
|
|
423
|
-
}
|
|
424
|
-
super.export(aiSpans.map(redactSpan), resultCallback);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const DEFAULT_OTEL_HOST = 'https://us.i.posthog.com';
|
|
325
|
+
* An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP
|
|
326
|
+
* ingestion endpoint. PostHog converts `gen_ai.*` spans into
|
|
327
|
+
* `$ai_generation` events server-side.
|
|
328
|
+
*
|
|
329
|
+
* Only AI-related spans (those whose name or attribute keys start with
|
|
330
|
+
* `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other
|
|
331
|
+
* spans are silently dropped.
|
|
332
|
+
*
|
|
333
|
+
* Use this when the API you're integrating with only accepts a
|
|
334
|
+
* `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to
|
|
335
|
+
* plug PostHog into an existing processor chain. Otherwise prefer
|
|
336
|
+
* {@link PostHogSpanProcessor}, which is self-contained.
|
|
337
|
+
*
|
|
338
|
+
* `projectToken` is required; a blank token disables the exporter as a defensive no-op.
|
|
339
|
+
* You can also optionally override the `host` URL.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```ts
|
|
343
|
+
* import { PostHogTraceExporter } from '@posthog/ai/otel'
|
|
344
|
+
* import { registerOTel } from '@vercel/otel'
|
|
345
|
+
*
|
|
346
|
+
* registerOTel({
|
|
347
|
+
* serviceName: 'my-app',
|
|
348
|
+
* traceExporter: new PostHogTraceExporter({ projectToken: 'phc_...' }),
|
|
349
|
+
* })
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
var PostHogTraceExporter = class extends _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter {
|
|
353
|
+
constructor(options) {
|
|
354
|
+
const token = normalizeToken$1(options.projectToken);
|
|
355
|
+
const disabled = !token;
|
|
356
|
+
const host = token ? new URL(normalizeHost$1(options.host)).origin : DEFAULT_OTEL_HOST$1;
|
|
357
|
+
super({
|
|
358
|
+
url: `${host}/i/v0/ai/otel`,
|
|
359
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
360
|
+
});
|
|
361
|
+
this.disabled = disabled;
|
|
362
|
+
if (this.disabled) console.warn("[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.");
|
|
363
|
+
}
|
|
364
|
+
export(spans, resultCallback) {
|
|
365
|
+
if (this.disabled) {
|
|
366
|
+
resultCallback({ code: EXPORT_SUCCESS });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const aiSpans = spans.filter(isAISpan);
|
|
370
|
+
if (aiSpans.length === 0) {
|
|
371
|
+
resultCallback({ code: EXPORT_SUCCESS });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
for (const span of aiSpans) warnIfPostHogAiGatewayOtelAttributes(span.attributes);
|
|
375
|
+
super.export(aiSpans.map(redactSpan), resultCallback);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/otel/processor.ts
|
|
380
|
+
const DEFAULT_OTEL_HOST = "https://us.i.posthog.com";
|
|
429
381
|
function normalizeToken(value) {
|
|
430
|
-
|
|
382
|
+
return typeof value === "string" ? value.trim() : "";
|
|
431
383
|
}
|
|
432
384
|
function normalizeHost(value) {
|
|
433
|
-
|
|
434
|
-
return normalizedValue || DEFAULT_OTEL_HOST;
|
|
435
|
-
}
|
|
436
|
-
class NoopSpanProcessor {
|
|
437
|
-
onStart(_span, _parentContext) {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
onEnd(_span) {
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
shutdown() {
|
|
444
|
-
return Promise.resolve();
|
|
445
|
-
}
|
|
446
|
-
forceFlush() {
|
|
447
|
-
return Promise.resolve();
|
|
448
|
-
}
|
|
385
|
+
return (typeof value === "string" ? value.trim() : "") || DEFAULT_OTEL_HOST;
|
|
449
386
|
}
|
|
450
|
-
|
|
387
|
+
var NoopSpanProcessor = class {
|
|
388
|
+
onStart(_span, _parentContext) {}
|
|
389
|
+
onEnd(_span) {}
|
|
390
|
+
shutdown() {
|
|
391
|
+
return Promise.resolve();
|
|
392
|
+
}
|
|
393
|
+
forceFlush() {
|
|
394
|
+
return Promise.resolve();
|
|
395
|
+
}
|
|
396
|
+
};
|
|
451
397
|
/**
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
forceFlush() {
|
|
519
|
-
return this.inner.forceFlush();
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
398
|
+
* An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
|
|
399
|
+
*
|
|
400
|
+
* `projectToken` is required; a blank token disables the processor as a defensive no-op.
|
|
401
|
+
*
|
|
402
|
+
* Internally batches spans and exports them to PostHog's OTLP ingestion
|
|
403
|
+
* endpoint. Only AI-related spans (those whose name or attribute keys
|
|
404
|
+
* start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;
|
|
405
|
+
* all other spans are silently dropped.
|
|
406
|
+
*
|
|
407
|
+
* Request-scoped and serverless runtimes should keep a reference to this
|
|
408
|
+
* processor and await {@link PostHogSpanProcessor.forceFlush} before the request
|
|
409
|
+
* lifecycle ends. This waits for queued exports without sending one request per span.
|
|
410
|
+
*
|
|
411
|
+
* This is the recommended integration point when your setup accepts a
|
|
412
|
+
* `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for
|
|
413
|
+
* Vercel's `registerOTel`), use {@link PostHogTraceExporter}.
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```ts
|
|
417
|
+
* import { PostHogSpanProcessor } from '@posthog/ai/otel'
|
|
418
|
+
* import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
419
|
+
*
|
|
420
|
+
* const processor = new PostHogSpanProcessor({ projectToken: 'phc_...' })
|
|
421
|
+
* const sdk = new NodeSDK({ spanProcessors: [processor] })
|
|
422
|
+
* sdk.start()
|
|
423
|
+
*
|
|
424
|
+
* // In request-scoped runtimes, wait for queued exports before returning.
|
|
425
|
+
* await processor.forceFlush()
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
var PostHogSpanProcessor = class {
|
|
429
|
+
constructor(options) {
|
|
430
|
+
const token = normalizeToken(options.projectToken);
|
|
431
|
+
if (!token) {
|
|
432
|
+
console.warn("[PostHogSpanProcessor] projectToken is missing or blank; the processor will be disabled.");
|
|
433
|
+
this.inner = new NoopSpanProcessor();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (options._spanProcessor) this.inner = options._spanProcessor;
|
|
437
|
+
else {
|
|
438
|
+
const host = new URL(normalizeHost(options.host)).origin;
|
|
439
|
+
const exporter = new _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
440
|
+
url: `${host}/i/v0/ai/otel`,
|
|
441
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
442
|
+
});
|
|
443
|
+
this.inner = new _opentelemetry_sdk_trace_base.BatchSpanProcessor(exporter);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
onStart(span, parentContext) {
|
|
447
|
+
this.inner.onStart(span, parentContext);
|
|
448
|
+
}
|
|
449
|
+
onEnd(span) {
|
|
450
|
+
if (isAISpan(span)) {
|
|
451
|
+
warnIfPostHogAiGatewayOtelAttributes(span.attributes);
|
|
452
|
+
this.inner.onEnd(redactSpan(span));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
shutdown() {
|
|
456
|
+
return this.inner.shutdown();
|
|
457
|
+
}
|
|
458
|
+
forceFlush() {
|
|
459
|
+
return this.inner.forceFlush();
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
//#endregion
|
|
523
463
|
exports.PostHogSpanProcessor = PostHogSpanProcessor;
|
|
524
464
|
exports.PostHogTraceExporter = PostHogTraceExporter;
|
|
525
|
-
|
|
465
|
+
|
|
466
|
+
//# sourceMappingURL=index.cjs.map
|