@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,1168 +1,965 @@
1
- import AnthropicOriginal from '@anthropic-ai/sdk';
2
- import { v4 } from 'uuid';
3
- import { toJsonSafeValue, uuidv7 } from '@posthog/core';
4
- import { Stream } from '@anthropic-ai/sdk/streaming';
5
-
6
- /** @internal */
7
-
8
- /** @internal */
9
-
1
+ import AnthropicOriginal from "@anthropic-ai/sdk";
2
+ import { v4 } from "uuid";
3
+ import { toJsonSafeValue, uuidv7 } from "@posthog/core";
4
+ import { Stream } from "@anthropic-ai/sdk/streaming";
5
+ //#region src/captureAiEvent.ts
10
6
  /** @internal */
11
7
  function isFullAiCaptureEnabled(client) {
12
- return client?.enableFullAiCapture === true;
8
+ return client?.enableFullAiCapture === true;
13
9
  }
14
-
15
10
  /** @internal */
16
11
  function captureAiEvent(client, event) {
17
- if (isFullAiCaptureEnabled(client) && typeof client.captureAi === 'function') {
18
- client.captureAi(event);
19
- return;
20
- }
21
- client.capture(event);
12
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAi === "function") {
13
+ client.captureAi(event);
14
+ return;
15
+ }
16
+ client.capture(event);
22
17
  }
23
-
24
18
  /** @internal */
25
19
  async function captureAiEventImmediate(client, event) {
26
- if (isFullAiCaptureEnabled(client) && typeof client.captureAiImmediate === 'function') {
27
- await client.captureAiImmediate(event);
28
- return;
29
- }
30
- await client.captureImmediate(event);
20
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAiImmediate === "function") {
21
+ await client.captureAiImmediate(event);
22
+ return;
23
+ }
24
+ await client.captureImmediate(event);
31
25
  }
32
-
26
+ //#endregion
27
+ //#region src/sanitization/base64_recognizer.ts
33
28
  const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
34
29
  const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
35
- class Base64Recognizer {
36
- recognize(value, minLength) {
37
- const dataUrl = DATA_URL_PREFIX_RE.exec(value);
38
- if (dataUrl) return {
39
- kind: 'data-url',
40
- mediaType: dataUrl[1]
41
- };
42
- if (value.length < minLength) return {
43
- kind: 'none'
44
- };
45
- const confidencePrefix = value.slice(0, minLength);
46
- if (BASE64_ALPHABET_RE.test(confidencePrefix)) {
47
- return {
48
- kind: 'raw'
49
- };
50
- } else {
51
- return {
52
- kind: 'none'
53
- };
54
- }
55
- }
56
- }
57
-
58
- const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
59
- 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']);
60
- const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
61
- const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
62
- const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
63
- class MediaTypeContext {
64
- static EMPTY = new MediaTypeContext(undefined, undefined);
65
- constructor(parent, key, explicitMediaType) {
66
- this.parent = parent;
67
- this.key = key;
68
- this.explicitMediaType = explicitMediaType;
69
- }
70
- inferMediaType() {
71
- return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
72
- }
73
- inferFromSiblingMime() {
74
- if (this.explicitMediaType) return this.explicitMediaType;
75
- if (!this.parent) return undefined;
76
- for (const hint of MIME_HINT_KEYS) {
77
- const v = this.parent[hint];
78
- if (typeof v === 'string') return v;
79
- }
80
- return undefined;
81
- }
82
- inferFromSiblingFormat() {
83
- if (!this.parent) return undefined;
84
- const fmt = this.parent.format;
85
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
86
- return `audio/${fmt.toLowerCase()}`;
87
- }
88
- return undefined;
89
- }
90
- inferFromParentType() {
91
- if (!this.parent) return undefined;
92
- const t = this.parent.type;
93
- if (typeof t !== 'string') return undefined;
94
- if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
95
- if (t === 'audio' || t === 'input_audio') return 'audio';
96
- if (t === 'video' || t === 'video_url') return 'video';
97
- if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
98
- return undefined;
99
- }
100
- inferFromKey() {
101
- if (!this.key) return undefined;
102
- const key = this.key.toLowerCase();
103
- if (key.includes('audio')) return 'audio';
104
- if (key.includes('video')) return 'video';
105
- if (key.includes('image')) return 'image';
106
- if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
107
- return undefined;
108
- }
109
- hasExplicitBinaryMediaType() {
110
- if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
111
- const mediaType = this.inferFromSiblingMime();
112
- return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/');
113
- }
114
- signalsBinary() {
115
- if (this.explicitMediaType) return true;
116
- if (this.parent) {
117
- for (const hint of MIME_HINT_KEYS) {
118
- if (typeof this.parent[hint] === 'string') return true;
119
- }
120
- const fmt = this.parent.format;
121
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
122
- const t = this.parent.type;
123
- if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
124
- }
125
- if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
126
- return false;
127
- }
128
- }
129
-
30
+ var Base64Recognizer = class {
31
+ recognize(value, minLength) {
32
+ const dataUrl = DATA_URL_PREFIX_RE.exec(value);
33
+ if (dataUrl) return {
34
+ kind: "data-url",
35
+ mediaType: dataUrl[1]
36
+ };
37
+ if (value.length < minLength) return { kind: "none" };
38
+ const confidencePrefix = value.slice(0, minLength);
39
+ if (BASE64_ALPHABET_RE.test(confidencePrefix)) return { kind: "raw" };
40
+ else return { kind: "none" };
41
+ }
42
+ };
43
+ //#endregion
44
+ //#region src/sanitization/media_type_context.ts
45
+ const MIME_HINT_KEYS = [
46
+ "mediaType",
47
+ "media_type",
48
+ "mimeType",
49
+ "mime_type"
50
+ ];
51
+ const STRONG_CONTEXT_KEYS = /* @__PURE__ */ new Set([
52
+ "data",
53
+ "file_data",
54
+ "fileData",
55
+ "image_url",
56
+ "imageUrl",
57
+ "video_url",
58
+ "videoUrl",
59
+ "audio",
60
+ "audio_data",
61
+ "audioData",
62
+ "inline_data",
63
+ "inlineData",
64
+ "source",
65
+ "result"
66
+ ]);
67
+ const STRONG_CONTEXT_TYPES = /* @__PURE__ */ new Set([
68
+ "image",
69
+ "image_url",
70
+ "input_image",
71
+ "audio",
72
+ "input_audio",
73
+ "video",
74
+ "video_url",
75
+ "file",
76
+ "input_file",
77
+ "document",
78
+ "media",
79
+ "file-data"
80
+ ]);
81
+ const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
82
+ "file",
83
+ "input_file",
84
+ "document",
85
+ "media",
86
+ "file-data"
87
+ ]);
88
+ const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
89
+ "wav",
90
+ "mp3",
91
+ "ogg",
92
+ "flac",
93
+ "m4a",
94
+ "aac",
95
+ "webm"
96
+ ]);
97
+ var MediaTypeContext = class MediaTypeContext {
98
+ static {
99
+ this.EMPTY = new MediaTypeContext(void 0, void 0);
100
+ }
101
+ constructor(parent, key, explicitMediaType) {
102
+ this.parent = parent;
103
+ this.key = key;
104
+ this.explicitMediaType = explicitMediaType;
105
+ }
106
+ inferMediaType() {
107
+ return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
108
+ }
109
+ inferFromSiblingMime() {
110
+ if (this.explicitMediaType) return this.explicitMediaType;
111
+ if (!this.parent) return void 0;
112
+ for (const hint of MIME_HINT_KEYS) {
113
+ const v = this.parent[hint];
114
+ if (typeof v === "string") return v;
115
+ }
116
+ }
117
+ inferFromSiblingFormat() {
118
+ if (!this.parent) return void 0;
119
+ const fmt = this.parent.format;
120
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return `audio/${fmt.toLowerCase()}`;
121
+ }
122
+ inferFromParentType() {
123
+ if (!this.parent) return void 0;
124
+ const t = this.parent.type;
125
+ if (typeof t !== "string") return void 0;
126
+ if (t === "image" || t === "image_url" || t === "input_image") return "image";
127
+ if (t === "audio" || t === "input_audio") return "audio";
128
+ if (t === "video" || t === "video_url") return "video";
129
+ if (FILE_FAMILY_TYPES.has(t)) return "application/octet-stream";
130
+ }
131
+ inferFromKey() {
132
+ if (!this.key) return void 0;
133
+ const key = this.key.toLowerCase();
134
+ if (key.includes("audio")) return "audio";
135
+ if (key.includes("video")) return "video";
136
+ if (key.includes("image")) return "image";
137
+ if (key.includes("file") || key.includes("document")) return "application/octet-stream";
138
+ }
139
+ hasExplicitBinaryMediaType() {
140
+ if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
141
+ const mediaType = this.inferFromSiblingMime();
142
+ return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
143
+ }
144
+ signalsBinary() {
145
+ if (this.explicitMediaType) return true;
146
+ if (this.parent) {
147
+ for (const hint of MIME_HINT_KEYS) if (typeof this.parent[hint] === "string") return true;
148
+ const fmt = this.parent.format;
149
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
150
+ const t = this.parent.type;
151
+ if (typeof t === "string" && STRONG_CONTEXT_TYPES.has(t)) return true;
152
+ }
153
+ if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
154
+ return false;
155
+ }
156
+ };
157
+ //#endregion
158
+ //#region src/sanitization/binary_content_redactor.ts
130
159
  const STRONG_CONTEXT_MIN_LENGTH = 64;
131
160
  const WEAK_CONTEXT_MIN_LENGTH = 1024;
