@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.
Files changed (51) hide show
  1. package/dist/adk/index.cjs +908 -1198
  2. package/dist/adk/index.cjs.map +1 -1
  3. package/dist/adk/index.d.ts +109 -109
  4. package/dist/adk/index.mjs +907 -1196
  5. package/dist/adk/index.mjs.map +1 -1
  6. package/dist/anthropic/index.cjs +927 -1111
  7. package/dist/anthropic/index.cjs.map +1 -1
  8. package/dist/anthropic/index.d.ts +34 -33
  9. package/dist/anthropic/index.mjs +899 -1102
  10. package/dist/anthropic/index.mjs.map +1 -1
  11. package/dist/gemini/index.cjs +863 -1108
  12. package/dist/gemini/index.cjs.map +1 -1
  13. package/dist/gemini/index.d.ts +38 -35
  14. package/dist/gemini/index.mjs +858 -1103
  15. package/dist/gemini/index.mjs.map +1 -1
  16. package/dist/index.cjs +1218 -1546
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +170 -163
  19. package/dist/index.mjs +1216 -1544
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/langchain/index.cjs +851 -1029
  22. package/dist/langchain/index.cjs.map +1 -1
  23. package/dist/langchain/index.d.ts +75 -75
  24. package/dist/langchain/index.mjs +850 -1027
  25. package/dist/langchain/index.mjs.map +1 -1
  26. package/dist/langchain/middleware/index.cjs +1016 -1225
  27. package/dist/langchain/middleware/index.cjs.map +1 -1
  28. package/dist/langchain/middleware/index.d.ts +29 -25
  29. package/dist/langchain/middleware/index.mjs +1015 -1223
  30. package/dist/langchain/middleware/index.mjs.map +1 -1
  31. package/dist/openai/index.cjs +1990 -2523
  32. package/dist/openai/index.cjs.map +1 -1
  33. package/dist/openai/index.d.ts +106 -104
  34. package/dist/openai/index.mjs +1985 -2518
  35. package/dist/openai/index.mjs.map +1 -1
  36. package/dist/openai-agents/index.cjs +745 -827
  37. package/dist/openai-agents/index.cjs.map +1 -1
  38. package/dist/openai-agents/index.d.ts +48 -47
  39. package/dist/openai-agents/index.mjs +744 -825
  40. package/dist/openai-agents/index.mjs.map +1 -1
  41. package/dist/otel/index.cjs +427 -486
  42. package/dist/otel/index.cjs.map +1 -1
  43. package/dist/otel/index.d.ts +36 -35
  44. package/dist/otel/index.mjs +426 -484
  45. package/dist/otel/index.mjs.map +1 -1
  46. package/dist/vercel/index.cjs +992 -1343
  47. package/dist/vercel/index.cjs.map +1 -1
  48. package/dist/vercel/index.d.ts +21 -16
  49. package/dist/vercel/index.mjs +991 -1341
  50. package/dist/vercel/index.mjs.map +1 -1
  51. package/package.json +15 -14
@@ -1,1277 +1,1068 @@
1
- 'use strict';
2
-
3
- var messages = require('@langchain/core/messages');
4
- var function_calling = require('@langchain/core/utils/function_calling');
5
- var types = require('@langchain/core/utils/types');
6
- var langchain = require('langchain');
7
- var uuid = require('uuid');
8
- var zod = require('zod');
9
- var base = require('@langchain/core/callbacks/base');
10
-
11
- // Type guards for safer type checking
12
-
13
- const isObject = value => {
14
- return value !== null && typeof value === 'object' && !Array.isArray(value);
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _langchain_core_messages = require("@langchain/core/messages");
3
+ let _langchain_core_utils_function_calling = require("@langchain/core/utils/function_calling");
4
+ let _langchain_core_utils_types = require("@langchain/core/utils/types");
5
+ let langchain = require("langchain");
6
+ let uuid = require("uuid");
7
+ let zod = require("zod");
8
+ let _langchain_core_callbacks_base = require("@langchain/core/callbacks/base");
9
+ //#region src/typeGuards.ts
10
+ const isObject = (value) => {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15
12
  };
16
-
17
- /** @internal */
18
-
19
- /** @internal */
20
-
13
+ //#endregion
14
+ //#region src/captureAiEvent.ts
21
15
  /** @internal */
22
16
  function isFullAiCaptureEnabled(client) {
23
- return client?.enableFullAiCapture === true;
17
+ return client?.enableFullAiCapture === true;
24
18
  }
25
-
26
19
  /** @internal */
27
20
  function captureAiEvent(client, event) {
28
- if (isFullAiCaptureEnabled(client) && typeof client.captureAi === 'function') {
29
- client.captureAi(event);
30
- return;
31
- }
32
- client.capture(event);
21
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAi === "function") {
22
+ client.captureAi(event);
23
+ return;
24
+ }
25
+ client.capture(event);
33
26
  }
34
-
27
+ //#endregion
28
+ //#region src/sanitization/base64_recognizer.ts
35
29
  const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
36
30
  const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
37
- class Base64Recognizer {
38
- recognize(value, minLength) {
39
- const dataUrl = DATA_URL_PREFIX_RE.exec(value);
40
- if (dataUrl) return {
41
- kind: 'data-url',
42
- mediaType: dataUrl[1]
43
- };
44
- if (value.length < minLength) return {
45
- kind: 'none'
46
- };
47
- const confidencePrefix = value.slice(0, minLength);
48
- if (BASE64_ALPHABET_RE.test(confidencePrefix)) {
49
- return {
50
- kind: 'raw'
51
- };
52
- } else {
53
- return {
54
- kind: 'none'
55
- };
56
- }
57
- }
58
- }
59
-
60
- const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
61
- const STRONG_CONTEXT_KEYS = new Set(['data', 'file_data', 'fileData', 'image_url', 'imageUrl', 'video_url', 'videoUrl', 'audio', 'audio_data', 'audioData', 'inline_data', 'inlineData', 'source', 'result']);
62
- const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
63
- const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
64
- const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
65
- class MediaTypeContext {
66
- static EMPTY = new MediaTypeContext(undefined, undefined);
67
- constructor(parent, key, explicitMediaType) {
68
- this.parent = parent;
69
- this.key = key;
70
- this.explicitMediaType = explicitMediaType;
71
- }
72
- inferMediaType() {
73
- return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
74
- }
75
- inferFromSiblingMime() {
76
- if (this.explicitMediaType) return this.explicitMediaType;
77
- if (!this.parent) return undefined;
78
- for (const hint of MIME_HINT_KEYS) {
79
- const v = this.parent[hint];
80
- if (typeof v === 'string') return v;
81
- }
82
- return undefined;
83
- }
84
- inferFromSiblingFormat() {
85
- if (!this.parent) return undefined;
86
- const fmt = this.parent.format;
87
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
88
- return `audio/${fmt.toLowerCase()}`;
89
- }
90
- return undefined;
91
- }
92
- inferFromParentType() {
93
- if (!this.parent) return undefined;
94
- const t = this.parent.type;
95
- if (typeof t !== 'string') return undefined;
96
- if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
97
- if (t === 'audio' || t === 'input_audio') return 'audio';
98
- if (t === 'video' || t === 'video_url') return 'video';
99
- if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
100
- return undefined;
101
- }
102
- inferFromKey() {
103
- if (!this.key) return undefined;
104
- const key = this.key.toLowerCase();
105
- if (key.includes('audio')) return 'audio';
106
- if (key.includes('video')) return 'video';
107
- if (key.includes('image')) return 'image';
108
- if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
109
- return undefined;
110
- }
111
- hasExplicitBinaryMediaType() {
112
- if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
113
- const mediaType = this.inferFromSiblingMime();
114
- return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/');
115
- }
116
- signalsBinary() {
117
- if (this.explicitMediaType) return true;
118
- if (this.parent) {
119
- for (const hint of MIME_HINT_KEYS) {
120
- if (typeof this.parent[hint] === 'string') return true;
121
- }
122
- const fmt = this.parent.format;
123
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
124
- const t = this.parent.type;
125
- if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
126
- }
127
- if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
128
- return false;
129
- }
130
- }
131
-
31
+ var Base64Recognizer = class {
32
+ recognize(value, minLength) {
33
+ const dataUrl = DATA_URL_PREFIX_RE.exec(value);
34
+ if (dataUrl) return {
35
+ kind: "data-url",
36
+ mediaType: dataUrl[1]
37
+ };
38
+ if (value.length < minLength) return { kind: "none" };
39
+ const confidencePrefix = value.slice(0, minLength);
40
+ if (BASE64_ALPHABET_RE.test(confidencePrefix)) return { kind: "raw" };
41
+ else return { kind: "none" };
42
+ }
43
+ };
44
+ //#endregion
45
+ //#region src/sanitization/media_type_context.ts
46
+ const MIME_HINT_KEYS = [
47
+ "mediaType",
48
+ "media_type",
49
+ "mimeType",
50
+ "mime_type"
51
+ ];
52
+ const STRONG_CONTEXT_KEYS = /* @__PURE__ */ new Set([
53
+ "data",
54
+ "file_data",
55
+ "fileData",
56
+ "image_url",
57
+ "imageUrl",
58
+ "video_url",
59
+ "videoUrl",
60
+ "audio",
61
+ "audio_data",
62
+ "audioData",
63
+ "inline_data",
64
+ "inlineData",
65
+ "source",
66
+ "result"
67
+ ]);
68
+ const STRONG_CONTEXT_TYPES = /* @__PURE__ */ new Set([
69
+ "image",
70
+ "image_url",
71
+ "input_image",
72
+ "audio",
73
+ "input_audio",
74
+ "video",
75
+ "video_url",
76
+ "file",
77
+ "input_file",
78
+ "document",
79
+ "media",
80
+ "file-data"
81
+ ]);
82
+ const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
83
+ "file",
84
+ "input_file",
85
+ "document",
86
+ "media",
87
+ "file-data"
88
+ ]);
89
+ const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
90
+ "wav",
91
+ "mp3",
92
+ "ogg",
93
+ "flac",
94
+ "m4a",
95
+ "aac",
96
+ "webm"
97
+ ]);
98
+ var MediaTypeContext = class MediaTypeContext {
99
+ static {
100
+ this.EMPTY = new MediaTypeContext(void 0, void 0);
101
+ }
102
+ constructor(parent, key, explicitMediaType) {
103
+ this.parent = parent;
104
+ this.key = key;
105
+ this.explicitMediaType = explicitMediaType;
106
+ }
107
+ inferMediaType() {
108
+ return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
109
+ }
110
+ inferFromSiblingMime() {
111
+ if (this.explicitMediaType) return this.explicitMediaType;
112
+ if (!this.parent) return void 0;
113
+ for (const hint of MIME_HINT_KEYS) {
114
+ const v = this.parent[hint];
115
+ if (typeof v === "string") return v;
116
+ }
117
+ }
118
+ inferFromSiblingFormat() {
119
+ if (!this.parent) return void 0;
120
+ const fmt = this.parent.format;
121
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return `audio/${fmt.toLowerCase()}`;
122
+ }
123
+ inferFromParentType() {
124
+ if (!this.parent) return void 0;
125
+ const t = this.parent.type;
126
+ if (typeof t !== "string") return void 0;
127
+ if (t === "image" || t === "image_url" || t === "input_image") return "image";
128
+ if (t === "audio" || t === "input_audio") return "audio";
129
+ if (t === "video" || t === "video_url") return "video";
130
+ if (FILE_FAMILY_TYPES.has(t)) return "application/octet-stream";
131
+ }
132
+ inferFromKey() {
133
+ if (!this.key) return void 0;
134
+ const key = this.key.toLowerCase();
135
+ if (key.includes("audio")) return "audio";
136
+ if (key.includes("video")) return "video";
137
+ if (key.includes("image")) return "image";
138
+ if (key.includes("file") || key.includes("document")) return "application/octet-stream";
139
+ }
140
+ hasExplicitBinaryMediaType() {
141
+ if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
142
+ const mediaType = this.inferFromSiblingMime();
143
+ return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
144
+ }
145
+ signalsBinary() {
146
+ if (this.explicitMediaType) return true;
147
+ if (this.parent) {
148
+ for (const hint of MIME_HINT_KEYS) if (typeof this.parent[hint] === "string") return true;
149
+ const fmt = this.parent.format;
150
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
151
+ const t = this.parent.type;
152
+ if (typeof t === "string" && STRONG_CONTEXT_TYPES.has(t)) return true;
153
+ }
154
+ if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
155
+ return false;
156
+ }
157
+ };
158
+ //#endregion
159
+ //#region src/sanitization/binary_content_redactor.ts
132
160
  const STRONG_CONTEXT_MIN_LENGTH = 64;
133
161
  const WEAK_CONTEXT_MIN_LENGTH = 1024;
134
- class BinaryContentRedactor {
135
- visited = new WeakSet();
136
- constructor(recognizer = new Base64Recognizer()) {
137
- this.recognizer = recognizer;
138
- }
139
- redact(value, mediaType) {
140
- this.visited = new WeakSet();
141
- return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
142
- }
143
- walk(value, ctx) {
144
- if (value === null || value === undefined) return value;
145
- if (typeof value === 'string') return this.redactString(value, ctx);
146
- if (typeof value !== 'object') return value;
147
-
148
- // Buffer extends Uint8Array, so this branch catches both.
149
- if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
150
- return this.placeholderFor(ctx.inferMediaType());
151
- }
152
- if (this.visited.has(value)) return null;
153
- this.visited.add(value);
154
- if (Array.isArray(value)) {
155
- return value.map(item => this.walk(item, ctx));
156
- }
157
- const obj = value;
158
- const out = {};
159
- for (const k of Object.keys(obj)) {
160
- out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
161
- }
162
- return out;
163
- }
164
- redactString(value, ctx) {
165
- const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
166
- const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, '') : value;
167
- const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
168
- const recognition = this.recognizer.recognize(recognitionValue, minLength);
169
- switch (recognition.kind) {
170
- case 'data-url':
171
- return this.placeholderFor(recognition.mediaType);
172
- case 'raw':
173
- return this.placeholderFor(ctx.inferMediaType());
174
- case 'none':
175
- return value;
176
- }
177
- }
178
- placeholderFor(mediaType) {
179
- if (!mediaType) return '[base64 redacted]';
180
- if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
181
- return `[base64 ${mediaType} redacted]`;
182
- }
183
- }
184
-
162
+ var BinaryContentRedactor = class {
163
+ constructor(recognizer = new Base64Recognizer()) {
164
+ this.recognizer = recognizer;
165
+ this.visited = /* @__PURE__ */ new WeakSet();
166
+ }
167
+ redact(value, mediaType) {
168
+ this.visited = /* @__PURE__ */ new WeakSet();
169
+ return this.walk(value, mediaType ? new MediaTypeContext(void 0, void 0, mediaType) : MediaTypeContext.EMPTY);
170
+ }
171
+ walk(value, ctx) {
172
+ if (value === null || value === void 0) return value;
173
+ if (typeof value === "string") return this.redactString(value, ctx);
174
+ if (typeof value !== "object") return value;
175
+ if (typeof Uint8Array !== "undefined" && value instanceof Uint8Array) return this.placeholderFor(ctx.inferMediaType());
176
+ if (this.visited.has(value)) return null;
177
+ this.visited.add(value);
178
+ if (Array.isArray(value)) return value.map((item) => this.walk(item, ctx));
179
+ const obj = value;
180
+ const out = {};
181
+ for (const k of Object.keys(obj)) out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
182
+ return out;
183
+ }
184
+ redactString(value, ctx) {
185
+ const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
186
+ const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
187
+ const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
188
+ const recognition = this.recognizer.recognize(recognitionValue, minLength);
189
+ switch (recognition.kind) {
190
+ case "data-url": return this.placeholderFor(recognition.mediaType);
191
+ case "raw": return this.placeholderFor(ctx.inferMediaType());
192
+ case "none": return value;
193
+ }
194
+ }
195
+ placeholderFor(mediaType) {
196
+ if (!mediaType) return "[base64 redacted]";
197
+ if (mediaType === "application/octet-stream") return "[base64 file redacted]";
198
+ return `[base64 ${mediaType} redacted]`;
199
+ }
200
+ };
201
+ //#endregion
202
+ //#region src/sanitization.ts
185
203
  const redactor = new BinaryContentRedactor();
186
204
  const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
187
205
  const sanitizeLangChain = (data, client) => sanitize(data, client);
188
-
189
- const STRING_FORMAT = 'utf8';
190
-
191
- // Reused across calls to avoid per-invocation allocation; truncate() runs
192
- // hundreds of times for prompts with many parts.
206
+ //#endregion
207
+ //#region src/utils.ts
208
+ const STRING_FORMAT = "utf8";
193
209
  new TextEncoder();
194
- new TextDecoder(STRING_FORMAT, {
195
- fatal: false
196
- });
197
-
210
+ new TextDecoder(STRING_FORMAT, { fatal: false });
198
211
  /**
199
- * Safely converts content to a string, preserving structure for objects/arrays.
200
- * - If content is already a string, returns it as-is
201
- * - If content is an object or array, stringifies it with JSON.stringify to preserve structure
202
- * - Otherwise, converts to string with String()
203
- *
204
- * This prevents the "[object Object]" bug when objects are naively converted to strings.
205
- *
206
- * @param content - The content to convert to a string
207
- * @returns A string representation that preserves structure for complex types
208
- */
212
+ * Safely converts content to a string, preserving structure for objects/arrays.
213
+ * - If content is already a string, returns it as-is
214
+ * - If content is an object or array, stringifies it with JSON.stringify to preserve structure
215
+ * - Otherwise, converts to string with String()
216
+ *
217
+ * This prevents the "[object Object]" bug when objects are naively converted to strings.
218
+ *
219
+ * @param content - The content to convert to a string
220
+ * @returns A string representation that preserves structure for complex types
221
+ */
209
222
  function toContentString(content) {
210
- if (typeof content === 'string') {
211
- return content;
212
- }
213
- if (content !== undefined && content !== null && typeof content === 'object') {
214
- try {
215
- return JSON.stringify(content);
216
- } catch {
217
- // Fallback for circular refs, BigInt, or objects with throwing toJSON
218
- return String(content);
219
- }
220
- }
221
- return String(content);
223
+ if (typeof content === "string") return content;
224
+ if (content !== void 0 && content !== null && typeof content === "object") try {
225
+ return JSON.stringify(content);
226
+ } catch {
227
+ return String(content);
228
+ }
229
+ return String(content);
222
230
  }
223
231
  const getModelParams = (params, responseServiceTier) => {
224
- if (!params) {
225
- return {};
226
- }
227
- const modelParams = {};
228
- const paramKeys = ['temperature', 'max_tokens', 'max_completion_tokens', 'top_p', 'frequency_penalty', 'presence_penalty', 'n', 'stop', 'stream', 'streaming', 'language', 'response_format', 'timestamp_granularities', 'service_tier'];
229
- for (const key of paramKeys) {
230
- if (key in params && params[key] !== undefined) {
231
- modelParams[key] = params[key];
232
- }
233
- }
234
- return modelParams;
232
+ if (!params) return {};
233
+ const modelParams = {};
234
+ for (const key of [
235
+ "temperature",
236
+ "max_tokens",
237
+ "max_completion_tokens",
238
+ "top_p",
239
+ "frequency_penalty",
240
+ "presence_penalty",
241
+ "n",
242
+ "stop",
243
+ "stream",
244
+ "streaming",
245
+ "language",
246
+ "response_format",
247
+ "timestamp_granularities",
248
+ "service_tier"
249
+ ]) if (key in params && params[key] !== void 0) modelParams[key] = params[key];
250
+ if (responseServiceTier != null) modelParams.service_tier = responseServiceTier;
251
+ return modelParams;
235
252
  };
236
253
  const withPrivacyMode = (client, privacyMode, input) => {
237
- return client.privacy_mode || privacyMode ? null : input;
254
+ return client.privacy_mode || privacyMode ? null : input;
238
255
  };
239
256
  function sanitizeValues(obj) {
240
- if (obj === undefined || obj === null) {
241
- return obj;
242
- }
243
- const jsonSafe = JSON.parse(JSON.stringify(obj));
244
- if (typeof jsonSafe === 'string') {
245
- // Sanitize lone surrogates by round-tripping through UTF-8
246
- return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
247
- } else if (Array.isArray(jsonSafe)) {
248
- return jsonSafe.map(sanitizeValues);
249
- } else if (jsonSafe && typeof jsonSafe === 'object') {
250
- return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
251
- }
252
- return jsonSafe;
257
+ if (obj === void 0 || obj === null) return obj;
258
+ const jsonSafe = JSON.parse(JSON.stringify(obj));
259
+ if (typeof jsonSafe === "string") return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
260
+ else if (Array.isArray(jsonSafe)) return jsonSafe.map(sanitizeValues);
261
+ else if (jsonSafe && typeof jsonSafe === "object") return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
262
+ return jsonSafe;
253
263
  }
254
-
255
- var version = "8.10.0";
256
-
264
+ //#endregion
265
+ //#region package.json
266
+ var version = "8.10.2";
267
+ //#endregion
268
+ //#region src/serializeError.ts
257
269
  const DEFAULT_MAX_DEPTH = 3;
258
270
  const MAX_STACK_LINES = 20;
259
271
  function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
260
- if (depth < 0 || value === null || typeof value !== 'object') {
261
- return value;
262
- }
263
- if (value instanceof Error) {
264
- const out = {
265
- name: value.name,
266
- message: value.message,
267
- stack: truncateStack(value.stack)
268
- };
269
- for (const key of Object.keys(value)) {
270
- out[key] = serializeError(value[key], depth - 1);
271
- }
272
- if (value.cause !== undefined) {
273
- out.cause = serializeError(value.cause, depth - 1);
274
- }
275
- return out;
276
- }
277
- if (Array.isArray(value)) {
278
- return value.map(item => serializeError(item, depth - 1));
279
- }
280
- return value;
272
+ if (depth < 0 || value === null || typeof value !== "object") return value;
273
+ if (value instanceof Error) {
274
+ const out = {
275
+ name: value.name,
276
+ message: value.message,
277
+ stack: truncateStack(value.stack)
278
+ };
279
+ for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
280
+ if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
281
+ return out;
282
+ }
283
+ if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
284
+ return value;
281
285
  }
282
286
  function stringifyError(error) {
283
- try {
284
- return JSON.stringify(sanitizeValues(serializeError(error)));
285
- } catch {
286
- if (error instanceof Error) {
287
- return JSON.stringify({
288
- name: error.name,
289
- message: error.message
290
- });
291
- }
292
- return JSON.stringify({
293
- message: String(error)
294
- });
295
- }
287
+ try {
288
+ return JSON.stringify(sanitizeValues(serializeError(error)));
289
+ } catch {
290
+ if (error instanceof Error) return JSON.stringify({
291
+ name: error.name,
292
+ message: error.message
293
+ });
294
+ return JSON.stringify({ message: String(error) });
295
+ }
296
296
  }
297
297
  function truncateStack(stack) {
298
- if (!stack) {
299
- return stack;
300
- }
301
- const lines = stack.split('\n');
302
- if (lines.length <= MAX_STACK_LINES) {
303
- return stack;
304
- }
305
- return [...lines.slice(0, MAX_STACK_LINES), '... (truncated)'].join('\n');
298
+ if (!stack) return stack;
299
+ const lines = stack.split("\n");
300
+ if (lines.length <= MAX_STACK_LINES) return stack;
301
+ return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
306
302
  }
307
-
308
- // Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway
309
- // emits its own $ai_generation, so each call would be captured (and, for billable
310
- // products, billed) twice. We only warn — the wrapper's event carries data the
311
- // gateway never sees (groups, custom properties, trace hierarchy).
312
-
313
- // Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the
314
- // main repo). gateway.us.posthog.com is live today; the rest are listed ahead of
315
- // any traffic moving to them.
316
- const POSTHOG_AI_GATEWAY_HOSTS = ['gateway.posthog.com', 'gateway.us.posthog.com', 'gateway.eu.posthog.com', 'ai-gateway.us.posthog.com', 'ai-gateway.eu.posthog.com'];
317
-
318
- // Swap for the dedicated AI Gateway page once it ships.
319
- const GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability';
320
- const extractHost = baseURL => {
321
- try {
322
- // Tolerate bare hosts that omit a scheme, e.g. "gateway.us.posthog.com/v1".
323
- const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
324
- return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
325
- } catch {
326
- return undefined;
327
- }
303
+ //#endregion
304
+ //#region src/gatewayWarning.ts
305
+ const POSTHOG_AI_GATEWAY_HOSTS = [
306
+ "gateway.posthog.com",
307
+ "gateway.us.posthog.com",
308
+ "gateway.eu.posthog.com",
309
+ "ai-gateway.us.posthog.com",
310
+ "ai-gateway.eu.posthog.com"
311
+ ];
312
+ const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
313
+ const extractHost = (baseURL) => {
314
+ try {
315
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
316
+ return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
317
+ } catch {
318
+ return;
319
+ }
328
320
  };
329
- const isPostHogAiGatewayUrl = baseURL => {
330
- if (!baseURL) {
331
- return false;
332
- }
333
- const host = extractHost(baseURL);
334
- return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
321
+ const isPostHogAiGatewayUrl = (baseURL) => {
322
+ if (!baseURL) return false;
323
+ const host = extractHost(baseURL);
324
+ return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
335
325
  };
336
-
337
- // Warns on every gateway call by design: the misconfiguration is impossible to
338
- // miss that way, and a doubled bill is worse than noisy logs.
339
- const warnIfPostHogAiGateway = baseURL => {
340
- if (!isPostHogAiGatewayUrl(baseURL)) {
341
- return;
342
- }
343
- 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}.`);
326
+ const warnIfPostHogAiGateway = (baseURL) => {
327
+ if (!isPostHogAiGatewayUrl(baseURL)) return;
328
+ 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}.`);
344
329
  };