132
- class BinaryContentRedactor {
133
- visited = new WeakSet();
134
- constructor(recognizer = new Base64Recognizer()) {
135
- this.recognizer = recognizer;
136
- }
137
- redact(value, mediaType) {
138
- this.visited = new WeakSet();
139
- return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
140
- }
141
- walk(value, ctx) {
142
- if (value === null || value === undefined) return value;
143
- if (typeof value === 'string') return this.redactString(value, ctx);
144
- if (typeof value !== 'object') return value;
145
-
146
- // Buffer extends Uint8Array, so this branch catches both.
147
- if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
148
- return this.placeholderFor(ctx.inferMediaType());
149
- }
150
- if (this.visited.has(value)) return null;
151
- this.visited.add(value);
152
- if (Array.isArray(value)) {
153
- return value.map(item => this.walk(item, ctx));
154
- }
155
- const obj = value;
156
- const out = {};
157
- for (const k of Object.keys(obj)) {
158
- out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
159
- }
160
- return out;
161
- }
162
- redactString(value, ctx) {
163
- const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
164
- const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, '') : value;
165
- const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
166
- const recognition = this.recognizer.recognize(recognitionValue, minLength);
167
- switch (recognition.kind) {
168
- case 'data-url':
169
- return this.placeholderFor(recognition.mediaType);
170
- case 'raw':
171
- return this.placeholderFor(ctx.inferMediaType());
172
- case 'none':
173
- return value;
174
- }
175
- }
176
- placeholderFor(mediaType) {
177
- if (!mediaType) return '[base64 redacted]';
178
- if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
179
- return `[base64 ${mediaType} redacted]`;
180
- }
181
- }
182
-
161
+ var BinaryContentRedactor = class {
162
+ constructor(recognizer = new Base64Recognizer()) {
163
+ this.recognizer = recognizer;
164
+ this.visited = /* @__PURE__ */ new WeakSet();
165
+ }
166
+ redact(value, mediaType) {
167
+ this.visited = /* @__PURE__ */ new WeakSet();
168
+ return this.walk(value, mediaType ? new MediaTypeContext(void 0, void 0, mediaType) : MediaTypeContext.EMPTY);
169
+ }
170
+ walk(value, ctx) {
171
+ if (value === null || value === void 0) return value;
172
+ if (typeof value === "string") return this.redactString(value, ctx);
173
+ if (typeof value !== "object") return value;
174
+ if (typeof Uint8Array !== "undefined" && value instanceof Uint8Array) return this.placeholderFor(ctx.inferMediaType());
175
+ if (this.visited.has(value)) return null;
176
+ this.visited.add(value);
177
+ if (Array.isArray(value)) return value.map((item) => this.walk(item, ctx));
178
+ const obj = value;
179
+ const out = {};
180
+ for (const k of Object.keys(obj)) out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
181
+ return out;
182
+ }
183
+ redactString(value, ctx) {
184
+ const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
185
+ const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
186
+ const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
187
+ const recognition = this.recognizer.recognize(recognitionValue, minLength);
188
+ switch (recognition.kind) {
189
+ case "data-url": return this.placeholderFor(recognition.mediaType);
190
+ case "raw": return this.placeholderFor(ctx.inferMediaType());
191
+ case "none": return value;
192
+ }
193
+ }
194
+ placeholderFor(mediaType) {
195
+ if (!mediaType) return "[base64 redacted]";
196
+ if (mediaType === "application/octet-stream") return "[base64 file redacted]";
197
+ return `[base64 ${mediaType} redacted]`;
198
+ }
199
+ };
200
+ //#endregion
201
+ //#region src/sanitization.ts
183
202
  const redactor = new BinaryContentRedactor();
184
203
  const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
185
204
  const sanitizeAnthropic = (data, client) => sanitize(data, client);
186
-
187
- const TOKEN_PROPERTY_KEYS = new Set(['$ai_input_tokens', '$ai_output_tokens', '$ai_cache_read_input_tokens', '$ai_cache_creation_input_tokens', '$ai_total_tokens', '$ai_reasoning_tokens']);
188
-
205
+ //#endregion
206
+ //#region src/utils.ts
207
+ const TOKEN_PROPERTY_KEYS = /* @__PURE__ */ new Set([
208
+ "$ai_input_tokens",
209
+ "$ai_output_tokens",
210
+ "$ai_cache_read_input_tokens",
211
+ "$ai_cache_creation_input_tokens",
212
+ "$ai_total_tokens",
213
+ "$ai_reasoning_tokens"
214
+ ]);
189
215
  /**
190
- * Whether the caller supplied their own token counts, which override the ones the SDK
191
- * derived from the provider response.
192
- */
216
+ * Whether the caller supplied their own token counts, which override the ones the SDK
217
+ * derived from the provider response.
218
+ */
193
219
  function hasTokenOverrides(posthogProperties) {
194
- return !!posthogProperties && Object.keys(posthogProperties).some(key => TOKEN_PROPERTY_KEYS.has(key));
220
+ return !!posthogProperties && Object.keys(posthogProperties).some((key) => TOKEN_PROPERTY_KEYS.has(key));
195
221
  }
196
222
  function getTokensSource(posthogProperties) {
197
- return hasTokenOverrides(posthogProperties) ? 'passthrough' : 'sdk';
223
+ return hasTokenOverrides(posthogProperties) ? "passthrough" : "sdk";
198
224
  }
199
- const STRING_FORMAT = 'utf8';
200
-
201
- // Reused across calls to avoid per-invocation allocation; truncate() runs
202
- // hundreds of times for prompts with many parts.
225
+ const STRING_FORMAT = "utf8";
203
226
  new TextEncoder();
204
- new TextDecoder(STRING_FORMAT, {
205
- fatal: false
206
- });
227
+ new TextDecoder(STRING_FORMAT, { fatal: false });
207
228
  const getModelParams = (params, responseServiceTier) => {
208
- if (!params) {
209
- return {};
210
- }
211
- const modelParams = {};
212
- 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'];
213
- for (const key of paramKeys) {
214
- if (key in params && params[key] !== undefined) {
215
- modelParams[key] = params[key];
216
- }
217
- }
218
- return modelParams;
229
+ if (!params) return {};
230
+ const modelParams = {};
231
+ for (const key of [
232
+ "temperature",
233
+ "max_tokens",
234
+ "max_completion_tokens",
235
+ "top_p",
236
+ "frequency_penalty",
237
+ "presence_penalty",
238
+ "n",
239
+ "stop",
240
+ "stream",
241
+ "streaming",
242
+ "language",
243
+ "response_format",
244
+ "timestamp_granularities",
245
+ "service_tier"
246
+ ]) if (key in params && params[key] !== void 0) modelParams[key] = params[key];
247
+ if (responseServiceTier != null) modelParams.service_tier = responseServiceTier;
248
+ return modelParams;
219
249
  };
220
- const formatResponseAnthropic = response => {
221
- const output = [];
222
- const content = [];
223
- for (const choice of response.content ?? []) {
224
- if (choice?.type === 'text' && choice?.text) {
225
- content.push({
226
- type: 'text',
227
- text: choice.text
228
- });
229
- } else if (choice?.type === 'tool_use' && choice?.name && choice?.id) {
230
- content.push({
231
- type: 'function',
232
- id: choice.id,
233
- function: {
234
- name: choice.name,
235
- arguments: choice.input || {}
236
- }
237
- });
238
- }
239
- }
240
- if (content.length > 0) {
241
- output.push({
242
- role: 'assistant',
243
- content
244
- });
245
- }
246
- return output;
250
+ const formatResponseAnthropic = (response) => {
251
+ const output = [];
252
+ const content = [];
253
+ for (const choice of response.content ?? []) if (choice?.type === "text" && choice?.text) content.push({
254
+ type: "text",
255
+ text: choice.text
256
+ });
257
+ else if (choice?.type === "tool_use" && choice?.name && choice?.id) content.push({
258
+ type: "function",
259
+ id: choice.id,
260
+ function: {
261
+ name: choice.name,
262
+ arguments: choice.input || {}
263
+ }
264
+ });
265
+ if (content.length > 0) output.push({
266
+ role: "assistant",
267
+ content
268
+ });
269
+ return output;
247
270
  };
248
271
  const mergeSystemPrompt = (params, provider) => {
249
- {
250
- const messages = params.messages || [];
251
- if (!params.system) {
252
- return messages;
253
- }
254
- const systemMessage = params.system;
255
- return [{
256
- role: 'system',
257
- content: systemMessage
258
- }, ...messages];
259
- }
272
+ if (provider == "anthropic") {
273
+ const messages = params.messages || [];
274
+ if (!params.system) return messages;
275
+ return [{
276
+ role: "system",
277
+ content: params.system
278
+ }, ...messages];
279
+ }
280
+ return params.messages;
260
281
  };
261
282
  const withPrivacyMode = (client, privacyMode, input) => {
262
- return client.privacy_mode || privacyMode ? null : input;
283
+ return client.privacy_mode || privacyMode ? null : input;
263
284
  };
264
-
265
285
  /**
266
- * Extract available tool calls from the request parameters.
267
- * These are the tools provided to the LLM, not the tool calls in the response.
268
- */
286
+ * Extract available tool calls from the request parameters.
287
+ * These are the tools provided to the LLM, not the tool calls in the response.
288
+ */
269
289
  const extractAvailableToolCalls = (provider, params) => {
270
- {
271
- if (params.tools) {
272
- return params.tools;
273
- }
274
- return null;
275
- }
290
+ if (provider === "anthropic") {
291
+ if (params.tools) return params.tools;
292
+ return null;
293
+ } else if (provider === "gemini") {
294
+ if (params.config && params.config.tools) return params.config.tools;
295
+ return null;
296
+ } else if (provider === "openai") {
297
+ if (params.tools) return params.tools;
298
+ return null;
299
+ } else if (provider === "vercel") {
300
+ if (params.tools) return params.tools;
301
+ return null;
302
+ }
303
+ return null;
276
304
  };
277
- let AIEvent = /*#__PURE__*/function (AIEvent) {
278
- AIEvent["Generation"] = "$ai_generation";
279
- AIEvent["Embedding"] = "$ai_embedding";
280
- return AIEvent;
281
- }({});
282
305
  function sanitizeValues(obj) {
283
- if (obj === undefined || obj === null) {
284
- return obj;
285
- }
286
- const jsonSafe = JSON.parse(JSON.stringify(obj));
287
- if (typeof jsonSafe === 'string') {
288
- // Sanitize lone surrogates by round-tripping through UTF-8
289
- return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
290
- } else if (Array.isArray(jsonSafe)) {
291
- return jsonSafe.map(sanitizeValues);
292
- } else if (jsonSafe && typeof jsonSafe === 'object') {
293
- return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
294
- }
295
- return jsonSafe;
306
+ if (obj === void 0 || obj === null) return obj;
307
+ const jsonSafe = JSON.parse(JSON.stringify(obj));
308
+ if (typeof jsonSafe === "string") return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
309
+ else if (Array.isArray(jsonSafe)) return jsonSafe.map(sanitizeValues);
310
+ else if (jsonSafe && typeof jsonSafe === "object") return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
311
+ return jsonSafe;
296
312
  }
297
313
  const POSTHOG_PARAMS_MAP = {
298
- posthogDistinctId: 'distinctId',
299
- posthogTraceId: 'traceId',
300
- posthogProperties: 'properties',
301
- posthogPrivacyMode: 'privacyMode',
302
- posthogGroups: 'groups',
303
- posthogModelOverride: 'modelOverride',
304
- posthogProviderOverride: 'providerOverride',
305
- posthogCostOverride: 'costOverride',
306
- posthogCaptureImmediate: 'captureImmediate'
314
+ posthogDistinctId: "distinctId",
315
+ posthogTraceId: "traceId",
316
+ posthogProperties: "properties",
317
+ posthogPrivacyMode: "privacyMode",
318
+ posthogGroups: "groups",
319
+ posthogModelOverride: "modelOverride",
320
+ posthogProviderOverride: "providerOverride",
321
+ posthogCostOverride: "costOverride",
322
+ posthogCaptureImmediate: "captureImmediate"
307
323
  };
308
324
  function extractPosthogParams(body) {
309
- const providerParams = {};
310
- const posthogParams = {};
311
- for (const [key, value] of Object.entries(body)) {
312
- if (POSTHOG_PARAMS_MAP[key]) {
313
- posthogParams[POSTHOG_PARAMS_MAP[key]] = value;
314
- } else if (key.startsWith('posthog')) {
315
- console.warn(`Unknown Posthog parameter ${key}`);
316
- } else {
317
- providerParams[key] = value;
318
- }
319
- }
320
- return {
321
- providerParams: providerParams,
322
- posthogParams: addDefaults(posthogParams)
323
- };
325
+ const providerParams = {};
326
+ const posthogParams = {};
327
+ for (const [key, value] of Object.entries(body)) if (POSTHOG_PARAMS_MAP[key]) posthogParams[POSTHOG_PARAMS_MAP[key]] = value;
328
+ else if (key.startsWith("posthog")) console.warn(`Unknown Posthog parameter ${key}`);
329
+ else providerParams[key] = value;
330
+ return {
331
+ providerParams,
332
+ posthogParams: addDefaults(posthogParams)
333
+ };
324
334
  }
325
335
  function addDefaults(params) {
326
- return {
327
- ...params,
328
- privacyMode: params.privacyMode ?? false,
329
- traceId: params.traceId ?? v4()
330
- };
336
+ return {
337
+ ...params,
338
+ privacyMode: params.privacyMode ?? false,
339
+ traceId: params.traceId ?? v4()
340
+ };
331
341
  }
332
-
333
- var version = "8.10.0";
334
-
342
+ //#endregion
343
+ //#region package.json
344
+ var version = "8.10.2";
345
+ //#endregion
346
+ //#region src/serializeError.ts
335
347
  const DEFAULT_MAX_DEPTH = 3;
336
348
  const MAX_STACK_LINES = 20;
337
349
  function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
338
- if (depth < 0 || value === null || typeof value !== 'object') {
339
- return value;
340
- }
341
- if (value instanceof Error) {
342
- const out = {
343
- name: value.name,
344
- message: value.message,
345
- stack: truncateStack(value.stack)
346
- };
347
- for (const key of Object.keys(value)) {
348
- out[key] = serializeError(value[key], depth - 1);
349
- }
350
- if (value.cause !== undefined) {
351
- out.cause = serializeError(value.cause, depth - 1);
352
- }
353
- return out;
354
- }
355
- if (Array.isArray(value)) {
356
- return value.map(item => serializeError(item, depth - 1));
357
- }
358
- return value;
350
+ if (depth < 0 || value === null || typeof value !== "object") return value;
351
+ if (value instanceof Error) {
352
+ const out = {
353
+ name: value.name,
354
+ message: value.message,
355
+ stack: truncateStack(value.stack)
356
+ };
357
+ for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
358
+ if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
359
+ return out;
360
+ }
361
+ if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
362
+ return value;
359
363
  }
360
364
  function stringifyError(error) {
361
- try {
362
- return JSON.stringify(sanitizeValues(serializeError(error)));
363
- } catch {
364
- if (error instanceof Error) {
365
- return JSON.stringify({
366
- name: error.name,
367
- message: error.message
368
- });
369
- }
370
- return JSON.stringify({
371
- message: String(error)
372
- });
373
- }
365
+ try {
366
+ return JSON.stringify(sanitizeValues(serializeError(error)));
367
+ } catch {
368
+ if (error instanceof Error) return JSON.stringify({
369
+ name: error.name,
370
+ message: error.message
371
+ });
372
+ return JSON.stringify({ message: String(error) });
373
+ }
374
374
  }
375
375
  function truncateStack(stack) {
376
- if (!stack) {
377
- return stack;
378
- }
379
- const lines = stack.split('\n');
380
- if (lines.length <= MAX_STACK_LINES) {
381
- return stack;
382
- }
383
- return [...lines.slice(0, MAX_STACK_LINES), '... (truncated)'].join('\n');
376
+ if (!stack) return stack;
377
+ const lines = stack.split("\n");
378
+ if (lines.length <= MAX_STACK_LINES) return stack;
379
+ return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
384
380
  }
385
-
386
- // Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway
387
- // emits its own $ai_generation, so each call would be captured (and, for billable
388
- // products, billed) twice. We only warn — the wrapper's event carries data the
389
- // gateway never sees (groups, custom properties, trace hierarchy).
390
-
391
- // Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the
392
- // main repo). gateway.us.posthog.com is live today; the rest are listed ahead of
393
- // any traffic moving to them.
394
- 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'];
395
-
396
- // Swap for the dedicated AI Gateway page once it ships.
397
- const GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability';
398
- const extractHost = baseURL => {
399
- try {
400
- // Tolerate bare hosts that omit a scheme, e.g. "gateway.us.posthog.com/v1".
401
- const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
402
- return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
403
- } catch {
404
- return undefined;
405
- }
381
+ //#endregion
382
+ //#region src/gatewayWarning.ts
383
+ const POSTHOG_AI_GATEWAY_HOSTS = [
384
+ "gateway.posthog.com",
385
+ "gateway.us.posthog.com",
386
+ "gateway.eu.posthog.com",
387
+ "ai-gateway.us.posthog.com",
388
+ "ai-gateway.eu.posthog.com"
389
+ ];
390
+ const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
391
+ const extractHost = (baseURL) => {
392
+ try {
393
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
394
+ return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
395
+ } catch {
396
+ return;
397
+ }
406
398
  };
407
- const isPostHogAiGatewayUrl = baseURL => {
408
- if (!baseURL) {
409
- return false;
410
- }
411
- const host = extractHost(baseURL);
412
- return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
399
+ const isPostHogAiGatewayUrl = (baseURL) => {
400
+ if (!baseURL) return false;
401
+ const host = extractHost(baseURL);
402
+ return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
413
403
  };
414
-
415
- // Warns on every gateway call by design: the misconfiguration is impossible to
416
- // miss that way, and a doubled bill is worse than noisy logs.
417
- const warnIfPostHogAiGateway = baseURL => {
418
- if (!isPostHogAiGatewayUrl(baseURL)) {
419
- return;
420
- }
421
- 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}.`);
404
+ const warnIfPostHogAiGateway = (baseURL) => {
405
+ if (!isPostHogAiGatewayUrl(baseURL)) return;
406
+ 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}.`);
422
407
  };