345
-
346
- // Mirror LangGraph's isGraphBubbleUp guard without adding LangGraph as a dependency. Every
347
- // LangGraph control-flow exception (GraphInterrupt, NodeInterrupt, ParentCommand, GraphDrained,
348
- // and future subclasses) exposes a prototype getter `is_bubble_up` that returns true, which the
349
- // LangGraph runtime itself uses to distinguish control flow from real failures. The getter reads
350
- // as undefined on ordinary Errors and works across duplicated LangGraph package copies.
351
- const isLangGraphControlFlow = error => error.is_bubble_up === true;
352
-
353
- /** A run may either be a Span or a Generation */
354
-
355
- /** Storage for run metadata */
356
-
357
- class LangChainCallbackHandler extends base.BaseCallbackHandler {
358
- name = 'PosthogCallbackHandler';
359
- runs = {};
360
- parentTree = {};
361
- constructor(options) {
362
- if (!options.client) {
363
- throw new Error('PostHog client is required');
364
- }
365
- super();
366
- this.client = options.client;
367
- this.distinctId = options.distinctId;
368
- this.traceId = options.traceId;
369
- this.properties = options.properties || {};
370
- this.privacyMode = options.privacyMode || false;
371
- this.groups = options.groups || {};
372
- this.debug = options.debug || false;
373
- }
374
-
375
- // ===== CALLBACK METHODS =====
376
-
377
- handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, runName, extra) {
378
- this._logDebugEvent('on_chain_start', runId, parentRunId, {
379
- inputs,
380
- tags
381
- });
382
- this._setParentOfRun(runId, parentRunId);
383
- this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName);
384
- if (typeof extra?.posthogStartTime === 'number' && Number.isFinite(extra.posthogStartTime)) {
385
- this.runs[runId].startTime = extra.posthogStartTime;
386
- }
387
- }
388
- handleChainEnd(outputs, runId, parentRunId, tags, _kwargs) {
389
- this._logAndPopTraceOrSpan('on_chain_end', runId, parentRunId, {
390
- outputs,
391
- tags
392
- }, outputs);
393
- }
394
- handleChainError(error, runId, parentRunId, tags, _kwargs) {
395
- this._logAndPopTraceOrSpan('on_chain_error', runId, parentRunId, {
396
- error,
397
- tags
398
- }, error);
399
- }
400
- handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata, runName) {
401
- this._logDebugEvent('on_chat_model_start', runId, parentRunId, {
402
- messages,
403
- tags
404
- });
405
- this._setParentOfRun(runId, parentRunId);
406
- // Flatten the two-dimensional messages and convert each message to a plain object
407
- const input = messages.flat().map(m => this._convertMessageToDict(m));
408
- this._setLLMMetadata(serialized, runId, input, metadata, extraParams, runName);
409
- }
410
- handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata, runName) {
411
- this._logDebugEvent('on_llm_start', runId, parentRunId, {
412
- prompts,
413
- tags
414
- });
415
- this._setParentOfRun(runId, parentRunId);
416
- this._setLLMMetadata(serialized, runId, prompts, metadata, extraParams, runName);
417
- }
418
- handleLLMEnd(output, runId, parentRunId, tags, _extraParams) {
419
- this._logAndPopGeneration('on_llm_end', runId, parentRunId, {
420
- output,
421
- tags
422
- }, output);
423
- }
424
- handleLLMError(err, runId, parentRunId, tags, _extraParams) {
425
- this._logAndPopGeneration('on_llm_error', runId, parentRunId, {
426
- err,
427
- tags
428
- }, err);
429
- }
430
- handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
431
- this._logAndSetTraceOrSpan('on_tool_start', tool, input, runId, parentRunId, {
432
- input,
433
- tags
434
- }, tags, metadata, runName);
435
- }
436
- handleToolEnd(output, runId, parentRunId, tags) {
437
- this._logAndPopTraceOrSpan('on_tool_end', runId, parentRunId, {
438
- output,
439
- tags
440
- }, output);
441
- }
442
- handleToolError(err, runId, parentRunId, tags) {
443
- this._logAndPopTraceOrSpan('on_tool_error', runId, parentRunId, {
444
- err,
445
- tags
446
- }, err);
447
- }
448
- handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
449
- this._logAndSetTraceOrSpan('on_retriever_start', retriever, query, runId, parentRunId, {
450
- query,
451
- tags
452
- }, tags, metadata, name);
453
- }
454
- handleRetrieverEnd(documents, runId, parentRunId, tags) {
455
- this._logAndPopTraceOrSpan('on_retriever_end', runId, parentRunId, {
456
- documents,
457
- tags
458
- }, documents);
459
- }
460
- handleRetrieverError(err, runId, parentRunId, tags) {
461
- this._logAndPopTraceOrSpan('on_retriever_error', runId, parentRunId, {
462
- err,
463
- tags
464
- }, err);
465
- }
466
- handleAgentAction(action, runId, parentRunId, tags) {
467
- this._logDebugEvent('on_agent_action', runId, parentRunId, {
468
- action,
469
- tags
470
- });
471
- this._setParentOfRun(runId, parentRunId);
472
- this._setTraceOrSpanMetadata(null, action, runId, parentRunId);
473
- }
474
- handleAgentEnd(action, runId, parentRunId, tags) {
475
- this._logDebugEvent('on_agent_finish', runId, parentRunId, {
476
- action,
477
- tags
478
- });
479
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, action);
480
- }
481
-
482
- // ===== PRIVATE HELPERS =====
483
-
484
- _logAndSetTraceOrSpan(eventName, serialized, input, runId, parentRunId, debugPayload, tags, metadata, runName) {
485
- this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
486
- this._setParentOfRun(runId, parentRunId);
487
- this._setTraceOrSpanMetadata(serialized, input, runId, parentRunId, metadata, tags, runName);
488
- }
489
- _logAndPopTraceOrSpan(eventName, runId, parentRunId, debugPayload, result) {
490
- this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
491
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, result);
492
- }
493
- _logAndPopGeneration(eventName, runId, parentRunId, debugPayload, result) {
494
- this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
495
- this._popRunAndCaptureGeneration(runId, parentRunId, result);
496
- }
497
- _setParentOfRun(runId, parentRunId) {
498
- if (parentRunId) {
499
- this.parentTree[runId] = parentRunId;
500
- }
501
- }
502
- _popParentOfRun(runId) {
503
- delete this.parentTree[runId];
504
- }
505
- _findRootRun(runId) {
506
- let id = runId;
507
- while (this.parentTree[id]) {
508
- id = this.parentTree[id];
509
- }
510
- return id;
511
- }
512
- _setTraceOrSpanMetadata(serialized, input, runId, parentRunId, ...args) {
513
- // Use default names if not provided: if this is a top-level run, we mark it as a trace, otherwise as a span.
514
- const defaultName = parentRunId ? 'span' : 'trace';
515
- const runName = this._getLangchainRunName(serialized, ...args) || defaultName;
516
- this.runs[runId] = {
517
- name: runName,
518
- input,
519
- startTime: Date.now()
520
- };
521
- }
522
- _setLLMMetadata(serialized, runId, messages, metadata, extraParams, runName) {
523
- const runNameFound = this._getLangchainRunName(serialized, {
524
- extraParams,
525
- runName
526
- }) || 'generation';
527
- const generation = {
528
- name: runNameFound,
529
- input: sanitizeLangChain(messages, this.client),
530
- startTime: Date.now()
531
- };
532
- if (extraParams) {
533
- generation.modelParams = getModelParams(extraParams.invocation_params);
534
- if (extraParams.invocation_params && extraParams.invocation_params.tools) {
535
- generation.tools = extraParams.invocation_params.tools;
536
- }
537
- }
538
- if (metadata) {
539
- if (metadata.ls_model_name) {
540
- generation.model = metadata.ls_model_name;
541
- }
542
- if (metadata.ls_provider) {
543
- generation.provider = metadata.ls_provider;
544
- }
545
- }
546
- if (serialized && 'kwargs' in serialized && serialized.kwargs.openai_api_base) {
547
- generation.baseUrl = serialized.kwargs.openai_api_base;
548
- }
549
- this.runs[runId] = generation;
550
- }
551
- _popRunMetadata(runId) {
552
- const endTime = Date.now();
553
- const run = this.runs[runId];
554
- if (!run) {
555
- console.warn(`No run metadata found for run ${runId}`);
556
- return undefined;
557
- }
558
- run.endTime = endTime;
559
- delete this.runs[runId];
560
- return run;
561
- }
562
- _getTraceId(runId) {
563
- return this.traceId ? String(this.traceId) : this._findRootRun(runId);
564
- }
565
- _getParentRunId(traceId, _runId, parentRunId) {
566
- // Replace the parent-run if not found in our stored parent tree.
567
- if (parentRunId && !this.parentTree[parentRunId]) {
568
- return traceId;
569
- }
570
- return parentRunId;
571
- }
572
- _safeCapture(message) {
573
- try {
574
- captureAiEvent(this.client, message);
575
- } catch {
576
- // Telemetry delivery must never affect the LangChain callback lifecycle.
577
- }
578
- }
579
- _popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs) {
580
- const traceId = this._getTraceId(runId);
581
- const isSpan = Boolean(parentRunId || this.parentTree[runId]);
582
- this._popParentOfRun(runId);
583
- const run = this._popRunMetadata(runId);
584
- if (!run) {
585
- return;
586
- }
587
- if ('modelParams' in run) {
588
- console.warn(`Run ${runId} is a generation, but attempted to be captured as a trace/span.`);
589
- return;
590
- }
591
- const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
592
- this._captureTraceOrSpan(traceId, runId, run, outputs, isSpan, actualParentRunId);
593
- }
594
- _captureTraceOrSpan(traceId, runId, run, outputs, isSpan, parentRunId) {
595
- const eventName = isSpan ? '$ai_span' : '$ai_trace';
596
- const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
597
- const eventProperties = {
598
- $ai_lib: 'posthog-ai',
599
- $ai_lib_version: version,
600
- $ai_trace_id: traceId,
601
- $ai_input_state: withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(run.input, this.client)),
602
- $ai_latency: latency,
603
- $ai_span_name: run.name,
604
- $ai_span_id: runId,
605
- $ai_framework: 'langchain'
606
- };
607
- if (parentRunId) {
608
- eventProperties['$ai_parent_id'] = parentRunId;
609
- }
610
- Object.assign(eventProperties, this.properties);
611
- if (!this.distinctId) {
612
- eventProperties['$process_person_profile'] = false;
613
- }
614
- if (outputs instanceof Error) {
615
- if (isLangGraphControlFlow(outputs)) {
616
- // GraphInterrupt carries the pending interrupts (e.g. the question posed to a human).
617
- // Surface them under the same `__interrupt__` key LangGraph hands back to the caller,
618
- // so an interrupted span stays distinguishable from a node that returned nothing.
619
- const interrupts = outputs.interrupts;
620
- if (interrupts !== undefined) {
621
- eventProperties['$ai_output_state'] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain({
622
- __interrupt__: interrupts
623
- }, this.client));
624
- }
625
- } else {
626
- eventProperties['$ai_error'] = stringifyError(outputs);
627
- eventProperties['$ai_is_error'] = true;
628
- }
629
- } else if (outputs !== undefined) {
630
- eventProperties['$ai_output_state'] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(outputs, this.client));
631
- }
632
- this._safeCapture({
633
- distinctId: this.distinctId ? this.distinctId.toString() : runId,
634
- event: eventName,
635
- properties: eventProperties,
636
- groups: this.groups
637
- });
638
- }
639
- _popRunAndCaptureGeneration(runId, parentRunId, response) {
640
- const traceId = this._getTraceId(runId);
641
- this._popParentOfRun(runId);
642
- const run = this._popRunMetadata(runId);
643
- if (!run || typeof run !== 'object' || !('modelParams' in run)) {
644
- console.warn(`Run ${runId} is not a generation, but attempted to be captured as such.`);
645
- return;
646
- }
647
- const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
648
- this._captureGeneration(traceId, runId, run, response, actualParentRunId);
649
- }
650
- _captureGeneration(traceId, runId, run, output, parentRunId) {
651
- const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
652
- warnIfPostHogAiGateway(run.baseUrl);
653
- const eventProperties = {
654
- $ai_lib: 'posthog-ai',
655
- $ai_lib_version: version,
656
- $ai_trace_id: traceId,
657
- $ai_span_id: runId,
658
- $ai_span_name: run.name,
659
- $ai_provider: run.provider,
660
- $ai_model: run.model,
661
- $ai_model_parameters: run.modelParams,
662
- $ai_input: withPrivacyMode(this.client, this.privacyMode, run.input),
663
- $ai_http_status: 200,
664
- $ai_latency: latency,
665
- $ai_base_url: run.baseUrl,
666
- $ai_framework: 'langchain'
667
- };
668
- if (parentRunId) {
669
- eventProperties['$ai_parent_id'] = parentRunId;
670
- }
671
- if (run.tools) {
672
- eventProperties['$ai_tools'] = run.tools;
673
- }
674
- if (output instanceof Error) {
675
- eventProperties['$ai_http_status'] = output.status || 500;
676
- eventProperties['$ai_error'] = stringifyError(output);
677
- eventProperties['$ai_is_error'] = true;
678
- } else {
679
- // Handle token usage
680
- const [inputTokens, outputTokens, additionalTokenData] = this.parseUsage(output, run.provider, run.model);
681
- eventProperties['$ai_input_tokens'] = inputTokens;
682
- eventProperties['$ai_output_tokens'] = outputTokens;
683
-
684
- // Add additional token data to properties
685
- if (additionalTokenData.cacheReadInputTokens) {
686
- eventProperties['$ai_cache_read_input_tokens'] = additionalTokenData.cacheReadInputTokens;
687
- }
688
- if (additionalTokenData.cacheWriteInputTokens) {
689
- eventProperties['$ai_cache_creation_input_tokens'] = additionalTokenData.cacheWriteInputTokens;
690
- }
691
- if (additionalTokenData.cacheWrite5mInputTokens !== undefined && additionalTokenData.cacheWrite1hInputTokens !== undefined) {
692
- eventProperties['$ai_cache_creation_5m_input_tokens'] = additionalTokenData.cacheWrite5mInputTokens;
693
- eventProperties['$ai_cache_creation_1h_input_tokens'] = additionalTokenData.cacheWrite1hInputTokens;
694
- }
695
- if (additionalTokenData.reasoningTokens) {
696
- eventProperties['$ai_reasoning_tokens'] = additionalTokenData.reasoningTokens;
697
- }
698
- if (additionalTokenData.webSearchCount !== undefined) {
699
- eventProperties['$ai_web_search_count'] = additionalTokenData.webSearchCount;
700
- }
701
-
702
- // Extract stop reason from generation info
703
- const stopReason = this._extractStopReason(output);
704
- if (stopReason) {
705
- eventProperties['$ai_stop_reason'] = stopReason;
706
- }
707
-
708
- // Handle generations/completions
709
- let completions;
710
- if (output.generations && Array.isArray(output.generations)) {
711
- const lastGeneration = output.generations[output.generations.length - 1];
712
- if (Array.isArray(lastGeneration) && lastGeneration.length > 0) {
713
- // Check if this is a ChatGeneration by looking at the first item
714
- const isChatGeneration = 'message' in lastGeneration[0] && lastGeneration[0].message;
715
- if (isChatGeneration) {
716
- // For ChatGeneration, convert messages to dict format
717
- completions = lastGeneration.map(gen => {
718
- return this._convertMessageToDict(gen.message);
719
- });
720
- } else {
721
- // For non-ChatGeneration, extract raw response
722
- completions = lastGeneration.map(gen => {
723
- return this._extractRawResponse(gen);
724
- });
725
- }
726
- }
727
- }
728
- if (completions) {
729
- eventProperties['$ai_output_choices'] = withPrivacyMode(this.client, this.privacyMode, completions);
730
- }
731
- }
732
- Object.assign(eventProperties, this.properties);
733
- if (!this.distinctId) {
734
- eventProperties['$process_person_profile'] = false;
735
- }
736
- this._safeCapture({
737
- distinctId: this.distinctId ? this.distinctId.toString() : traceId,
738
- event: '$ai_generation',
739
- properties: eventProperties,
740
- groups: this.groups
741
- });
742
- }
743
- _logDebugEvent(eventName, runId, parentRunId, extra) {
744
- if (this.debug) {
745
- console.log(`Event: ${eventName}, runId: ${runId}, parentRunId: ${parentRunId}, extra:`, extra);
746
- }
747
- }
748
- _getLangchainRunName(serialized, ...args) {
749
- if (args && args.length > 0) {
750
- for (const arg of args) {
751
- // LangChain hands runName through as a bare string, not wrapped in an object
752
- if (typeof arg === 'string' && arg) {
753
- return arg;
754
- }
755
- if (arg && typeof arg === 'object') {
756
- if (arg.name) {
757
- return arg.name;
758
- }
759
- if (arg.runName) {
760
- return arg.runName;
761
- }
762
- }
763
- }
764
- }
765
- if (serialized && serialized.name) {
766
- return serialized.name;
767
- }
768
- if (serialized && serialized.id) {
769
- return Array.isArray(serialized.id) ? serialized.id[serialized.id.length - 1] : serialized.id;
770
- }
771
- return undefined;
772
- }
773
- _convertLcToolCallsToOai(toolCalls) {
774
- return toolCalls.map(toolCall => ({
775
- type: 'function',
776
- id: toolCall.id,
777
- function: {
778
- name: toolCall.name,
779
- arguments: JSON.stringify(toolCall.args)
780
- }
781
- }));
782
- }
783
- _extractRawResponse(generation) {
784
- // Extract the response from the last response of the LLM call
785
- // We return the text of the response if not empty
786
- if (generation.text != null && generation.text.trim() !== '') {
787
- return generation.text.trim();
788
- } else if (generation.message) {
789
- // Additional kwargs contains the response in case of tool usage
790
- return generation.message.additional_kwargs || generation.message.additionalKwargs || {};
791
- } else {
792
- // Not tool usage, some LLM responses can be simply empty
793
- return '';
794
- }
795
- }
796
- _convertMessageToDict(message) {
797
- let messageDict = {};
798
- const messageType = message.getType();
799
- switch (messageType) {
800
- case 'human':
801
- messageDict = {
802
- role: 'user',
803
- content: message.content
804
- };
805
- break;
806
- case 'ai':
807
- messageDict = {
808
- role: 'assistant',
809
- content: message.content
810
- };
811
- if (message.tool_calls) {
812
- messageDict.tool_calls = this._convertLcToolCallsToOai(message.tool_calls);
813
- }
814
- break;
815
- case 'system':
816
- messageDict = {
817
- role: 'system',
818
- content: message.content
819
- };
820
- break;
821
- case 'tool':
822
- messageDict = {
823
- role: 'tool',
824
- content: message.content
825
- };
826
- break;
827
- case 'function':
828
- messageDict = {
829
- role: 'function',
830
- content: message.content
831
- };
832
- break;
833
- default:
834
- messageDict = {
835
- role: messageType,
836
- content: toContentString(message.content)
837
- };
838
- break;
839
- }
840
- if (message.additional_kwargs) {
841
- messageDict = {
842
- ...messageDict,
843
- ...message.additional_kwargs
844
- };
845
- }
846
-
847
- // Sanitize the message content to redact base64 images
848
- return sanitizeLangChain(messageDict, this.client);
849
- }
850
- _extractStopReason(output) {
851
- if (!output.generations || !Array.isArray(output.generations)) {
852
- return undefined;
853
- }
854
- const lastGeneration = output.generations[output.generations.length - 1];
855
- if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) {
856
- return undefined;
857
- }
858
- const gen = lastGeneration[0];
859
- const messageResponseMetadata = gen.message?.response_metadata;
860
- const generationResponseMetadata = gen.generationInfo?.response_metadata;
861
- const stopReason = messageResponseMetadata?.finish_reason || messageResponseMetadata?.stop_reason || gen.generationInfo?.finish_reason || generationResponseMetadata?.stop_reason || generationResponseMetadata?.finish_reason || gen.generationInfo?.stop_reason ||
862
- // The Responses API reports no finish_reason. An early stop is named by
863
- // `incomplete_details.reason`, and `status` covers the rest, matching the
864
- // native OpenAI Responses wrapper.
865
- messageResponseMetadata?.incomplete_details?.reason || generationResponseMetadata?.incomplete_details?.reason || messageResponseMetadata?.status || generationResponseMetadata?.status;
866
- return stopReason != null ? String(stopReason) : undefined;
867
- }
868
- _extractCacheCreationTtlBreakdown(cacheCreation, aggregateValues) {
869
- if (!isObject(cacheCreation)) {
870
- return undefined;
871
- }
872
- const {
873
- ephemeral_5m_input_tokens: cache5m,
874
- ephemeral_1h_input_tokens: cache1h
875
- } = cacheCreation;
876
- const providedValues = [cache5m, cache1h].filter(value => value != null);
877
- if (providedValues.length === 0 || !providedValues.every(value => typeof value === 'number' && Number.isFinite(value) && value >= 0)) {
878
- return undefined;
879
- }
880
- const breakdown = [typeof cache5m === 'number' ? cache5m : 0, typeof cache1h === 'number' ? cache1h : 0];
881
- const total = breakdown[0] + breakdown[1];
882
- const validAggregates = aggregateValues.filter(value => typeof value === 'number' && Number.isFinite(value) && value >= 0);
883
- return total > 0 && !validAggregates.some(aggregate => aggregate !== total) ? breakdown : undefined;
884
- }
885
- _extractBedrockCacheCreationTtlBreakdown(cacheDetails, aggregateValues) {
886
- if (!Array.isArray(cacheDetails)) {
887
- return undefined;
888
- }
889
- let cache5m = 0;
890
- let cache1h = 0;
891
- for (const detail of cacheDetails) {
892
- if (!isObject(detail)) {
893
- continue;
894
- }
895
- const ttl = typeof detail.ttl === 'string' ? detail.ttl.toLowerCase() : undefined;
896
- const inputTokens = detail.inputTokens;
897
- if (ttl !== '5m' && ttl !== 't5m' && ttl !== '1h' && ttl !== 't1h' || typeof inputTokens !== 'number' || !Number.isFinite(inputTokens) || inputTokens < 0) {
898
- continue;
899
- }
900
- if (ttl === '5m' || ttl === 't5m') {
901
- cache5m += inputTokens;
902
- } else {
903
- cache1h += inputTokens;
904
- }
905
- }
906
- const total = cache5m + cache1h;
907
- const validAggregates = aggregateValues.filter(value => typeof value === 'number' && Number.isFinite(value) && value >= 0);
908
- if (total === 0 || validAggregates.some(aggregate => aggregate !== total)) {
909
- return undefined;
910
- }
911
- return [cache5m, cache1h];
912
- }
913
- _parseUsageModel(usage, provider, model, inputIncludesCacheTokens = true, rawUsage) {
914
- const conversionList = [['promptTokens', 'input'], ['completionTokens', 'output'], ['input_tokens', 'input'], ['output_tokens', 'output'], ['prompt_token_count', 'input'], ['candidates_token_count', 'output'], ['inputTokenCount', 'input'], ['outputTokenCount', 'output'], ['input_token_count', 'input'], ['generated_token_count', 'output']];
915
- const parsedUsage = conversionList.reduce((acc, [modelKey, typeKey]) => {
916
- const value = usage[modelKey];
917
- if (value != null) {
918
- const finalCount = Array.isArray(value) ? value.reduce((sum, tokenCount) => sum + tokenCount, 0) : value;
919
- acc[typeKey] = finalCount;
920
- }
921
- return acc;
922
- }, {
923
- input: 0,
924
- output: 0
925
- });
926
-
927
- // Extract additional token details like cached tokens and reasoning tokens
928
- const additionalTokenData = {};
929
-
930
- // Check for cached tokens in various formats
931
- if (usage.prompt_tokens_details?.cached_tokens != null) {
932
- additionalTokenData.cacheReadInputTokens = usage.prompt_tokens_details.cached_tokens;
933
- } else if (usage.input_token_details?.cache_read != null) {
934
- additionalTokenData.cacheReadInputTokens = usage.input_token_details.cache_read;
935
- } else if (usage.cachedPromptTokens != null) {
936
- additionalTokenData.cacheReadInputTokens = usage.cachedPromptTokens;
937
- } else if (usage.cache_read_input_tokens != null) {
938
- additionalTokenData.cacheReadInputTokens = usage.cache_read_input_tokens;
939
- }
940
-
941
- // Check for cache write/creation tokens in various formats
942
- if (usage.cache_creation_input_tokens != null) {
943
- additionalTokenData.cacheWriteInputTokens = usage.cache_creation_input_tokens;
944
- } else if (usage.input_token_details?.cache_creation != null) {
945
- additionalTokenData.cacheWriteInputTokens = usage.input_token_details.cache_creation;
946
- }
947
- const directCacheCreationAggregates = [usage.cache_creation_input_tokens, usage.input_token_details?.cache_creation, usage.cacheWriteInputTokens, rawUsage?.cache_creation_input_tokens, rawUsage?.input_token_details?.cache_creation, rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens];
948
- const cacheCreationTtl = this._extractCacheCreationTtlBreakdown(usage.cache_creation, directCacheCreationAggregates) ?? this._extractCacheCreationTtlBreakdown(rawUsage?.cache_creation, directCacheCreationAggregates) ?? this._extractBedrockCacheCreationTtlBreakdown(usage.cacheDetails, [usage.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]) ?? this._extractBedrockCacheCreationTtlBreakdown(rawUsage?.cacheDetails, [rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]);
949
- if (cacheCreationTtl) {
950
- const [cacheWrite5mInputTokens, cacheWrite1hInputTokens] = cacheCreationTtl;
951
- additionalTokenData.cacheWrite5mInputTokens = cacheWrite5mInputTokens;
952
- additionalTokenData.cacheWrite1hInputTokens = cacheWrite1hInputTokens;
953
- additionalTokenData.cacheWriteInputTokens = cacheWrite5mInputTokens + cacheWrite1hInputTokens;
954
- }
955
-
956
- // Check for reasoning tokens in various formats
957
- if (usage.completion_tokens_details?.reasoning_tokens != null) {
958
- additionalTokenData.reasoningTokens = usage.completion_tokens_details.reasoning_tokens;
959
- } else if (usage.output_token_details?.reasoning != null) {
960
- additionalTokenData.reasoningTokens = usage.output_token_details.reasoning;
961
- } else if (usage.reasoningTokens != null) {
962
- additionalTokenData.reasoningTokens = usage.reasoningTokens;
963
- }
964
-
965
- // Extract web search counts from various provider formats
966
- let webSearchCount;
967
-
968
- // Priority 1: Exact Count
969
- // Check Anthropic format (server_tool_use.web_search_requests)
970
- if (usage.server_tool_use?.web_search_requests !== undefined) {
971
- webSearchCount = usage.server_tool_use.web_search_requests;
972
- }
973
- // Priority 2: Binary Detection (1 or 0)
974
- // Check for citations array (Perplexity)
975
- else if (usage.citations && Array.isArray(usage.citations) && usage.citations.length > 0) {
976
- webSearchCount = 1;
977
- }
978
- // Check for search_results array (Perplexity via OpenRouter)
979
- else if (usage.search_results && Array.isArray(usage.search_results) && usage.search_results.length > 0) {
980
- webSearchCount = 1;
981
- }
982
- // Check for search_context_size (Perplexity via OpenRouter)
983
- else if (usage.search_context_size) {
984
- webSearchCount = 1;
985
- }
986
- // Check for annotations with url_citation type
987
- else if (usage.annotations && Array.isArray(usage.annotations)) {
988
- const hasUrlCitation = usage.annotations.some(ann => {
989
- return ann && typeof ann === 'object' && 'type' in ann && ann.type === 'url_citation';
990
- });
991
- if (hasUrlCitation) {
992
- webSearchCount = 1;
993
- }
994
- }
995
- // Check Gemini format (grounding metadata - binary 0 or 1)
996
- else if (usage.grounding_metadata?.grounding_support !== undefined || usage.grounding_metadata?.web_search_queries !== undefined) {
997
- webSearchCount = 1;
998
- }
999
- if (webSearchCount !== undefined) {
1000
- additionalTokenData.webSearchCount = webSearchCount;
1001
- }
1002
-
1003
- // For Anthropic providers, LangChain reports input_tokens as the sum of all input tokens.
1004
- // Our cost calculation expects them to be separate for Anthropic, so we subtract cache tokens.
1005
- // Both cache_read and cache_write tokens should be subtracted since Anthropic's raw API
1006
- // reports input_tokens as tokens NOT read from or used to create a cache.
1007
- // For other providers (OpenAI, etc.), input_tokens already excludes cache tokens as expected.
1008
- // Match logic consistent with plugin-server: exact match on provider OR substring match on model
1009
- let isAnthropic = false;
1010
- if (provider && provider.toLowerCase() === 'anthropic') {
1011
- isAnthropic = true;
1012
- } else if (model && model.toLowerCase().includes('anthropic')) {
1013
- isAnthropic = true;
1014
- }
1015
- if (isAnthropic && inputIncludesCacheTokens && parsedUsage.input) {
1016
- const cacheTokens = (additionalTokenData.cacheReadInputTokens || 0) + (additionalTokenData.cacheWriteInputTokens || 0);
1017
- if (cacheTokens > 0) {
1018
- parsedUsage.input = Math.max(parsedUsage.input - cacheTokens, 0);
1019
- }
1020
- }
1021
- return [parsedUsage.input, parsedUsage.output, additionalTokenData];
1022
- }
1023
- parseUsage(response, provider, model) {
1024
- const isNonEmptyUsage = usage => isObject(usage) && Object.keys(usage).length > 0;
1025
- const firstNonEmptyUsage = (...candidates) => candidates.find(isNonEmptyUsage);
1026
- let normalizedGenerationUsage;
1027
- let rawGenerationUsage;
1028
- let fallbackGenerationUsage;
1029
- for (const generation of response.generations ?? []) {
1030
- for (const genChunk of generation) {
1031
- const generationInfo = genChunk.generationInfo ?? {};
1032
- const message = 'message' in genChunk ? genChunk.message : undefined;
1033
- const messageUsage = message && typeof message === 'object' && 'usage_metadata' in message ? message.usage_metadata : undefined;
1034
- normalizedGenerationUsage = firstNonEmptyUsage(normalizedGenerationUsage, messageUsage, generationInfo.usage_metadata);
1035
- const messageResponseMetadata = message && typeof message === 'object' && 'response_metadata' in message && isObject(message.response_metadata) ? message.response_metadata : undefined;
1036
- const generationResponseMetadata = isObject(generationInfo.response_metadata) ? generationInfo.response_metadata : undefined;
1037
- const messageStreamMetadata = isObject(messageResponseMetadata?.metadata) ? messageResponseMetadata.metadata : undefined;
1038
- const generationStreamMetadata = isObject(generationResponseMetadata?.metadata) ? generationResponseMetadata.metadata : undefined;
1039
- rawGenerationUsage = firstNonEmptyUsage(rawGenerationUsage, messageResponseMetadata?.usage, messageStreamMetadata?.usage, generationResponseMetadata?.usage, generationStreamMetadata?.usage);
1040
- fallbackGenerationUsage = firstNonEmptyUsage(fallbackGenerationUsage, messageResponseMetadata?.['amazon-bedrock-invocationMetrics'], generationResponseMetadata?.['amazon-bedrock-invocationMetrics'], generationInfo.usage_metadata);
1041
- }
1042
- }
1043
- const isAnthropic = provider?.toLowerCase() === 'anthropic' || model?.toLowerCase().includes('anthropic') === true;
1044
- if (isAnthropic && isNonEmptyUsage(normalizedGenerationUsage)) {
1045
- return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
1046
- }
1047
- const llmUsageKeys = ['token_usage', 'usage', 'tokenUsage'];
1048
- if (response.llmOutput != null) {
1049
- for (const key of llmUsageKeys) {
1050
- const llmUsage = response.llmOutput[key];
1051
- if (!isNonEmptyUsage(llmUsage)) {
1052
- continue;
1053
- }
1054
- return this._parseUsageModel(llmUsage, provider, model, key !== 'usage', llmUsage);
1055
- }
1056
- }
1057
- if (isNonEmptyUsage(normalizedGenerationUsage)) {
1058
- return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
1059
- }
1060
- if (isNonEmptyUsage(rawGenerationUsage)) {
1061
- return this._parseUsageModel(rawGenerationUsage, provider, model, false, rawGenerationUsage);
1062
- }
1063
- if (isNonEmptyUsage(fallbackGenerationUsage)) {
1064
- return this._parseUsageModel(fallbackGenerationUsage, provider, model);
1065
- }
1066
- return [0, 0, {}];
1067
- }
330
+ //#endregion
331
+ //#region src/openai/utils.ts
332
+ const TERMINAL_RESPONSE_STATUSES = /* @__PURE__ */ new Set([
333
+ "completed",
334
+ "failed",
335
+ "cancelled",
336
+ "incomplete"
337
+ ]);
338
+ /**
339
+ * Checks whether a Responses API response has reached a status that should
340
+ * produce a final `$ai_generation` event.
341
+ */
342
+ function isTerminalResponse(response) {
343
+ return !!response?.status && TERMINAL_RESPONSE_STATUSES.has(response.status);
1068
344
  }