423
-
408
+ //#endregion
409
+ //#region src/captureAiGeneration.ts
424
410
  /**
425
- * Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
426
- * directly so that any caller — first-party SDK wrappers and external code
427
- * alike produces an identical event.
428
- */
429
-
430
- /**
431
- * Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
432
- *
433
- * This is the canonical primitive that every `@posthog/ai` wrapper
434
- * (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
435
- * external code can use it directly to instrument LLM calls made through
436
- * arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
437
- * same events the SDK wrappers produce.
438
- *
439
- * When `error` is set, the event is captured as an error. If the error is an
440
- * object, it is mutated in place to set `__posthog_previously_captured_error`
441
- * so callers can re-throw the original error reference safely.
442
- */
411
+ * Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
412
+ *
413
+ * This is the canonical primitive that every `@posthog/ai` wrapper
414
+ * (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
415
+ * external code can use it directly to instrument LLM calls made through
416
+ * arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
417
+ * same events the SDK wrappers produce.
418
+ *
419
+ * When `error` is set, the event is captured as an error. If the error is an
420
+ * object, it is mutated in place to set `__posthog_previously_captured_error`
421
+ * so callers can re-throw the original error reference safely.
422
+ */
443
423
  const captureAiGeneration = async (client, options) => {
444
- try {
445
- if (!client.capture) {
446
- return;
447
- }
448
- warnIfPostHogAiGateway(options.baseURL);
449
- const traceId = options.traceId ?? v4();
450
- const eventType = options.eventType ?? AIEvent.Generation;
451
- const privacyMode = options.privacyMode ?? false;
452
- const usage = options.usage ?? {};
453
-
454
- // Check privacy before reading or traversing input/output. Besides avoiding
455
- // needless work, this ensures hostile getters/proxies cannot observe a value
456
- // that the caller explicitly requested us to redact.
457
- const shouldRedact = withPrivacyMode(client, privacyMode, false) === null;
458
- const safeInput = shouldRedact ? null : toJsonSafeValue(options.input);
459
- const safeOutput = shouldRedact ? null : toJsonSafeValue(options.output);
460
- let httpStatus = options.httpStatus;
461
- let errorData = {};
462
- if (options.error) {
463
- if (httpStatus === undefined) {
464
- if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
465
- httpStatus = options.error.status;
466
- } else if (typeof options.error === 'object' && 'statusCode' in options.error && typeof options.error.statusCode === 'number') {
467
- httpStatus = options.error.statusCode;
468
- } else {
469
- httpStatus = 500;
470
- }
471
- }
472
- let exceptionId;
473
- if (client.options?.enableExceptionAutocapture) {
474
- exceptionId = uuidv7();
475
- client.captureException(options.error, undefined, {
476
- $ai_trace_id: traceId
477
- }, exceptionId);
478
- if (typeof options.error === 'object') {
479
- ;
480
- options.error.__posthog_previously_captured_error = true;
481
- }
482
- }
483
- errorData = {
484
- $ai_is_error: true,
485
- $ai_error: stringifyError(options.error),
486
- $exception_event_id: exceptionId
487
- };
488
- }
489
- httpStatus = httpStatus ?? 200;
490
-
491
- // A configured price applies only to a count the provider reported, so a call with no
492
- // reported usage sends no cost instead of asserting $0. $ai_total_cost_usd sums the sides
493
- // that were priced, which makes it the cost of the known side alone when the other side
494
- // went unreported: a lower bound on the true total, not an assertion of it.
495
- const costOverrideData = {};
496
- if (options.costOverride) {
497
- if (usage.inputTokens !== undefined) {
498
- costOverrideData.$ai_input_cost_usd = (options.costOverride.inputCost ?? 0) * usage.inputTokens;
499
- }
500
- if (usage.outputTokens !== undefined) {
501
- costOverrideData.$ai_output_cost_usd = (options.costOverride.outputCost ?? 0) * usage.outputTokens;
502
- }
503
- if (Object.keys(costOverrideData).length > 0) {
504
- costOverrideData.$ai_total_cost_usd = (costOverrideData.$ai_input_cost_usd ?? 0) + (costOverrideData.$ai_output_cost_usd ?? 0);
505
- }
506
- }
507
-
508
- // The caller's own token counts override the SDK-derived ones further down, via the
509
- // `options.properties` spread.
510
- const tokensOverridden = hasTokenOverrides(options.properties);
511
- const additionalTokenValues = {
512
- ...(usage.reasoningTokens ? {
513
- $ai_reasoning_tokens: usage.reasoningTokens
514
- } : {}),
515
- ...(usage.cacheReadInputTokens ? {
516
- $ai_cache_read_input_tokens: usage.cacheReadInputTokens
517
- } : {}),
518
- ...(usage.cacheCreationInputTokens ? {
519
- $ai_cache_creation_input_tokens: usage.cacheCreationInputTokens
520
- } : {}),
521
- // Checked against undefined rather than truthiness, because false is the meaningful
522
- // value here and a truthiness guard would drop it.
523
- //
524
- // Dropped entirely when the caller overrides the token counts: the flag describes how
525
- // the SDK-derived counts relate to each other, so against passthrough counts it can be
526
- // wrong in the expensive direction. Declaring inclusive over counts that are actually
527
- // exclusive makes ingestion subtract the cache pool that was never in the input. A
528
- // caller who knows their own accounting model can still pass
529
- // `$ai_cache_reporting_exclusive` themselves, and that value wins.
530
- ...(usage.cacheReportingExclusive !== undefined && !tokensOverridden ? {
531
- $ai_cache_reporting_exclusive: usage.cacheReportingExclusive
532
- } : {}),
533
- ...(usage.webSearchCount ? {
534
- $ai_web_search_count: usage.webSearchCount
535
- } : {}),
536
- ...(usage.rawUsage ? {
537
- $ai_usage: usage.rawUsage
538
- } : {})
539
- };
540
- const properties = {
541
- $ai_lib: 'posthog-ai',
542
- $ai_lib_version: version,
543
- $ai_provider: options.providerOverride ?? options.provider,
544
- $ai_model: options.modelOverride ?? options.model,
545
- $ai_model_parameters: options.modelParameters ?? {},
546
- $ai_input: safeInput,
547
- $ai_output_choices: safeOutput,
548
- $ai_http_status: httpStatus,
549
- ...(usage.inputTokens !== undefined ? {
550
- $ai_input_tokens: usage.inputTokens
551
- } : {}),
552
- ...(usage.outputTokens !== undefined ? {
553
- $ai_output_tokens: usage.outputTokens
554
- } : {}),
555
- ...additionalTokenValues,
556
- ...(options.latency !== undefined ? {
557
- $ai_latency: options.latency
558
- } : {}),
559
- ...(options.timeToFirstToken !== undefined ? {
560
- $ai_time_to_first_token: options.timeToFirstToken
561
- } : {}),
562
- $ai_trace_id: traceId,
563
- ...(options.baseURL === null ? {} : {
564
- $ai_base_url: options.baseURL ?? ''
565
- }),
566
- ...options.properties,
567
- $ai_tokens_source: getTokensSource(options.properties),
568
- ...(options.distinctId ? {} : {
569
- $process_person_profile: false
570
- }),
571
- ...(options.stopReason ? {
572
- $ai_stop_reason: options.stopReason
573
- } : {}),
574
- ...(options.tools ? {
575
- $ai_tools: options.tools
576
- } : {}),
577
- ...(options.completionId ? {
578
- $ai_completion_id: options.completionId
579
- } : {}),
580
- ...(options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? {
581
- $ai_provider_metadata: options.providerMetadata
582
- } : {}),
583
- ...errorData,
584
- ...costOverrideData
585
- };
586
- const event = {
587
- distinctId: options.distinctId ?? traceId,
588
- event: eventType,
589
- properties,
590
- groups: options.groups
591
- };
592
- if (options.captureImmediate) {
593
- await captureAiEventImmediate(client, event);
594
- } else {
595
- captureAiEvent(client, event);
596
- }
597
- } catch (error) {
598
- // Telemetry failures must never affect the instrumented provider call.
599
- try {
600
- options.onError?.(error);
601
- } catch {
602
- // Error reporting must not affect the instrumented provider call either.
603
- }
604
- console.warn('[PostHog AI] Failed to capture generation telemetry:', error);
605
- }
424
+ try {
425
+ if (!client.capture) return;
426
+ warnIfPostHogAiGateway(options.baseURL);
427
+ const traceId = options.traceId ?? v4();
428
+ const eventType = options.eventType ?? "$ai_generation";
429
+ const privacyMode = options.privacyMode ?? false;
430
+ const usage = options.usage ?? {};
431
+ const shouldRedact = withPrivacyMode(client, privacyMode, false) === null;
432
+ const safeInput = shouldRedact ? null : toJsonSafeValue(options.input);
433
+ const safeOutput = shouldRedact ? null : toJsonSafeValue(options.output);
434
+ let httpStatus = options.httpStatus;
435
+ let errorData = {};
436
+ if (options.error) {
437
+ if (httpStatus === void 0) {
438
+ if (typeof options.error === "object" && "status" in options.error && typeof options.error.status === "number") httpStatus = options.error.status;
439
+ else if (typeof options.error === "object" && "statusCode" in options.error && typeof options.error.statusCode === "number") httpStatus = options.error.statusCode;
440
+ else httpStatus = 500;
441
+ }
442
+ let exceptionId;
443
+ if (client.options?.enableExceptionAutocapture) {
444
+ exceptionId = uuidv7();
445
+ client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
446
+ if (typeof options.error === "object") options.error.__posthog_previously_captured_error = true;
447
+ }
448
+ errorData = {
449
+ $ai_is_error: true,
450
+ $ai_error: stringifyError(options.error),
451
+ $exception_event_id: exceptionId
452
+ };
453
+ }
454
+ httpStatus = httpStatus ?? 200;
455
+ const costOverrideData = {};
456
+ if (options.costOverride) {
457
+ if (usage.inputTokens !== void 0) costOverrideData.$ai_input_cost_usd = (options.costOverride.inputCost ?? 0) * usage.inputTokens;
458
+ if (usage.outputTokens !== void 0) costOverrideData.$ai_output_cost_usd = (options.costOverride.outputCost ?? 0) * usage.outputTokens;
459
+ if (Object.keys(costOverrideData).length > 0) costOverrideData.$ai_total_cost_usd = (costOverrideData.$ai_input_cost_usd ?? 0) + (costOverrideData.$ai_output_cost_usd ?? 0);
460
+ }
461
+ const tokensOverridden = hasTokenOverrides(options.properties);
462
+ const additionalTokenValues = {
463
+ ...usage.reasoningTokens ? { $ai_reasoning_tokens: usage.reasoningTokens } : {},
464
+ ...usage.cacheReadInputTokens ? { $ai_cache_read_input_tokens: usage.cacheReadInputTokens } : {},
465
+ ...usage.cacheCreationInputTokens ? { $ai_cache_creation_input_tokens: usage.cacheCreationInputTokens } : {},
466
+ ...usage.cacheReportingExclusive !== void 0 && !tokensOverridden ? { $ai_cache_reporting_exclusive: usage.cacheReportingExclusive } : {},
467
+ ...usage.webSearchCount ? { $ai_web_search_count: usage.webSearchCount } : {},
468
+ ...usage.rawUsage ? { $ai_usage: usage.rawUsage } : {}
469
+ };
470
+ const properties = {
471
+ $ai_lib: "posthog-ai",
472
+ $ai_lib_version: version,
473
+ $ai_provider: options.providerOverride ?? options.provider,
474
+ $ai_model: options.modelOverride ?? options.model,
475
+ $ai_model_parameters: options.modelParameters ?? {},
476
+ $ai_input: safeInput,
477
+ $ai_output_choices: safeOutput,
478
+ $ai_http_status: httpStatus,
479
+ ...usage.inputTokens !== void 0 ? { $ai_input_tokens: usage.inputTokens } : {},
480
+ ...usage.outputTokens !== void 0 ? { $ai_output_tokens: usage.outputTokens } : {},
481
+ ...additionalTokenValues,
482
+ ...options.latency !== void 0 ? { $ai_latency: options.latency } : {},
483
+ ...options.timeToFirstToken !== void 0 ? { $ai_time_to_first_token: options.timeToFirstToken } : {},
484
+ $ai_trace_id: traceId,
485
+ ...options.baseURL === null ? {} : { $ai_base_url: options.baseURL ?? "" },
486
+ ...options.properties,
487
+ $ai_tokens_source: getTokensSource(options.properties),
488
+ ...options.distinctId ? {} : { $process_person_profile: false },
489
+ ...options.stopReason ? { $ai_stop_reason: options.stopReason } : {},
490
+ ...options.tools ? { $ai_tools: options.tools } : {},
491
+ ...options.completionId ? { $ai_completion_id: options.completionId } : {},
492
+ ...options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? { $ai_provider_metadata: options.providerMetadata } : {},
493
+ ...errorData,
494
+ ...costOverrideData
495
+ };
496
+ const event = {
497
+ distinctId: options.distinctId ?? traceId,
498
+ event: eventType,
499
+ properties,
500
+ groups: options.groups
501
+ };
502
+ if (options.captureImmediate) await captureAiEventImmediate(client, event);
503
+ else captureAiEvent(client, event);
504
+ } catch (error) {
505
+ try {
506
+ options.onError?.(error);
507
+ } catch {}
508
+ console.warn("[PostHog AI] Failed to capture generation telemetry:", error);
509
+ }
606
510
  };
607
-
511
+ //#endregion
512
+ //#region src/providerPromise.ts
608
513
  function addResponseIds(result, response, requestIdHeader, workspaceIdHeader) {
609
- if (!result || typeof result !== 'object' || Array.isArray(result)) {
610
- return result;
611
- }
612
- const properties = {
613
- _request_id: {
614
- value: response.headers.get(requestIdHeader),
615
- enumerable: false
616
- }
617
- };
618
- if (workspaceIdHeader) {
619
- properties._workspace_id = {
620
- value: response.headers.get(workspaceIdHeader),
621
- enumerable: false
622
- };
623
- }
624
- return Object.defineProperties(result, properties);
514
+ if (!result || typeof result !== "object" || Array.isArray(result)) return result;
515
+ const properties = { _request_id: {
516
+ value: response.headers.get(requestIdHeader),
517
+ enumerable: false
518
+ } };
519
+ if (workspaceIdHeader) properties._workspace_id = {
520
+ value: response.headers.get(workspaceIdHeader),
521
+ enumerable: false
522
+ };
523
+ return Object.defineProperties(result, properties);
625
524
  }
626
525
  function getResponsePropsPromise(parentPromise) {
627
- const responsePromise = parentPromise.responsePromise;
628
- if (!responsePromise || typeof responsePromise.then !== 'function') {
629
- return undefined;
630
- }
631
- return responsePromise;
526
+ const responsePromise = parentPromise.responsePromise;
527
+ if (!responsePromise || typeof responsePromise.then !== "function") return;
528
+ return responsePromise;
632
529
  }
633
530
  function decorateProviderPromise(wrappedPromise, responsePropsPromise, requestIdHeader, workspaceIdHeader, preserveThenUnwrap) {
634
- const providerPromise = wrappedPromise;
635
- if (responsePropsPromise) {
636
- providerPromise.asResponse = async () => (await responsePropsPromise).response;
637
- providerPromise.withResponse = async () => {
638
- const [props, data] = await Promise.all([responsePropsPromise, wrappedPromise]);
639
- return {
640
- response: props.response,
641
- data,
642
- request_id: props.response.headers.get(requestIdHeader),
643
- ...(workspaceIdHeader ? {
644
- workspace_id: props.response.headers.get(workspaceIdHeader)
645
- } : {})
646
- };
647
- };
648
- }
649
- if (preserveThenUnwrap) {
650
- providerPromise._thenUnwrap = transform => {
651
- if (!responsePropsPromise) {
652
- throw new Error('The provider promise response metadata is unavailable');
653
- }
654
- const transformedPromise = Promise.all([wrappedPromise, responsePropsPromise]).then(([data, props]) => addResponseIds(transform(data, props), props.response, requestIdHeader, workspaceIdHeader));
655
- return decorateProviderPromise(transformedPromise, responsePropsPromise, requestIdHeader, workspaceIdHeader, true);
656
- };
657
- }
658
- return providerPromise;
531
+ const providerPromise = wrappedPromise;
532
+ if (responsePropsPromise) {
533
+ providerPromise.asResponse = async () => (await responsePropsPromise).response;
534
+ providerPromise.withResponse = async () => {
535
+ const [props, data] = await Promise.all([responsePropsPromise, wrappedPromise]);
536
+ return {
537
+ response: props.response,
538
+ data,
539
+ request_id: props.response.headers.get(requestIdHeader),
540
+ ...workspaceIdHeader ? { workspace_id: props.response.headers.get(workspaceIdHeader) } : {}
541
+ };
542
+ };
543
+ }
544
+ if (preserveThenUnwrap) providerPromise._thenUnwrap = (transform) => {
545
+ if (!responsePropsPromise) throw new Error("The provider promise response metadata is unavailable");
546
+ return decorateProviderPromise(Promise.all([wrappedPromise, responsePropsPromise]).then(([data, props]) => addResponseIds(transform(data, props), props.response, requestIdHeader, workspaceIdHeader)), responsePropsPromise, requestIdHeader, workspaceIdHeader, true);
547
+ };
548
+ return providerPromise;
659
549
  }
660
-
661
- /**
662
- * Keep the provider SDK helpers on a promise whose resolved value is instrumented.
663
- * OpenAI's parse helpers compose create calls through `_thenUnwrap`, while both
664
- * OpenAI and Anthropic expose the raw response through `asResponse` and
665
- * `withResponse`.
666
- */
667
-
668
550
  function preserveProviderPromise(parentPromise, wrappedPromise, options = {}) {
669
- const responsePropsPromise = getResponsePropsPromise(parentPromise);
670
- const preserveThenUnwrap = typeof parentPromise._thenUnwrap === 'function';
671
- const providerPromise = decorateProviderPromise(wrappedPromise, responsePropsPromise, options.requestIdHeader ?? 'x-request-id', options.workspaceIdHeader, preserveThenUnwrap);
672
- if (!responsePropsPromise) {
673
- const asResponse = parentPromise.asResponse?.bind(parentPromise);
674
- if (asResponse) {
675
- providerPromise.asResponse = asResponse;
676
- }
677
- const withResponse = parentPromise.withResponse?.bind(parentPromise);
678
- if (withResponse) {
679
- providerPromise.withResponse = async () => {
680
- const [response, data] = await Promise.all([withResponse(), wrappedPromise]);
681
- return {
682
- ...response,
683
- data
684
- };
685
- };
686
- }
687
- }
688
- return providerPromise;
551
+ const responsePropsPromise = getResponsePropsPromise(parentPromise);
552
+ const preserveThenUnwrap = typeof parentPromise._thenUnwrap === "function";
553
+ const providerPromise = decorateProviderPromise(wrappedPromise, responsePropsPromise, options.requestIdHeader ?? "x-request-id", options.workspaceIdHeader, preserveThenUnwrap);
554
+ if (!responsePropsPromise) {
555
+ const asResponse = parentPromise.asResponse?.bind(parentPromise);
556
+ if (asResponse) providerPromise.asResponse = asResponse;
557
+ const withResponse = parentPromise.withResponse?.bind(parentPromise);
558
+ if (withResponse) providerPromise.withResponse = async () => {
559
+ const [response, data] = await Promise.all([withResponse(), wrappedPromise]);
560
+ return {
561
+ ...response,
562
+ data
563
+ };
564
+ };
565
+ }
566
+ return providerPromise;
689
567
  }