1069
-
345
+ /**
346
+ * Maps a Responses API outcome to a `$ai_stop_reason`. An incomplete run is
347
+ * named by what cut it short (`incomplete_details.reason`, e.g.
348
+ * `max_output_tokens`); the other terminal statuses stand for themselves.
349
+ * Non-terminal lifecycle statuses (`queued`, `in_progress`) are not stop
350
+ * reasons, so they yield undefined.
351
+ */
352
+ function responsesStopReason(response) {
353
+ if (!response || !isTerminalResponse(response)) return;
354
+ if (response.status === "incomplete" && response.incomplete_details?.reason) return response.incomplete_details.reason;
355
+ return response.status ?? void 0;
356
+ }
357
+ //#endregion
358
+ //#region src/langchain/callbacks.ts
359
+ const isLangGraphControlFlow = (error) => error.is_bubble_up === true;
360
+ var LangChainCallbackHandler = class extends _langchain_core_callbacks_base.BaseCallbackHandler {
361
+ constructor(options) {
362
+ if (!options.client) throw new Error("PostHog client is required");
363
+ super();
364
+ this.name = "PosthogCallbackHandler";
365
+ this.runs = {};
366
+ this.parentTree = {};
367
+ this.client = options.client;
368
+ this.distinctId = options.distinctId;
369
+ this.traceId = options.traceId;
370
+ this.properties = options.properties || {};
371
+ this.privacyMode = options.privacyMode || false;
372
+ this.groups = options.groups || {};
373
+ this.debug = options.debug || false;
374
+ }
375
+ handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, runName, extra) {
376
+ this._logDebugEvent("on_chain_start", runId, parentRunId, {
377
+ inputs,
378
+ tags
379
+ });
380
+ this._setParentOfRun(runId, parentRunId);
381
+ this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName);
382
+ if (typeof extra?.posthogStartTime === "number" && Number.isFinite(extra.posthogStartTime)) this.runs[runId].startTime = extra.posthogStartTime;
383
+ }
384
+ handleChainEnd(outputs, runId, parentRunId, tags, _kwargs) {
385
+ this._logAndPopTraceOrSpan("on_chain_end", runId, parentRunId, {
386
+ outputs,
387
+ tags
388
+ }, outputs);
389
+ }
390
+ handleChainError(error, runId, parentRunId, tags, _kwargs) {
391
+ this._logAndPopTraceOrSpan("on_chain_error", runId, parentRunId, {
392
+ error,
393
+ tags
394
+ }, error);
395
+ }
396
+ handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata, runName) {
397
+ this._logDebugEvent("on_chat_model_start", runId, parentRunId, {
398
+ messages,
399
+ tags
400
+ });
401
+ this._setParentOfRun(runId, parentRunId);
402
+ const input = messages.flat().map((m) => this._convertMessageToDict(m));
403
+ this._setLLMMetadata(serialized, runId, input, metadata, extraParams, runName);
404
+ }
405
+ handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata, runName) {
406
+ this._logDebugEvent("on_llm_start", runId, parentRunId, {
407
+ prompts,
408
+ tags
409
+ });
410
+ this._setParentOfRun(runId, parentRunId);
411
+ this._setLLMMetadata(serialized, runId, prompts, metadata, extraParams, runName);
412
+ }
413
+ handleLLMEnd(output, runId, parentRunId, tags, _extraParams) {
414
+ this._logAndPopGeneration("on_llm_end", runId, parentRunId, {
415
+ output,
416
+ tags
417
+ }, output);
418
+ }
419
+ handleLLMError(err, runId, parentRunId, tags, _extraParams) {
420
+ this._logAndPopGeneration("on_llm_error", runId, parentRunId, {
421
+ err,
422
+ tags
423
+ }, err);
424
+ }
425
+ handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
426
+ this._logAndSetTraceOrSpan("on_tool_start", tool, input, runId, parentRunId, {
427
+ input,
428
+ tags
429
+ }, tags, metadata, runName);
430
+ }
431
+ handleToolEnd(output, runId, parentRunId, tags) {
432
+ this._logAndPopTraceOrSpan("on_tool_end", runId, parentRunId, {
433
+ output,
434
+ tags
435
+ }, output);
436
+ }
437
+ handleToolError(err, runId, parentRunId, tags) {
438
+ this._logAndPopTraceOrSpan("on_tool_error", runId, parentRunId, {
439
+ err,
440
+ tags
441
+ }, err);
442
+ }
443
+ handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
444
+ this._logAndSetTraceOrSpan("on_retriever_start", retriever, query, runId, parentRunId, {
445
+ query,
446
+ tags
447
+ }, tags, metadata, name);
448
+ }
449
+ handleRetrieverEnd(documents, runId, parentRunId, tags) {
450
+ this._logAndPopTraceOrSpan("on_retriever_end", runId, parentRunId, {
451
+ documents,
452
+ tags
453
+ }, documents);
454
+ }
455
+ handleRetrieverError(err, runId, parentRunId, tags) {
456
+ this._logAndPopTraceOrSpan("on_retriever_error", runId, parentRunId, {
457
+ err,
458
+ tags
459
+ }, err);
460
+ }
461
+ handleAgentAction(action, runId, parentRunId, tags) {
462
+ this._logDebugEvent("on_agent_action", runId, parentRunId, {
463
+ action,
464
+ tags
465
+ });
466
+ this._setParentOfRun(runId, parentRunId);
467
+ this._setTraceOrSpanMetadata(null, action, runId, parentRunId);
468
+ }
469
+ handleAgentEnd(action, runId, parentRunId, tags) {
470
+ this._logDebugEvent("on_agent_finish", runId, parentRunId, {
471
+ action,
472
+ tags
473
+ });
474
+ this._popRunAndCaptureTraceOrSpan(runId, parentRunId, action);
475
+ }
476
+ _logAndSetTraceOrSpan(eventName, serialized, input, runId, parentRunId, debugPayload, tags, metadata, runName) {
477
+ this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
478
+ this._setParentOfRun(runId, parentRunId);
479
+ this._setTraceOrSpanMetadata(serialized, input, runId, parentRunId, metadata, tags, runName);
480
+ }
481
+ _logAndPopTraceOrSpan(eventName, runId, parentRunId, debugPayload, result) {
482
+ this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
483
+ this._popRunAndCaptureTraceOrSpan(runId, parentRunId, result);
484
+ }
485
+ _logAndPopGeneration(eventName, runId, parentRunId, debugPayload, result) {
486
+ this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
487
+ this._popRunAndCaptureGeneration(runId, parentRunId, result);
488
+ }
489
+ _setParentOfRun(runId, parentRunId) {
490
+ if (parentRunId) this.parentTree[runId] = parentRunId;
491
+ }
492
+ _popParentOfRun(runId) {
493
+ delete this.parentTree[runId];
494
+ }
495
+ _findRootRun(runId) {
496
+ let id = runId;
497
+ while (this.parentTree[id]) id = this.parentTree[id];
498
+ return id;
499
+ }
500
+ _setTraceOrSpanMetadata(serialized, input, runId, parentRunId, ...args) {
501
+ const defaultName = parentRunId ? "span" : "trace";
502
+ const runName = this._getLangchainRunName(serialized, ...args) || defaultName;
503
+ this.runs[runId] = {
504
+ name: runName,
505
+ input,
506
+ startTime: Date.now()
507
+ };
508
+ }
509
+ _setLLMMetadata(serialized, runId, messages, metadata, extraParams, runName) {
510
+ const generation = {
511
+ name: this._getLangchainRunName(serialized, {
512
+ extraParams,
513
+ runName
514
+ }) || "generation",
515
+ input: sanitizeLangChain(messages, this.client),
516
+ startTime: Date.now()
517
+ };
518
+ if (extraParams) {
519
+ generation.modelParams = getModelParams(extraParams.invocation_params);
520
+ if (extraParams.invocation_params && extraParams.invocation_params.tools) generation.tools = extraParams.invocation_params.tools;
521
+ }
522
+ if (metadata) {
523
+ if (metadata.ls_model_name) generation.model = metadata.ls_model_name;
524
+ if (metadata.ls_provider) generation.provider = metadata.ls_provider;
525
+ }
526
+ if (serialized && "kwargs" in serialized && serialized.kwargs.openai_api_base) generation.baseUrl = serialized.kwargs.openai_api_base;
527
+ this.runs[runId] = generation;
528
+ }
529
+ _popRunMetadata(runId) {
530
+ const endTime = Date.now();
531
+ const run = this.runs[runId];
532
+ if (!run) {
533
+ console.warn(`No run metadata found for run ${runId}`);
534
+ return;
535
+ }
536
+ run.endTime = endTime;
537
+ delete this.runs[runId];
538
+ return run;
539
+ }
540
+ _getTraceId(runId) {
541
+ return this.traceId ? String(this.traceId) : this._findRootRun(runId);
542
+ }
543
+ _getParentRunId(traceId, _runId, parentRunId) {
544
+ if (parentRunId && !this.parentTree[parentRunId]) return traceId;
545
+ return parentRunId;
546
+ }
547
+ _safeCapture(message) {
548
+ try {
549
+ captureAiEvent(this.client, message);
550
+ } catch {}
551
+ }
552
+ _popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs) {
553
+ const traceId = this._getTraceId(runId);
554
+ const isSpan = Boolean(parentRunId || this.parentTree[runId]);
555
+ this._popParentOfRun(runId);
556
+ const run = this._popRunMetadata(runId);
557
+ if (!run) return;
558
+ if ("modelParams" in run) {
559
+ console.warn(`Run ${runId} is a generation, but attempted to be captured as a trace/span.`);
560
+ return;
561
+ }
562
+ const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
563
+ this._captureTraceOrSpan(traceId, runId, run, outputs, isSpan, actualParentRunId);
564
+ }
565
+ _captureTraceOrSpan(traceId, runId, run, outputs, isSpan, parentRunId) {
566
+ const eventName = isSpan ? "$ai_span" : "$ai_trace";
567
+ const latency = run.endTime ? (run.endTime - run.startTime) / 1e3 : 0;
568
+ const eventProperties = {
569
+ $ai_lib: "posthog-ai",
570
+ $ai_lib_version: version,
571
+ $ai_trace_id: traceId,
572
+ $ai_input_state: withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(run.input, this.client)),
573
+ $ai_latency: latency,
574
+ $ai_span_name: run.name,
575
+ $ai_span_id: runId,
576
+ $ai_framework: "langchain"
577
+ };
578
+ if (parentRunId) eventProperties["$ai_parent_id"] = parentRunId;
579
+ Object.assign(eventProperties, this.properties);
580
+ if (!this.distinctId) eventProperties["$process_person_profile"] = false;
581
+ if (outputs instanceof Error) {
582
+ if (isLangGraphControlFlow(outputs)) {
583
+ const interrupts = outputs.interrupts;
584
+ if (interrupts !== void 0) eventProperties["$ai_output_state"] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain({ __interrupt__: interrupts }, this.client));
585
+ } else {
586
+ eventProperties["$ai_error"] = stringifyError(outputs);
587
+ eventProperties["$ai_is_error"] = true;
588
+ }
589
+ } else if (outputs !== void 0) eventProperties["$ai_output_state"] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(outputs, this.client));
590
+ this._safeCapture({
591
+ distinctId: this.distinctId ? this.distinctId.toString() : runId,
592
+ event: eventName,
593
+ properties: eventProperties,
594
+ groups: this.groups
595
+ });
596
+ }
597
+ _popRunAndCaptureGeneration(runId, parentRunId, response) {
598
+ const traceId = this._getTraceId(runId);
599
+ this._popParentOfRun(runId);
600
+ const run = this._popRunMetadata(runId);
601
+ if (!run || typeof run !== "object" || !("modelParams" in run)) {
602
+ console.warn(`Run ${runId} is not a generation, but attempted to be captured as such.`);
603
+ return;
604
+ }
605
+ const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
606
+ this._captureGeneration(traceId, runId, run, response, actualParentRunId);
607
+ }
608
+ _captureGeneration(traceId, runId, run, output, parentRunId) {
609
+ const latency = run.endTime ? (run.endTime - run.startTime) / 1e3 : 0;
610
+ warnIfPostHogAiGateway(run.baseUrl);
611
+ const eventProperties = {
612
+ $ai_lib: "posthog-ai",
613
+ $ai_lib_version: version,
614
+ $ai_trace_id: traceId,
615
+ $ai_span_id: runId,
616
+ $ai_span_name: run.name,
617
+ $ai_provider: run.provider,
618
+ $ai_model: run.model,
619
+ $ai_model_parameters: run.modelParams,
620
+ $ai_input: withPrivacyMode(this.client, this.privacyMode, run.input),
621
+ $ai_http_status: 200,
622
+ $ai_latency: latency,
623
+ $ai_base_url: run.baseUrl,
624
+ $ai_framework: "langchain"
625
+ };
626
+ if (parentRunId) eventProperties["$ai_parent_id"] = parentRunId;
627
+ if (run.tools) eventProperties["$ai_tools"] = run.tools;
628
+ if (output instanceof Error) {
629
+ eventProperties["$ai_http_status"] = output.status || 500;
630
+ eventProperties["$ai_error"] = stringifyError(output);
631
+ eventProperties["$ai_is_error"] = true;
632
+ } else {
633
+ const [inputTokens, outputTokens, additionalTokenData] = this.parseUsage(output, run.provider, run.model);
634
+ eventProperties["$ai_input_tokens"] = inputTokens;
635
+ eventProperties["$ai_output_tokens"] = outputTokens;
636
+ if (additionalTokenData.cacheReadInputTokens) eventProperties["$ai_cache_read_input_tokens"] = additionalTokenData.cacheReadInputTokens;
637
+ if (additionalTokenData.cacheWriteInputTokens) eventProperties["$ai_cache_creation_input_tokens"] = additionalTokenData.cacheWriteInputTokens;
638
+ if (additionalTokenData.cacheWrite5mInputTokens !== void 0 && additionalTokenData.cacheWrite1hInputTokens !== void 0) {
639
+ eventProperties["$ai_cache_creation_5m_input_tokens"] = additionalTokenData.cacheWrite5mInputTokens;
640
+ eventProperties["$ai_cache_creation_1h_input_tokens"] = additionalTokenData.cacheWrite1hInputTokens;
641
+ }
642
+ if (additionalTokenData.reasoningTokens) eventProperties["$ai_reasoning_tokens"] = additionalTokenData.reasoningTokens;
643
+ if (additionalTokenData.webSearchCount !== void 0) eventProperties["$ai_web_search_count"] = additionalTokenData.webSearchCount;
644
+ const stopReason = this._extractStopReason(output);
645
+ if (stopReason) eventProperties["$ai_stop_reason"] = stopReason;
646
+ let completions;
647
+ if (output.generations && Array.isArray(output.generations)) {
648
+ const lastGeneration = output.generations[output.generations.length - 1];
649
+ if (Array.isArray(lastGeneration) && lastGeneration.length > 0) {
650
+ if ("message" in lastGeneration[0] && lastGeneration[0].message) completions = lastGeneration.map((gen) => {
651
+ return this._convertMessageToDict(gen.message);
652
+ });
653
+ else completions = lastGeneration.map((gen) => {
654
+ return this._extractRawResponse(gen);
655
+ });
656
+ }
657
+ }
658
+ if (completions) eventProperties["$ai_output_choices"] = withPrivacyMode(this.client, this.privacyMode, completions);
659
+ }
660
+ Object.assign(eventProperties, this.properties);
661
+ if (!this.distinctId) eventProperties["$process_person_profile"] = false;
662
+ this._safeCapture({
663
+ distinctId: this.distinctId ? this.distinctId.toString() : traceId,
664
+ event: "$ai_generation",
665
+ properties: eventProperties,
666
+ groups: this.groups
667
+ });
668
+ }
669
+ _logDebugEvent(eventName, runId, parentRunId, extra) {
670
+ if (this.debug) console.log(`Event: ${eventName}, runId: ${runId}, parentRunId: ${parentRunId}, extra:`, extra);
671
+ }
672
+ _getLangchainRunName(serialized, ...args) {
673
+ if (args && args.length > 0) for (const arg of args) {
674
+ if (typeof arg === "string" && arg) return arg;
675
+ if (arg && typeof arg === "object") {
676
+ if (arg.name) return arg.name;
677
+ if (arg.runName) return arg.runName;
678
+ }
679
+ }
680
+ if (serialized && serialized.name) return serialized.name;
681
+ if (serialized && serialized.id) return Array.isArray(serialized.id) ? serialized.id[serialized.id.length - 1] : serialized.id;
682
+ }
683
+ _convertLcToolCallsToOai(toolCalls) {
684
+ return toolCalls.map((toolCall) => ({
685
+ type: "function",
686
+ id: toolCall.id,
687
+ function: {
688
+ name: toolCall.name,
689
+ arguments: JSON.stringify(toolCall.args)
690
+ }
691
+ }));
692
+ }
693
+ _extractRawResponse(generation) {
694
+ if (generation.text != null && generation.text.trim() !== "") return generation.text.trim();
695
+ else if (generation.message) return generation.message.additional_kwargs || generation.message.additionalKwargs || {};
696
+ else return "";
697
+ }
698
+ _convertMessageToDict(message) {
699
+ let messageDict = {};
700
+ const messageType = message.getType();
701
+ switch (messageType) {
702
+ case "human":
703
+ messageDict = {
704
+ role: "user",
705
+ content: message.content
706
+ };
707
+ break;
708
+ case "ai":
709
+ messageDict = {
710
+ role: "assistant",
711
+ content: message.content
712
+ };
713
+ if (message.tool_calls) messageDict.tool_calls = this._convertLcToolCallsToOai(message.tool_calls);
714
+ break;
715
+ case "system":
716
+ messageDict = {
717
+ role: "system",
718
+ content: message.content
719
+ };
720
+ break;
721
+ case "tool":
722
+ messageDict = {
723
+ role: "tool",
724
+ content: message.content
725
+ };
726
+ break;
727
+ case "function":
728
+ messageDict = {
729
+ role: "function",
730
+ content: message.content
731
+ };
732
+ break;
733
+ default: messageDict = {
734
+ role: messageType,
735
+ content: toContentString(message.content)
736
+ };
737
+ }
738
+ if (message.additional_kwargs) messageDict = {
739
+ ...messageDict,
740
+ ...message.additional_kwargs
741
+ };
742
+ return sanitizeLangChain(messageDict, this.client);
743
+ }
744
+ _extractStopReason(output) {
745
+ if (!output.generations || !Array.isArray(output.generations)) return;
746
+ const lastGeneration = output.generations[output.generations.length - 1];
747
+ if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) return;
748
+ const gen = lastGeneration[0];
749
+ const messageResponseMetadata = gen.message?.response_metadata;
750
+ const generationResponseMetadata = gen.generationInfo?.response_metadata;
751
+ const stopReason = messageResponseMetadata?.finish_reason || messageResponseMetadata?.stop_reason || gen.generationInfo?.finish_reason || generationResponseMetadata?.stop_reason || generationResponseMetadata?.finish_reason || gen.generationInfo?.stop_reason || responsesStopReason(messageResponseMetadata) || responsesStopReason(generationResponseMetadata);
752
+ return stopReason != null ? String(stopReason) : void 0;
753
+ }
754
+ _extractCacheCreationTtlBreakdown(cacheCreation, aggregateValues) {
755
+ if (!isObject(cacheCreation)) return;
756
+ const { ephemeral_5m_input_tokens: cache5m, ephemeral_1h_input_tokens: cache1h } = cacheCreation;
757
+ const providedValues = [cache5m, cache1h].filter((value) => value != null);
758
+ if (providedValues.length === 0 || !providedValues.every((value) => typeof value === "number" && Number.isFinite(value) && value >= 0)) return;
759
+ const breakdown = [typeof cache5m === "number" ? cache5m : 0, typeof cache1h === "number" ? cache1h : 0];
760
+ const total = breakdown[0] + breakdown[1];
761
+ const validAggregates = aggregateValues.filter((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
762
+ return total > 0 && !validAggregates.some((aggregate) => aggregate !== total) ? breakdown : void 0;
763
+ }
764
+ _extractBedrockCacheCreationTtlBreakdown(cacheDetails, aggregateValues) {
765
+ if (!Array.isArray(cacheDetails)) return;
766
+ let cache5m = 0;
767
+ let cache1h = 0;
768
+ for (const detail of cacheDetails) {
769
+ if (!isObject(detail)) continue;
770
+ const ttl = typeof detail.ttl === "string" ? detail.ttl.toLowerCase() : void 0;
771
+ const inputTokens = detail.inputTokens;
772
+ if (ttl !== "5m" && ttl !== "t5m" && ttl !== "1h" && ttl !== "t1h" || typeof inputTokens !== "number" || !Number.isFinite(inputTokens) || inputTokens < 0) continue;
773
+ if (ttl === "5m" || ttl === "t5m") cache5m += inputTokens;
774
+ else cache1h += inputTokens;
775
+ }
776
+ const total = cache5m + cache1h;
777
+ const validAggregates = aggregateValues.filter((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
778
+ if (total === 0 || validAggregates.some((aggregate) => aggregate !== total)) return;
779
+ return [cache5m, cache1h];
780
+ }
781
+ _parseUsageModel(usage, provider, model, inputIncludesCacheTokens = true, rawUsage) {
782
+ const parsedUsage = [
783
+ ["promptTokens", "input"],
784
+ ["completionTokens", "output"],
785
+ ["input_tokens", "input"],
786
+ ["output_tokens", "output"],
787
+ ["prompt_token_count", "input"],
788
+ ["candidates_token_count", "output"],
789
+ ["inputTokenCount", "input"],
790
+ ["outputTokenCount", "output"],
791
+ ["input_token_count", "input"],
792
+ ["generated_token_count", "output"]
793
+ ].reduce((acc, [modelKey, typeKey]) => {
794
+ const value = usage[modelKey];
795
+ if (value != null) acc[typeKey] = Array.isArray(value) ? value.reduce((sum, tokenCount) => sum + tokenCount, 0) : value;
796
+ return acc;
797
+ }, {
798
+ input: 0,
799
+ output: 0
800
+ });
801
+ const additionalTokenData = {};
802
+ if (usage.prompt_tokens_details?.cached_tokens != null) additionalTokenData.cacheReadInputTokens = usage.prompt_tokens_details.cached_tokens;
803
+ else if (usage.input_token_details?.cache_read != null) additionalTokenData.cacheReadInputTokens = usage.input_token_details.cache_read;
804
+ else if (usage.cachedPromptTokens != null) additionalTokenData.cacheReadInputTokens = usage.cachedPromptTokens;
805
+ else if (usage.cache_read_input_tokens != null) additionalTokenData.cacheReadInputTokens = usage.cache_read_input_tokens;
806
+ if (usage.cache_creation_input_tokens != null) additionalTokenData.cacheWriteInputTokens = usage.cache_creation_input_tokens;
807
+ else if (usage.input_token_details?.cache_creation != null) additionalTokenData.cacheWriteInputTokens = usage.input_token_details.cache_creation;
808
+ const directCacheCreationAggregates = [
809
+ usage.cache_creation_input_tokens,
810
+ usage.input_token_details?.cache_creation,
811
+ usage.cacheWriteInputTokens,
812
+ rawUsage?.cache_creation_input_tokens,
813
+ rawUsage?.input_token_details?.cache_creation,
814
+ rawUsage?.cacheWriteInputTokens,
815
+ additionalTokenData.cacheWriteInputTokens
816
+ ];
817
+ const cacheCreationTtl = this._extractCacheCreationTtlBreakdown(usage.cache_creation, directCacheCreationAggregates) ?? this._extractCacheCreationTtlBreakdown(rawUsage?.cache_creation, directCacheCreationAggregates) ?? this._extractBedrockCacheCreationTtlBreakdown(usage.cacheDetails, [usage.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]) ?? this._extractBedrockCacheCreationTtlBreakdown(rawUsage?.cacheDetails, [rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]);
818
+ if (cacheCreationTtl) {
819
+ const [cacheWrite5mInputTokens, cacheWrite1hInputTokens] = cacheCreationTtl;
820
+ additionalTokenData.cacheWrite5mInputTokens = cacheWrite5mInputTokens;
821
+ additionalTokenData.cacheWrite1hInputTokens = cacheWrite1hInputTokens;
822
+ additionalTokenData.cacheWriteInputTokens = cacheWrite5mInputTokens + cacheWrite1hInputTokens;
823
+ }
824
+ if (usage.completion_tokens_details?.reasoning_tokens != null) additionalTokenData.reasoningTokens = usage.completion_tokens_details.reasoning_tokens;
825
+ else if (usage.output_token_details?.reasoning != null) additionalTokenData.reasoningTokens = usage.output_token_details.reasoning;
826
+ else if (usage.reasoningTokens != null) additionalTokenData.reasoningTokens = usage.reasoningTokens;
827
+ let webSearchCount;
828
+ if (usage.server_tool_use?.web_search_requests !== void 0) webSearchCount = usage.server_tool_use.web_search_requests;
829
+ else if (usage.citations && Array.isArray(usage.citations) && usage.citations.length > 0) webSearchCount = 1;
830
+ else if (usage.search_results && Array.isArray(usage.search_results) && usage.search_results.length > 0) webSearchCount = 1;
831
+ else if (usage.search_context_size) webSearchCount = 1;
832
+ else if (usage.annotations && Array.isArray(usage.annotations)) {
833
+ if (usage.annotations.some((ann) => {
834
+ return ann && typeof ann === "object" && "type" in ann && ann.type === "url_citation";
835
+ })) webSearchCount = 1;
836
+ } else if (usage.grounding_metadata?.grounding_support !== void 0 || usage.grounding_metadata?.web_search_queries !== void 0) webSearchCount = 1;
837
+ if (webSearchCount !== void 0) additionalTokenData.webSearchCount = webSearchCount;
838
+ let isAnthropic = false;
839
+ if (provider && provider.toLowerCase() === "anthropic") isAnthropic = true;
840
+ else if (model && model.toLowerCase().includes("anthropic")) isAnthropic = true;
841
+ if (isAnthropic && inputIncludesCacheTokens && parsedUsage.input) {
842
+ const cacheTokens = (additionalTokenData.cacheReadInputTokens || 0) + (additionalTokenData.cacheWriteInputTokens || 0);
843
+ if (cacheTokens > 0) parsedUsage.input = Math.max(parsedUsage.input - cacheTokens, 0);
844
+ }
845
+ return [
846
+ parsedUsage.input,
847
+ parsedUsage.output,
848
+ additionalTokenData
849
+ ];
850
+ }
851
+ parseUsage(response, provider, model) {
852
+ const isNonEmptyUsage = (usage) => isObject(usage) && Object.keys(usage).length > 0;
853
+ const firstNonEmptyUsage = (...candidates) => candidates.find(isNonEmptyUsage);
854
+ let normalizedGenerationUsage;
855
+ let rawGenerationUsage;
856
+ let fallbackGenerationUsage;
857
+ for (const generation of response.generations ?? []) for (const genChunk of generation) {
858
+ const generationInfo = genChunk.generationInfo ?? {};
859
+ const message = "message" in genChunk ? genChunk.message : void 0;
860
+ const messageUsage = message && typeof message === "object" && "usage_metadata" in message ? message.usage_metadata : void 0;
861
+ normalizedGenerationUsage = firstNonEmptyUsage(normalizedGenerationUsage, messageUsage, generationInfo.usage_metadata);
862
+ const messageResponseMetadata = message && typeof message === "object" && "response_metadata" in message && isObject(message.response_metadata) ? message.response_metadata : void 0;
863
+ const generationResponseMetadata = isObject(generationInfo.response_metadata) ? generationInfo.response_metadata : void 0;
864
+ const messageStreamMetadata = isObject(messageResponseMetadata?.metadata) ? messageResponseMetadata.metadata : void 0;
865
+ const generationStreamMetadata = isObject(generationResponseMetadata?.metadata) ? generationResponseMetadata.metadata : void 0;
866
+ rawGenerationUsage = firstNonEmptyUsage(rawGenerationUsage, messageResponseMetadata?.usage, messageStreamMetadata?.usage, generationResponseMetadata?.usage, generationStreamMetadata?.usage);
867
+ fallbackGenerationUsage = firstNonEmptyUsage(fallbackGenerationUsage, messageResponseMetadata?.["amazon-bedrock-invocationMetrics"], generationResponseMetadata?.["amazon-bedrock-invocationMetrics"], generationInfo.usage_metadata);
868
+ }
869
+ if ((provider?.toLowerCase() === "anthropic" || model?.toLowerCase().includes("anthropic") === true) && isNonEmptyUsage(normalizedGenerationUsage)) return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
870
+ const llmUsageKeys = [
871
+ "token_usage",
872
+ "usage",
873
+ "tokenUsage"
874
+ ];
875
+ if (response.llmOutput != null) for (const key of llmUsageKeys) {
876
+ const llmUsage = response.llmOutput[key];
877
+ if (!isNonEmptyUsage(llmUsage)) continue;
878
+ return this._parseUsageModel(llmUsage, provider, model, key !== "usage", llmUsage);
879
+ }
880
+ if (isNonEmptyUsage(normalizedGenerationUsage)) return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
881
+ if (isNonEmptyUsage(rawGenerationUsage)) return this._parseUsageModel(rawGenerationUsage, provider, model, false, rawGenerationUsage);
882
+ if (isNonEmptyUsage(fallbackGenerationUsage)) return this._parseUsageModel(fallbackGenerationUsage, provider, model);
883
+ return [
884
+ 0,
885
+ 0,
886
+ {}
887
+ ];
888
+ }
889
+ };
890
+ //#endregion
891
+ //#region src/langchain/middleware/index.ts
1070
892
  const postHogStateSchema = zod.z.object({
1071
- _posthogRunId: zod.z.string().optional(),
1072
- _posthogStartTime: zod.z.number().optional(),
1073
- _posthogInput: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
893
+ _posthogRunId: zod.z.string().optional(),
894
+ _posthogStartTime: zod.z.number().optional(),
895
+ _posthogInput: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1074
896
  });
1075
- class LangChainMiddlewareCallbackHandler extends LangChainCallbackHandler {
1076
- _getParentRunId(_traceId, _runId, parentRunId) {
1077
- return parentRunId;
1078
- }
1079
- }
1080
- const withoutPostHogState = state => {
1081
- const {
1082
- _posthogRunId: _,
1083
- _posthogStartTime: __,
1084
- _posthogInput: ___,
1085
- ...rest
1086
- } = state;
1087
- return rest;
897
+ var LangChainMiddlewareCallbackHandler = class extends LangChainCallbackHandler {
898
+ _getParentRunId(_traceId, _runId, parentRunId) {
899
+ return parentRunId;
900
+ }
1088
901
  };
1089
- const getRunId = state => state._posthogRunId ?? uuid.v7();
1090
- const stringify = value => {
1091
- try {
1092
- return JSON.stringify(value) ?? String(value);
1093
- } catch {
1094
- try {
1095
- return String(value);
1096
- } catch {
1097
- return '';
1098
- }
1099
- }
902
+ const withoutPostHogState = (state) => {
903
+ const { _posthogRunId: _, _posthogStartTime: __, _posthogInput: ___, ...rest } = state;
904
+ return rest;
1100
905
  };
1101
- const toError = error => error instanceof Error ? error : new Error(stringify(error));
1102
- const safely = callback => {
1103
- try {
1104
- callback();
1105
- } catch {
1106
- // Telemetry must never affect the LangChain middleware lifecycle.
1107
- }
906
+ const getRunId = (state) => state._posthogRunId ?? (0, uuid.v7)();
907
+ const stringify = (value) => {
908
+ try {
909
+ return JSON.stringify(value) ?? String(value);
910
+ } catch {
911
+ try {
912
+ return String(value);
913
+ } catch {
914
+ return "";
915
+ }
916
+ }
1108
917
  };
1109
- const serializeModel = model => {
1110
- if (model && typeof model === 'object' && 'toJSON' in model && typeof model.toJSON === 'function') {
1111
- try {
1112
- return model.toJSON();
1113
- } catch {
1114
- // Fall back to a minimal LangChain serialization below.
1115
- }
1116
- }
1117
- return {
1118
- lc: 1,
1119
- type: 'constructor',
1120
- id: ['langchain', 'chat_models', 'unknown'],
1121
- kwargs: {}
1122
- };
918
+ const toError = (error) => error instanceof Error ? error : new Error(stringify(error));
919
+ const safely = (callback) => {
920
+ try {
921
+ callback();
922
+ } catch {}
1123
923
  };
1124
- const toModelOptions = modelSettings => {
1125
- if (!isObject(modelSettings)) {
1126
- return {};
1127
- }
1128
- try {
1129
- return {
1130
- ...modelSettings
1131
- };
1132
- } catch {
1133
- return {};
1134
- }
924
+ const serializeModel = (model) => {
925
+ if (model && typeof model === "object" && "toJSON" in model && typeof model.toJSON === "function") try {
926
+ return model.toJSON();
927
+ } catch {}
928
+ return {
929
+ lc: 1,
930
+ type: "constructor",
931
+ id: [
932
+ "langchain",
933
+ "chat_models",
934
+ "unknown"
935
+ ],
936
+ kwargs: {}
937
+ };
938
+ };
939
+ const toModelOptions = (modelSettings) => {
940
+ if (!isObject(modelSettings)) return {};
941
+ try {
942
+ return { ...modelSettings };
943
+ } catch {
944
+ return {};
945
+ }
1135
946
  };
1136
947
  const getModelMetadata = (model, modelSettings) => {
1137
- if (model && typeof model === 'object' && 'getLsParams' in model && typeof model.getLsParams === 'function') {
1138
- try {
1139
- return model.getLsParams(toModelOptions(modelSettings));
1140
- } catch {
1141
- return undefined;
1142
- }
1143
- }
1144
- return undefined;
948
+ if (model && typeof model === "object" && "getLsParams" in model && typeof model.getLsParams === "function") try {
949
+ return model.getLsParams(toModelOptions(modelSettings));
950
+ } catch {
951
+ return;
952
+ }
1145
953
  };
1146
954
  const getModelInvocationParams = (model, modelSettings) => {
1147
- const options = toModelOptions(modelSettings);
1148
- if (model && typeof model === 'object' && 'invocationParams' in model && typeof model.invocationParams === 'function') {
1149
- try {
1150
- const params = model.invocationParams(options);
1151
- if (isObject(params)) {
1152
- return {
1153
- ...options,
1154
- ...params
1155
- };
1156
- }
1157
- } catch {
1158
- // Preserve the bind-time settings when the model cannot expose invocation parameters.
1159
- }
1160
- }
1161
- return options;
955
+ const options = toModelOptions(modelSettings);
956
+ if (model && typeof model === "object" && "invocationParams" in model && typeof model.invocationParams === "function") try {
957
+ const params = model.invocationParams(options);
958
+ if (isObject(params)) return {
959
+ ...options,
960
+ ...params
961
+ };
962
+ } catch {}
963
+ return options;
1162
964
  };
1163
- const normalizeTools = tools => {
1164
- return tools.map(tool => {
1165
- try {
1166
- return function_calling.convertToOpenAITool(tool);
1167
- } catch {
1168
- return tool;
1169
- }
1170
- });
965
+ const normalizeTools = (tools) => {
966
+ return tools.map((tool) => {
967
+ try {
968
+ return (0, _langchain_core_utils_function_calling.convertToOpenAITool)(tool);
969
+ } catch {
970
+ return tool;
971
+ }
972
+ });
1171
973
  };
1172
- const toLLMResult = response => {
1173
- if (!messages.AIMessage.isInstance(response)) {
1174
- return {
1175
- generations: []
1176
- };
1177
- }
1178
- const generation = {
1179
- text: toContentString(response.content),
1180
- message: response
1181
- };
1182
- return {
1183
- generations: [[generation]]
1184
- };
974
+ const toLLMResult = (response) => {
975
+ if (!_langchain_core_messages.AIMessage.isInstance(response)) return { generations: [] };
976
+ return { generations: [[{
977
+ text: toContentString(response.content),
978
+ message: response
979
+ }]] };
1185
980
  };
1186
-
1187
- /** Options shared with the LangChain callback integration. */
1188
-
1189
981
  /**
1190
- * Creates PostHog AI observability middleware for LangChain v1 agents.
1191
- *
1192
- * Use either this middleware or `LangChainCallbackHandler`, not both, to avoid
1193
- * capturing the same model and tool calls twice.
1194
- *
1195
- * LangChain only invokes `afterAgent` for completed runs. A terminal agent
1196
- * failure still captures the failed model or tool call, but not a root trace.
1197
- */
1198
- const createPostHogMiddleware = options => {
1199
- const {
1200
- stateSchema,
1201
- ...callbackOptions
1202
- } = options;
1203
- const callback = new LangChainMiddlewareCallbackHandler(callbackOptions);
1204
- const middlewareStateSchema = stateSchema ? types.extendInteropZodObject(stateSchema, postHogStateSchema.shape) : postHogStateSchema;
1205
- return langchain.createMiddleware({
1206
- name: 'PostHogMiddleware',
1207
- stateSchema: middlewareStateSchema,
1208
- beforeAgent: state => {
1209
- return {
1210
- _posthogRunId: uuid.v7(),
1211
- _posthogStartTime: Date.now(),
1212
- _posthogInput: withoutPostHogState(state)
1213
- };
1214
- },
1215
- afterAgent: state => {
1216
- safely(() => {
1217
- const runId = getRunId(state);
1218
- callback.handleChainStart({
1219
- lc: 1,
1220
- type: 'constructor',
1221
- id: ['langchain', 'agents', 'PostHogMiddleware'],
1222
- kwargs: {}
1223
- }, state._posthogInput ?? withoutPostHogState(state), runId, undefined, undefined, undefined, undefined, 'LangChain Agent', {
1224
- posthogStartTime: state._posthogStartTime
1225
- });
1226
- callback.handleChainEnd(withoutPostHogState(state), runId);
1227
- });
1228
- },
1229
- wrapModelCall: async (request, handler) => {
1230
- const runId = uuid.v7();
1231
- const parentRunId = getRunId(request.state);
1232
- const messages = request.systemMessage.text === '' ? request.messages : [request.systemMessage, ...request.messages];
1233
- const invocationParams = {
1234
- ...getModelInvocationParams(request.model, request.modelSettings),
1235
- tools: normalizeTools(request.tools)
1236
- };
1237
- safely(() => callback.handleChatModelStart(serializeModel(request.model), [messages], runId, parentRunId, {
1238
- invocation_params: invocationParams
1239
- }, undefined, getModelMetadata(request.model, request.modelSettings)));
1240
- try {
1241
- const response = await handler(request);
1242
- safely(() => callback.handleLLMEnd(toLLMResult(response), runId, parentRunId));
1243
- return response;
1244
- } catch (error) {
1245
- safely(() => callback.handleLLMError(toError(error), runId));
1246
- throw error;
1247
- }
1248
- },
1249
- wrapToolCall: async (request, handler) => {
1250
- const runId = uuid.v7();
1251
- const parentRunId = getRunId(request.state);
1252
- const toolName = String(request.tool?.name ?? request.toolCall.name);
1253
- const serializedTool = {
1254
- lc: 1,
1255
- type: 'constructor',
1256
- id: ['langchain', 'tools', toolName],
1257
- kwargs: {}
1258
- };
1259
- safely(() => callback.handleToolStart(serializedTool, stringify(request.toolCall.args), runId, parentRunId, undefined, undefined, toolName));
1260
- try {
1261
- const result = await handler(request);
1262
- if (messages.ToolMessage.isInstance(result) && result.status === 'error') {
1263
- safely(() => callback.handleToolError(new Error(toContentString(result.content)), runId, parentRunId));
1264
- } else {
1265
- safely(() => callback.handleToolEnd(result, runId, parentRunId));
1266
- }
1267
- return result;
1268
- } catch (error) {
1269
- safely(() => callback.handleToolError(toError(error), runId));
1270
- throw error;
1271
- }
1272
- }
1273
- });
982
+ * Creates PostHog AI observability middleware for LangChain v1 agents.
983
+ *
984
+ * Use either this middleware or `LangChainCallbackHandler`, not both, to avoid
985
+ * capturing the same model and tool calls twice.
986
+ *
987
+ * LangChain only invokes `afterAgent` for completed runs. A terminal agent
988
+ * failure still captures the failed model or tool call, but not a root trace.
989
+ */
990
+ const createPostHogMiddleware = (options) => {
991
+ const { stateSchema, ...callbackOptions } = options;
992
+ const callback = new LangChainMiddlewareCallbackHandler(callbackOptions);
993
+ const middlewareStateSchema = stateSchema ? (0, _langchain_core_utils_types.extendInteropZodObject)(stateSchema, postHogStateSchema.shape) : postHogStateSchema;
994
+ return (0, langchain.createMiddleware)({
995
+ name: "PostHogMiddleware",
996
+ stateSchema: middlewareStateSchema,
997
+ beforeAgent: (state) => {
998
+ return {
999
+ _posthogRunId: (0, uuid.v7)(),
1000
+ _posthogStartTime: Date.now(),
1001
+ _posthogInput: withoutPostHogState(state)
1002
+ };
1003
+ },
1004
+ afterAgent: (state) => {
1005
+ safely(() => {
1006
+ const runId = getRunId(state);
1007
+ callback.handleChainStart({
1008
+ lc: 1,
1009
+ type: "constructor",
1010
+ id: [
1011
+ "langchain",
1012
+ "agents",
1013
+ "PostHogMiddleware"
1014
+ ],
1015
+ kwargs: {}
1016
+ }, state._posthogInput ?? withoutPostHogState(state), runId, void 0, void 0, void 0, void 0, "LangChain Agent", { posthogStartTime: state._posthogStartTime });
1017
+ callback.handleChainEnd(withoutPostHogState(state), runId);
1018
+ });
1019
+ },
1020
+ wrapModelCall: async (request, handler) => {
1021
+ const runId = (0, uuid.v7)();
1022
+ const parentRunId = getRunId(request.state);
1023
+ const messages = request.systemMessage.text === "" ? request.messages : [request.systemMessage, ...request.messages];
1024
+ const invocationParams = {
1025
+ ...getModelInvocationParams(request.model, request.modelSettings),
1026
+ tools: normalizeTools(request.tools)
1027
+ };
1028
+ safely(() => callback.handleChatModelStart(serializeModel(request.model), [messages], runId, parentRunId, { invocation_params: invocationParams }, void 0, getModelMetadata(request.model, request.modelSettings)));
1029
+ try {
1030
+ const response = await handler(request);
1031
+ safely(() => callback.handleLLMEnd(toLLMResult(response), runId, parentRunId));
1032
+ return response;
1033
+ } catch (error) {
1034
+ safely(() => callback.handleLLMError(toError(error), runId));
1035
+ throw error;
1036
+ }
1037
+ },
1038
+ wrapToolCall: async (request, handler) => {
1039
+ const runId = (0, uuid.v7)();
1040
+ const parentRunId = getRunId(request.state);
1041
+ const toolName = String(request.tool?.name ?? request.toolCall.name);
1042
+ const serializedTool = {
1043
+ lc: 1,
1044
+ type: "constructor",
1045
+ id: [
1046
+ "langchain",
1047
+ "tools",
1048
+ toolName
1049
+ ],
1050
+ kwargs: {}
1051
+ };
1052
+ safely(() => callback.handleToolStart(serializedTool, stringify(request.toolCall.args), runId, parentRunId, void 0, void 0, toolName));
1053
+ try {
1054
+ const result = await handler(request);
1055
+ if (_langchain_core_messages.ToolMessage.isInstance(result) && result.status === "error") safely(() => callback.handleToolError(new Error(toContentString(result.content)), runId, parentRunId));
1056
+ else safely(() => callback.handleToolEnd(result, runId, parentRunId));
1057
+ return result;
1058
+ } catch (error) {
1059
+ safely(() => callback.handleToolError(toError(error), runId));
1060
+ throw error;
1061
+ }
1062
+ }
1063
+ });
1274
1064
  };
1275
-
1065
+ //#endregion
1276
1066
  exports.createPostHogMiddleware = createPostHogMiddleware;
1277
- //# sourceMappingURL=index.cjs.map
1067
+
1068
+ //# sourceMappingURL=index.cjs.map