690
-
568
+ //#endregion
569
+ //#region src/stream.ts
691
570
  /**
692
- * Splits an SDK stream into a monitoring branch and a caller branch without
693
- * allowing either branch to read ahead of the other. Unlike the SDKs' `tee()`
694
- * implementations, this keeps at most one result in flight and makes caller
695
- * cancellation terminate the monitoring branch and the source iterator.
696
- */
571
+ * Splits an SDK stream into a monitoring branch and a caller branch without
572
+ * allowing either branch to read ahead of the other. Unlike the SDKs' `tee()`
573
+ * implementations, this keeps at most one result in flight and makes caller
574
+ * cancellation terminate the monitoring branch and the source iterator.
575
+ */
697
576
  function monitoredStreamTee(source, createStream) {
698
- const controller = source.controller ?? new AbortController();
699
- const sourceIterator = source[Symbol.asyncIterator]();
700
- const callerQueue = [];
701
- let monitorPending;
702
- let monitorActive = true;
703
- let operationInFlight = false;
704
- let terminalResult;
705
- let bufferedMonitorResult;
706
- let terminalError;
707
- let hasTerminalError = false;
708
- let cancellationPromise;
709
- let abortListener;
710
- const removeAbortListener = () => {
711
- if (abortListener) {
712
- controller.signal.removeEventListener('abort', abortListener);
713
- abortListener = undefined;
714
- }
715
- };
716
- const settleMonitorTerminal = () => {
717
- if (!monitorPending) {
718
- return;
719
- }
720
- const pending = monitorPending;
721
- monitorPending = undefined;
722
- if (hasTerminalError) {
723
- pending.reject(terminalError);
724
- } else if (terminalResult) {
725
- pending.resolve(terminalResult);
726
- }
727
- };
728
- const settleCallersTerminal = () => {
729
- while (callerQueue.length > 0) {
730
- const pending = callerQueue.shift();
731
- if (hasTerminalError) {
732
- pending.reject(terminalError);
733
- } else if (terminalResult) {
734
- pending.resolve(terminalResult);
735
- }
736
- }
737
- };
738
- const pump = () => {
739
- if (operationInFlight || callerQueue.length === 0 || monitorActive && !monitorPending) {
740
- return;
741
- }
742
- const pendingCaller = callerQueue.shift();
743
- const pendingMonitor = monitorPending;
744
- monitorPending = undefined;
745
- operationInFlight = true;
746
- void sourceIterator.next().then(result => {
747
- operationInFlight = false;
748
- if (result.done) {
749
- terminalResult = result;
750
- removeAbortListener();
751
- }
752
- pendingCaller.resolve(result);
753
- pendingMonitor?.resolve(result);
754
- if (result.done) {
755
- settleCallersTerminal();
756
- } else {
757
- pump();
758
- }
759
- }, error => {
760
- operationInFlight = false;
761
- terminalError = error;
762
- hasTerminalError = true;
763
- removeAbortListener();
764
- pendingCaller.reject(error);
765
- pendingMonitor?.reject(error);
766
- settleCallersTerminal();
767
- });
768
- };
769
- const monitoringStream = {
770
- [Symbol.asyncIterator]() {
771
- return {
772
- next: () => {
773
- if (hasTerminalError) {
774
- return Promise.reject(terminalError);
775
- }
776
- if (terminalResult) {
777
- return Promise.resolve(terminalResult);
778
- }
779
- if (bufferedMonitorResult) {
780
- const result = bufferedMonitorResult;
781
- bufferedMonitorResult = undefined;
782
- return Promise.resolve(result);
783
- }
784
- return new Promise((resolve, reject) => {
785
- monitorPending = {
786
- resolve,
787
- reject
788
- };
789
- pump();
790
- });
791
- },
792
- return: async value => {
793
- monitorActive = false;
794
- monitorPending = undefined;
795
- pump();
796
- return {
797
- done: true,
798
- value: value
799
- };
800
- }
801
- };
802
- }
803
- };
804
- const cancelSource = value => {
805
- if (cancellationPromise) {
806
- return cancellationPromise;
807
- }
808
- removeAbortListener();
809
- if (!controller.signal.aborted) {
810
- controller.abort();
811
- }
812
- cancellationPromise = (async () => {
813
- try {
814
- const defaultResult = {
815
- done: true,
816
- value
817
- };
818
- const result = sourceIterator.return ? await sourceIterator.return(value) : defaultResult;
819
- if (result.done) {
820
- terminalResult = result;
821
- removeAbortListener();
822
- settleMonitorTerminal();
823
- settleCallersTerminal();
824
- } else if (monitorPending) {
825
- monitorPending.resolve(result);
826
- monitorPending = undefined;
827
- cancellationPromise = undefined;
828
- } else {
829
- bufferedMonitorResult = result;
830
- cancellationPromise = undefined;
831
- }
832
- return result;
833
- } catch (error) {
834
- terminalError = error;
835
- hasTerminalError = true;
836
- removeAbortListener();
837
- settleMonitorTerminal();
838
- settleCallersTerminal();
839
- throw error;
840
- }
841
- })();
842
- // An AbortController cancellation has no caller awaiting this promise.
843
- void cancellationPromise.catch(() => undefined);
844
- return cancellationPromise;
845
- };
846
- abortListener = () => {
847
- void cancelSource();
848
- };
849
- if (controller.signal.aborted) {
850
- abortListener();
851
- } else {
852
- controller.signal.addEventListener('abort', abortListener, {
853
- once: true
854
- });
855
- }
856
- const callerStream = createStream(() => ({
857
- next: () => {
858
- if (hasTerminalError) {
859
- return Promise.reject(terminalError);
860
- }
861
- if (terminalResult) {
862
- return Promise.resolve(terminalResult);
863
- }
864
- return new Promise((resolve, reject) => {
865
- callerQueue.push({
866
- resolve,
867
- reject
868
- });
869
- pump();
870
- });
871
- },
872
- return: value => cancelSource(value),
873
- throw: async error => {
874
- if (!sourceIterator.throw) {
875
- await cancelSource();
876
- throw error;
877
- }
878
- try {
879
- const result = await sourceIterator.throw(error);
880
- if (result.done) {
881
- terminalResult = result;
882
- removeAbortListener();
883
- settleCallersTerminal();
884
- }
885
- if (monitorPending) {
886
- monitorPending.resolve(result);
887
- monitorPending = undefined;
888
- } else {
889
- bufferedMonitorResult = result;
890
- }
891
- return result;
892
- } catch (sourceError) {
893
- terminalError = sourceError;
894
- hasTerminalError = true;
895
- removeAbortListener();
896
- settleMonitorTerminal();
897
- settleCallersTerminal();
898
- throw sourceError;
899
- }
900
- }
901
- }), controller);
902
- return [monitoringStream, callerStream];
903
- }
904
-
905
- class PostHogAnthropic extends AnthropicOriginal {
906
- constructor(config) {
907
- const {
908
- posthog,
909
- ...anthropicConfig
910
- } = config;
911
- super(anthropicConfig);
912
- this.phClient = posthog;
913
- this.messages = new WrappedMessages(this, this.phClient);
914
- }
915
- }
916
- class WrappedMessages extends AnthropicOriginal.Messages {
917
- constructor(parentClient, phClient) {
918
- super(parentClient);
919
- this.phClient = phClient;
920
- this.baseURL = parentClient.baseURL;
921
- }
922
- create(body, options) {
923
- const {
924
- providerParams: anthropicParams,
925
- posthogParams
926
- } = extractPosthogParams(body);
927
- const startTime = Date.now();
928
- if (anthropicParams.stream) {
929
- const parentPromise = super.create(anthropicParams, options);
930
- const wrappedPromise = parentPromise.then(value => {
931
- let accumulatedContent = '';
932
- const contentBlocks = [];
933
- const toolsInProgress = new Map();
934
- let currentTextBlock = null;
935
- let firstTokenTime;
936
- let stopReason;
937
- const usage = {
938
- webSearchCount: 0
939
- };
940
- let rawUsage = {};
941
- if (Symbol.asyncIterator in value) {
942
- const [stream1, stream2] = monitoredStreamTee(value, (iterator, controller) => new Stream(iterator, controller));
943
- (async () => {
944
- try {
945
- for await (const chunk of stream1) {
946
- // Handle content block start events
947
- if (chunk.type === 'content_block_start') {
948
- if (chunk.content_block?.type === 'text') {
949
- currentTextBlock = {
950
- type: 'text',
951
- text: ''
952
- };
953
- contentBlocks.push(currentTextBlock);
954
- } else if (chunk.content_block?.type === 'tool_use') {
955
- if (firstTokenTime === undefined) {
956
- firstTokenTime = Date.now();
957
- }
958
- const toolBlock = {
959
- type: 'function',
960
- id: chunk.content_block.id,
961
- function: {
962
- name: chunk.content_block.name,
963
- arguments: {}
964
- }
965
- };
966
- contentBlocks.push(toolBlock);
967
- toolsInProgress.set(chunk.content_block.id, {
968
- block: toolBlock,
969
- inputString: ''
970
- });
971
- currentTextBlock = null;
972
- }
973
- }
974
-
975
- // Handle text delta events
976
- if ('delta' in chunk) {
977
- if ('text' in chunk.delta) {
978
- const delta = chunk.delta.text;
979
- if (firstTokenTime === undefined) {
980
- firstTokenTime = Date.now();
981
- }
982
- accumulatedContent += delta;
983
- if (currentTextBlock) {
984
- currentTextBlock.text += delta;
985
- }
986
- }
987
- }
988
-
989
- // Handle tool input delta events
990
- if (chunk.type === 'content_block_delta' && chunk.delta?.type === 'input_json_delta') {
991
- const block = chunk.index !== undefined ? contentBlocks[chunk.index] : undefined;
992
- const toolId = block?.type === 'function' ? block.id : undefined;
993
- if (toolId && toolsInProgress.has(toolId)) {
994
- const tool = toolsInProgress.get(toolId);
995
- if (tool) {
996
- tool.inputString += chunk.delta.partial_json || '';
997
- }
998
- }
999
- }
1000
-
1001
- // Handle content block stop events
1002
- if (chunk.type === 'content_block_stop') {
1003
- currentTextBlock = null;
1004
-
1005
- // Parse accumulated tool input
1006
- if (chunk.index !== undefined) {
1007
- const block = contentBlocks[chunk.index];
1008
- if (block?.type === 'function' && block.id && toolsInProgress.has(block.id)) {
1009
- const tool = toolsInProgress.get(block.id);
1010
- if (tool) {
1011
- try {
1012
- block.function.arguments = JSON.parse(tool.inputString);
1013
- } catch (e) {
1014
- // Keep empty object if parsing fails
1015
- console.error('Error parsing tool input:', e);
1016
- }
1017
- }
1018
- toolsInProgress.delete(block.id);
1019
- }
1020
- }
1021
- }
1022
- if (chunk.type == 'message_start') {
1023
- rawUsage = {
1024
- ...chunk.message.usage
1025
- };
1026
- usage.inputTokens = chunk.message.usage.input_tokens ?? 0;
1027
- usage.cacheCreationInputTokens = chunk.message.usage.cache_creation_input_tokens ?? 0;
1028
- usage.cacheReadInputTokens = chunk.message.usage.cache_read_input_tokens ?? 0;
1029
- usage.webSearchCount = chunk.message.usage.server_tool_use?.web_search_requests ?? 0;
1030
- }
1031
- if ('usage' in chunk) {
1032
- rawUsage = {
1033
- ...rawUsage,
1034
- ...Object.fromEntries(Object.entries(chunk.usage).filter(([, value]) => value != null))
1035
- };
1036
- usage.outputTokens = chunk.usage.output_tokens ?? 0;
1037
- // Update web search count if present in delta
1038
- if (chunk.usage.server_tool_use?.web_search_requests !== undefined) {
1039
- usage.webSearchCount = chunk.usage.server_tool_use.web_search_requests;
1040
- }
1041
- }
1042
- if (chunk.type === 'message_delta' && 'delta' in chunk) {
1043
- const delta = chunk.delta;
1044
- if ('stop_reason' in delta && typeof delta.stop_reason === 'string' && delta.stop_reason) {
1045
- stopReason = delta.stop_reason;
1046
- }
1047
- }
1048
- }
1049
- usage.rawUsage = rawUsage;
1050
- const latency = (Date.now() - startTime) / 1000;
1051
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1052
- const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
1053
-
1054
- // Format output to match non-streaming version
1055
- const formattedOutput = contentBlocks.length > 0 ? [{
1056
- role: 'assistant',
1057
- content: contentBlocks
1058
- }] : [{
1059
- role: 'assistant',
1060
- content: [{
1061
- type: 'text',
1062
- text: accumulatedContent
1063
- }]
1064
- }];
1065
- await captureAiGeneration(this.phClient, {
1066
- ...posthogParams,
1067
- model: anthropicParams.model,
1068
- provider: 'anthropic',
1069
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, 'anthropic'), this.phClient),
1070
- output: formattedOutput,
1071
- latency,
1072
- timeToFirstToken,
1073
- baseURL: this.baseURL,
1074
- modelParameters: getModelParams(body),
1075
- httpStatus: 200,
1076
- usage,
1077
- stopReason,
1078
- tools: availableTools
1079
- });
1080
- } catch (error) {
1081
- // The final usage delta may never arrive; whatever raw usage did
1082
- // still belongs on the event.
1083
- if (Object.keys(rawUsage).length > 0) {
1084
- usage.rawUsage = rawUsage;
1085
- }
1086
- await captureAiGeneration(this.phClient, {
1087
- ...posthogParams,
1088
- model: anthropicParams.model,
1089
- provider: 'anthropic',
1090
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams), this.phClient),
1091
- output: [],
1092
- latency: (Date.now() - startTime) / 1000,
1093
- baseURL: this.baseURL,
1094
- modelParameters: getModelParams(body),
1095
- usage,
1096
- error: error
1097
- });
1098
- throw error;
1099
- }
1100
- })().catch(() => {
1101
- // Swallow: analytics must never crash the host process. The caller
1102
- // already receives this error via their own tee of the stream.
1103
- });
1104
-
1105
- // Return the other stream to the user
1106
- return stream2;
1107
- }
1108
- return value;
1109
- });
1110
- return preserveProviderPromise(parentPromise, wrappedPromise, {
1111
- requestIdHeader: 'request-id',
1112
- workspaceIdHeader: 'anthropic-workspace-id'
1113
- });
1114
- } else {
1115
- const parentPromise = super.create(anthropicParams, options);
1116
- const wrappedPromise = parentPromise.then(async result => {
1117
- if ('content' in result) {
1118
- const latency = (Date.now() - startTime) / 1000;
1119
- const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
1120
- await captureAiGeneration(this.phClient, {
1121
- ...posthogParams,
1122
- model: anthropicParams.model,
1123
- provider: 'anthropic',
1124
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams), this.phClient),
1125
- output: formatResponseAnthropic(result),
1126
- latency,
1127
- baseURL: this.baseURL,
1128
- modelParameters: getModelParams(body),
1129
- httpStatus: 200,
1130
- usage: {
1131
- inputTokens: result.usage.input_tokens ?? 0,
1132
- outputTokens: result.usage.output_tokens ?? 0,
1133
- cacheCreationInputTokens: result.usage.cache_creation_input_tokens ?? 0,
1134
- cacheReadInputTokens: result.usage.cache_read_input_tokens ?? 0,
1135
- webSearchCount: result.usage.server_tool_use?.web_search_requests ?? 0,
1136
- rawUsage: result.usage
1137
- },
1138
- stopReason: result.stop_reason ?? undefined,
1139
- tools: availableTools
1140
- });
1141
- }
1142
- return result;
1143
- }, async error => {
1144
- await captureAiGeneration(this.phClient, {
1145
- ...posthogParams,
1146
- model: anthropicParams.model,
1147
- provider: 'anthropic',
1148
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams), this.phClient),
1149
- output: [],
1150
- latency: (Date.now() - startTime) / 1000,
1151
- baseURL: this.baseURL,
1152
- modelParameters: getModelParams(body),
1153
- httpStatus: error?.status ? error.status : 500,
1154
- usage: {},
1155
- error: error
1156
- });
1157
- throw error;
1158
- });
1159
- return preserveProviderPromise(parentPromise, wrappedPromise, {
1160
- requestIdHeader: 'request-id',
1161
- workspaceIdHeader: 'anthropic-workspace-id'
1162
- });
1163
- }
1164
- }
577
+ const controller = source.controller ?? new AbortController();
578
+ const sourceIterator = source[Symbol.asyncIterator]();
579
+ const callerQueue = [];
580
+ let monitorPending;
581
+ let monitorActive = true;
582
+ let operationInFlight = false;
583
+ let terminalResult;
584
+ let bufferedMonitorResult;
585
+ let terminalError;
586
+ let hasTerminalError = false;
587
+ let cancellationPromise;
588
+ let abortListener;
589
+ const removeAbortListener = () => {
590
+ if (abortListener) {
591
+ controller.signal.removeEventListener("abort", abortListener);
592
+ abortListener = void 0;
593
+ }
594
+ };
595
+ const settleMonitorTerminal = () => {
596
+ if (!monitorPending) return;
597
+ const pending = monitorPending;
598
+ monitorPending = void 0;
599
+ if (hasTerminalError) pending.reject(terminalError);
600
+ else if (terminalResult) pending.resolve(terminalResult);
601
+ };
602
+ const settleCallersTerminal = () => {
603
+ while (callerQueue.length > 0) {
604
+ const pending = callerQueue.shift();
605
+ if (hasTerminalError) pending.reject(terminalError);
606
+ else if (terminalResult) pending.resolve(terminalResult);
607
+ }
608
+ };
609
+ const pump = () => {
610
+ if (operationInFlight || callerQueue.length === 0 || monitorActive && !monitorPending) return;
611
+ const pendingCaller = callerQueue.shift();
612
+ const pendingMonitor = monitorPending;
613
+ monitorPending = void 0;
614
+ operationInFlight = true;
615
+ sourceIterator.next().then((result) => {
616
+ operationInFlight = false;
617
+ if (result.done) {
618
+ terminalResult = result;
619
+ removeAbortListener();
620
+ }
621
+ pendingCaller.resolve(result);
622
+ pendingMonitor?.resolve(result);
623
+ if (result.done) settleCallersTerminal();
624
+ else pump();
625
+ }, (error) => {
626
+ operationInFlight = false;
627
+ terminalError = error;
628
+ hasTerminalError = true;
629
+ removeAbortListener();
630
+ pendingCaller.reject(error);
631
+ pendingMonitor?.reject(error);
632
+ settleCallersTerminal();
633
+ });
634
+ };
635
+ const monitoringStream = { [Symbol.asyncIterator]() {
636
+ return {
637
+ next: () => {
638
+ if (hasTerminalError) return Promise.reject(terminalError);
639
+ if (terminalResult) return Promise.resolve(terminalResult);
640
+ if (bufferedMonitorResult) {
641
+ const result = bufferedMonitorResult;
642
+ bufferedMonitorResult = void 0;
643
+ return Promise.resolve(result);
644
+ }
645
+ return new Promise((resolve, reject) => {
646
+ monitorPending = {
647
+ resolve,
648
+ reject
649
+ };
650
+ pump();
651
+ });
652
+ },
653
+ return: async (value) => {
654
+ monitorActive = false;
655
+ monitorPending = void 0;
656
+ pump();
657
+ return {
658
+ done: true,
659
+ value
660
+ };
661
+ }
662
+ };
663
+ } };
664
+ const cancelSource = (value) => {
665
+ if (cancellationPromise) return cancellationPromise;
666
+ removeAbortListener();
667
+ if (!controller.signal.aborted) controller.abort();
668
+ cancellationPromise = (async () => {
669
+ try {
670
+ const defaultResult = {
671
+ done: true,
672
+ value
673
+ };
674
+ const result = sourceIterator.return ? await sourceIterator.return(value) : defaultResult;
675
+ if (result.done) {
676
+ terminalResult = result;
677
+ removeAbortListener();
678
+ settleMonitorTerminal();
679
+ settleCallersTerminal();
680
+ } else if (monitorPending) {
681
+ monitorPending.resolve(result);
682
+ monitorPending = void 0;
683
+ cancellationPromise = void 0;
684
+ } else {
685
+ bufferedMonitorResult = result;
686
+ cancellationPromise = void 0;
687
+ }
688
+ return result;
689
+ } catch (error) {
690
+ terminalError = error;
691
+ hasTerminalError = true;
692
+ removeAbortListener();
693
+ settleMonitorTerminal();
694
+ settleCallersTerminal();
695
+ throw error;
696
+ }
697
+ })();
698
+ cancellationPromise.catch(() => void 0);
699
+ return cancellationPromise;
700
+ };
701
+ abortListener = () => {
702
+ cancelSource();
703
+ };
704
+ if (controller.signal.aborted) abortListener();
705
+ else controller.signal.addEventListener("abort", abortListener, { once: true });
706
+ return [monitoringStream, createStream(() => ({
707
+ next: () => {
708
+ if (hasTerminalError) return Promise.reject(terminalError);
709
+ if (terminalResult) return Promise.resolve(terminalResult);
710
+ return new Promise((resolve, reject) => {
711
+ callerQueue.push({
712
+ resolve,
713
+ reject
714
+ });
715
+ pump();
716
+ });
717
+ },
718
+ return: (value) => cancelSource(value),
719
+ throw: async (error) => {
720
+ if (!sourceIterator.throw) {
721
+ await cancelSource();
722
+ throw error;
723
+ }
724
+ try {
725
+ const result = await sourceIterator.throw(error);
726
+ if (result.done) {
727
+ terminalResult = result;
728
+ removeAbortListener();
729
+ settleCallersTerminal();
730
+ }
731
+ if (monitorPending) {
732
+ monitorPending.resolve(result);
733
+ monitorPending = void 0;
734
+ } else bufferedMonitorResult = result;
735
+ return result;
736
+ } catch (sourceError) {
737
+ terminalError = sourceError;
738
+ hasTerminalError = true;
739
+ removeAbortListener();
740
+ settleMonitorTerminal();
741
+ settleCallersTerminal();
742
+ throw sourceError;
743
+ }
744
+ }
745
+ }), controller)];
1165
746
  }
747
+ //#endregion
748
+ //#region src/anthropic/index.ts
749
+ var PostHogAnthropic = class extends AnthropicOriginal {
750
+ constructor(config) {
751
+ const { posthog, ...anthropicConfig } = config;
752
+ super(anthropicConfig);
753
+ this.phClient = posthog;
754
+ this.messages = new WrappedMessages(this, this.phClient);
755
+ }
756
+ };
757
+ var WrappedMessages = class extends AnthropicOriginal.Messages {
758
+ constructor(parentClient, phClient) {
759
+ super(parentClient);
760
+ this.phClient = phClient;
761
+ this.baseURL = parentClient.baseURL;
762
+ }
763
+ create(body, options) {
764
+ const { providerParams: anthropicParams, posthogParams } = extractPosthogParams(body);
765
+ const startTime = Date.now();
766
+ if (anthropicParams.stream) {
767
+ const parentPromise = super.create(anthropicParams, options);
768
+ return preserveProviderPromise(parentPromise, parentPromise.then((value) => {
769
+ let accumulatedContent = "";
770
+ const contentBlocks = [];
771
+ const toolsInProgress = /* @__PURE__ */ new Map();
772
+ let currentTextBlock = null;
773
+ let firstTokenTime;
774
+ let stopReason;
775
+ const usage = { webSearchCount: 0 };
776
+ let rawUsage = {};
777
+ if (Symbol.asyncIterator in value) {
778
+ const [stream1, stream2] = monitoredStreamTee(value, (iterator, controller) => new Stream(iterator, controller));
779
+ (async () => {
780
+ try {
781
+ for await (const chunk of stream1) {
782
+ if (chunk.type === "content_block_start") {
783
+ if (chunk.content_block?.type === "text") {
784
+ currentTextBlock = {
785
+ type: "text",
786
+ text: ""
787
+ };
788
+ contentBlocks.push(currentTextBlock);
789
+ } else if (chunk.content_block?.type === "tool_use") {
790
+ if (firstTokenTime === void 0) firstTokenTime = Date.now();
791
+ const toolBlock = {
792
+ type: "function",
793
+ id: chunk.content_block.id,
794
+ function: {
795
+ name: chunk.content_block.name,
796
+ arguments: {}
797
+ }
798
+ };
799
+ contentBlocks.push(toolBlock);
800
+ toolsInProgress.set(chunk.content_block.id, {
801
+ block: toolBlock,
802
+ inputString: ""
803
+ });
804
+ currentTextBlock = null;
805
+ }
806
+ }
807
+ if ("delta" in chunk) {
808
+ if ("text" in chunk.delta) {
809
+ const delta = chunk.delta.text;
810
+ if (firstTokenTime === void 0) firstTokenTime = Date.now();
811
+ accumulatedContent += delta;
812
+ if (currentTextBlock) currentTextBlock.text += delta;
813
+ }
814
+ }
815
+ if (chunk.type === "content_block_delta" && chunk.delta?.type === "input_json_delta") {
816
+ const block = chunk.index !== void 0 ? contentBlocks[chunk.index] : void 0;
817
+ const toolId = block?.type === "function" ? block.id : void 0;
818
+ if (toolId && toolsInProgress.has(toolId)) {
819
+ const tool = toolsInProgress.get(toolId);
820
+ if (tool) tool.inputString += chunk.delta.partial_json || "";
821
+ }
822
+ }
823
+ if (chunk.type === "content_block_stop") {
824
+ currentTextBlock = null;
825
+ if (chunk.index !== void 0) {
826
+ const block = contentBlocks[chunk.index];
827
+ if (block?.type === "function" && block.id && toolsInProgress.has(block.id)) {
828
+ const tool = toolsInProgress.get(block.id);
829
+ if (tool) try {
830
+ block.function.arguments = JSON.parse(tool.inputString);
831
+ } catch (e) {
832
+ console.error("Error parsing tool input:", e);
833
+ }
834
+ toolsInProgress.delete(block.id);
835
+ }
836
+ }
837
+ }
838
+ if (chunk.type == "message_start") {
839
+ rawUsage = { ...chunk.message.usage };
840
+ usage.inputTokens = chunk.message.usage.input_tokens ?? 0;
841
+ usage.cacheCreationInputTokens = chunk.message.usage.cache_creation_input_tokens ?? 0;
842
+ usage.cacheReadInputTokens = chunk.message.usage.cache_read_input_tokens ?? 0;
843
+ usage.webSearchCount = chunk.message.usage.server_tool_use?.web_search_requests ?? 0;
844
+ }
845
+ if ("usage" in chunk) {
846
+ rawUsage = {
847
+ ...rawUsage,
848
+ ...Object.fromEntries(Object.entries(chunk.usage).filter(([, value]) => value != null))
849
+ };
850
+ usage.outputTokens = chunk.usage.output_tokens ?? 0;
851
+ if (chunk.usage.server_tool_use?.web_search_requests !== void 0) usage.webSearchCount = chunk.usage.server_tool_use.web_search_requests;
852
+ }
853
+ if (chunk.type === "message_delta" && "delta" in chunk) {
854
+ const delta = chunk.delta;
855
+ if ("stop_reason" in delta && typeof delta.stop_reason === "string" && delta.stop_reason) stopReason = delta.stop_reason;
856
+ }
857
+ }
858
+ usage.rawUsage = rawUsage;
859
+ const latency = (Date.now() - startTime) / 1e3;
860
+ const timeToFirstToken = firstTokenTime !== void 0 ? (firstTokenTime - startTime) / 1e3 : void 0;
861
+ const availableTools = extractAvailableToolCalls("anthropic", anthropicParams);
862
+ const formattedOutput = contentBlocks.length > 0 ? [{
863
+ role: "assistant",
864
+ content: contentBlocks
865
+ }] : [{
866
+ role: "assistant",
867
+ content: [{
868
+ type: "text",
869
+ text: accumulatedContent
870
+ }]
871
+ }];
872
+ await captureAiGeneration(this.phClient, {
873
+ ...posthogParams,
874
+ model: anthropicParams.model,
875
+ provider: "anthropic",
876
+ input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, "anthropic"), this.phClient),
877
+ output: formattedOutput,
878
+ latency,
879
+ timeToFirstToken,
880
+ baseURL: this.baseURL,
881
+ modelParameters: getModelParams(body),
882
+ httpStatus: 200,
883
+ usage,
884
+ stopReason,
885
+ tools: availableTools
886
+ });
887
+ } catch (error) {
888
+ if (Object.keys(rawUsage).length > 0) usage.rawUsage = rawUsage;
889
+ await captureAiGeneration(this.phClient, {
890
+ ...posthogParams,
891
+ model: anthropicParams.model,
892
+ provider: "anthropic",
893
+ input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, "anthropic"), this.phClient),
894
+ output: [],
895
+ latency: (Date.now() - startTime) / 1e3,
896
+ baseURL: this.baseURL,
897
+ modelParameters: getModelParams(body),
898
+ usage,
899
+ error
900
+ });
901
+ throw error;
902
+ }
903
+ })().catch(() => {});
904
+ return stream2;
905
+ }
906
+ return value;
907
+ }), {
908
+ requestIdHeader: "request-id",
909
+ workspaceIdHeader: "anthropic-workspace-id"
910
+ });
911
+ } else {
912
+ const parentPromise = super.create(anthropicParams, options);
913
+ return preserveProviderPromise(parentPromise, parentPromise.then(async (result) => {
914
+ if ("content" in result) {
915
+ const latency = (Date.now() - startTime) / 1e3;
916
+ const availableTools = extractAvailableToolCalls("anthropic", anthropicParams);
917
+ await captureAiGeneration(this.phClient, {
918
+ ...posthogParams,
919
+ model: anthropicParams.model,
920
+ provider: "anthropic",
921
+ input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, "anthropic"), this.phClient),
922
+ output: formatResponseAnthropic(result),
923
+ latency,
924
+ baseURL: this.baseURL,
925
+ modelParameters: getModelParams(body),
926
+ httpStatus: 200,
927
+ usage: {
928
+ inputTokens: result.usage.input_tokens ?? 0,
929
+ outputTokens: result.usage.output_tokens ?? 0,
930
+ cacheCreationInputTokens: result.usage.cache_creation_input_tokens ?? 0,
931
+ cacheReadInputTokens: result.usage.cache_read_input_tokens ?? 0,
932
+ webSearchCount: result.usage.server_tool_use?.web_search_requests ?? 0,
933
+ rawUsage: result.usage
934
+ },
935
+ stopReason: result.stop_reason ?? void 0,
936
+ tools: availableTools
937
+ });
938
+ }
939
+ return result;
940
+ }, async (error) => {
941
+ await captureAiGeneration(this.phClient, {
942
+ ...posthogParams,
943
+ model: anthropicParams.model,
944
+ provider: "anthropic",
945
+ input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, "anthropic"), this.phClient),
946
+ output: [],
947
+ latency: (Date.now() - startTime) / 1e3,
948
+ baseURL: this.baseURL,
949
+ modelParameters: getModelParams(body),
950
+ httpStatus: error?.status ? error.status : 500,
951
+ usage: {},
952
+ error
953
+ });
954
+ throw error;
955
+ }), {
956
+ requestIdHeader: "request-id",
957
+ workspaceIdHeader: "anthropic-workspace-id"
958
+ });
959
+ }
960
+ }
961
+ };
962
+ //#endregion
963
+ export { PostHogAnthropic as Anthropic, PostHogAnthropic, PostHogAnthropic as default, WrappedMessages };
1166
964
 
1167
- export { PostHogAnthropic as Anthropic, PostHogAnthropic, WrappedMessages, PostHogAnthropic as default };
1168
- //# sourceMappingURL=index.mjs.map
965
+ //# sourceMappingURL=index.mjs.map