@posthog/ai 5.2.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +245 -0
  2. package/{lib → dist}/anthropic/index.cjs +44 -17
  3. package/dist/anthropic/index.cjs.map +1 -0
  4. package/{lib → dist}/anthropic/index.mjs +41 -10
  5. package/dist/anthropic/index.mjs.map +1 -0
  6. package/{lib → dist}/gemini/index.cjs +68 -26
  7. package/dist/gemini/index.cjs.map +1 -0
  8. package/{lib → dist}/gemini/index.d.ts +0 -1
  9. package/{lib → dist}/gemini/index.mjs +67 -25
  10. package/dist/gemini/index.mjs.map +1 -0
  11. package/{lib → dist}/index.cjs +875 -601
  12. package/dist/index.cjs.map +1 -0
  13. package/{lib → dist}/index.d.ts +3 -3
  14. package/{lib → dist}/index.mjs +859 -579
  15. package/dist/index.mjs.map +1 -0
  16. package/{lib → dist}/langchain/index.cjs +178 -118
  17. package/dist/langchain/index.cjs.map +1 -0
  18. package/{lib → dist}/langchain/index.d.ts +1 -0
  19. package/{lib → dist}/langchain/index.mjs +175 -112
  20. package/dist/langchain/index.mjs.map +1 -0
  21. package/{lib → dist}/openai/index.cjs +113 -6
  22. package/dist/openai/index.cjs.map +1 -0
  23. package/{lib → dist}/openai/index.mjs +112 -5
  24. package/dist/openai/index.mjs.map +1 -0
  25. package/{lib → dist}/vercel/index.cjs +117 -82
  26. package/dist/vercel/index.cjs.map +1 -0
  27. package/{lib → dist}/vercel/index.d.ts +2 -2
  28. package/{lib → dist}/vercel/index.mjs +118 -81
  29. package/dist/vercel/index.mjs.map +1 -0
  30. package/package.json +45 -35
  31. package/CHANGELOG.md +0 -89
  32. package/index.ts +0 -1
  33. package/lib/anthropic/index.cjs.map +0 -1
  34. package/lib/anthropic/index.mjs.map +0 -1
  35. package/lib/gemini/index.cjs.map +0 -1
  36. package/lib/gemini/index.mjs.map +0 -1
  37. package/lib/index.cjs.map +0 -1
  38. package/lib/index.mjs.map +0 -1
  39. package/lib/langchain/index.cjs.map +0 -1
  40. package/lib/langchain/index.mjs.map +0 -1
  41. package/lib/openai/index.cjs.map +0 -1
  42. package/lib/openai/index.mjs.map +0 -1
  43. package/lib/vercel/index.cjs.map +0 -1
  44. package/lib/vercel/index.mjs.map +0 -1
  45. package/src/anthropic/index.ts +0 -211
  46. package/src/gemini/index.ts +0 -254
  47. package/src/index.ts +0 -13
  48. package/src/langchain/callbacks.ts +0 -640
  49. package/src/langchain/index.ts +0 -1
  50. package/src/openai/azure.ts +0 -481
  51. package/src/openai/index.ts +0 -498
  52. package/src/utils.ts +0 -287
  53. package/src/vercel/index.ts +0 -1
  54. package/src/vercel/middleware.ts +0 -393
  55. package/tests/callbacks.test.ts +0 -48
  56. package/tests/gemini.test.ts +0 -344
  57. package/tests/openai.test.ts +0 -403
  58. package/tsconfig.json +0 -10
  59. /package/{lib → dist}/anthropic/index.d.ts +0 -0
  60. /package/{lib → dist}/openai/index.d.ts +0 -0
@@ -1,403 +0,0 @@
1
- import { PostHog } from 'posthog-node'
2
- import PostHogOpenAI from '../src/openai'
3
- import openaiModule from 'openai'
4
-
5
- let mockOpenAiChatResponse: any = {}
6
- let mockOpenAiParsedResponse: any = {}
7
-
8
- jest.mock('posthog-node', () => {
9
- return {
10
- PostHog: jest.fn().mockImplementation(() => {
11
- return {
12
- capture: jest.fn(),
13
- captureImmediate: jest.fn(),
14
- privacy_mode: false,
15
- }
16
- }),
17
- }
18
- })
19
-
20
- jest.mock('openai', () => {
21
- // Mock Completions class – `create` is declared on the prototype so that
22
- // subclasses can safely `super.create(...)` without it being shadowed by an
23
- // instance field (which would overwrite the subclass implementation).
24
- class MockCompletions {
25
- constructor() {}
26
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
- create(..._args: any[]): any {
28
- /* will be stubbed in beforeEach */
29
- return undefined
30
- }
31
- }
32
-
33
- // Mock Chat class
34
- class MockChat {
35
- constructor() {}
36
- static Completions = MockCompletions
37
- }
38
-
39
- // Mock Responses class
40
- class MockResponses {
41
- constructor() {}
42
- create = jest.fn()
43
- }
44
-
45
- // Add parse to prototype instead of instance
46
- ;(MockResponses.prototype as any).parse = jest.fn()
47
-
48
- // Mock OpenAI class
49
- class MockOpenAI {
50
- chat: any
51
- embeddings: any
52
- responses: any
53
- constructor() {
54
- this.chat = {
55
- completions: {
56
- create: jest.fn(),
57
- },
58
- }
59
- this.embeddings = {
60
- create: jest.fn(),
61
- }
62
- this.responses = {
63
- create: jest.fn(),
64
- }
65
- }
66
- static Chat = MockChat
67
- static Responses = MockResponses
68
- }
69
-
70
- return {
71
- __esModule: true,
72
- default: MockOpenAI,
73
- OpenAI: MockOpenAI,
74
- Chat: MockChat,
75
- Responses: MockResponses,
76
- }
77
- })
78
-
79
- describe('PostHogOpenAI - Jest test suite', () => {
80
- let mockPostHogClient: PostHog
81
- let client: PostHogOpenAI
82
-
83
- beforeAll(() => {
84
- if (!process.env.OPENAI_API_KEY) {
85
- console.warn('⚠️ Skipping OpenAI tests: No OPENAI_API_KEY environment variable set')
86
- }
87
- })
88
-
89
- beforeEach(() => {
90
- // Skip all tests if no API key is present
91
- if (!process.env.OPENAI_API_KEY) {
92
- return
93
- }
94
-
95
- jest.clearAllMocks()
96
-
97
- // Reset the default mocks
98
- mockPostHogClient = new (PostHog as any)()
99
- client = new PostHogOpenAI({
100
- apiKey: process.env.OPENAI_API_KEY || '',
101
- posthog: mockPostHogClient as any,
102
- })
103
-
104
- // Some default chat completion mock
105
- mockOpenAiChatResponse = {
106
- id: 'test-response-id',
107
- model: 'gpt-4',
108
- object: 'chat.completion',
109
- created: Date.now() / 1000,
110
- choices: [
111
- {
112
- index: 0,
113
- finish_reason: 'stop',
114
- message: {
115
- role: 'assistant',
116
- content: 'Hello from OpenAI!',
117
- },
118
- },
119
- ],
120
- usage: {
121
- prompt_tokens: 20,
122
- completion_tokens: 10,
123
- total_tokens: 30,
124
- },
125
- }
126
-
127
- // Some default parsed response mock
128
- mockOpenAiParsedResponse = {
129
- id: 'test-parsed-response-id',
130
- model: 'gpt-4o-2024-08-06',
131
- object: 'response',
132
- created_at: Date.now(),
133
- status: 'completed',
134
- output: [
135
- {
136
- type: 'output_text',
137
- text: '{"name": "Science Fair", "date": "Friday", "participants": ["Alice", "Bob"]}',
138
- },
139
- ],
140
- output_parsed: {
141
- name: 'Science Fair',
142
- date: 'Friday',
143
- participants: ['Alice', 'Bob'],
144
- },
145
- usage: {
146
- input_tokens: 15,
147
- output_tokens: 20,
148
- input_tokens_details: { cached_tokens: 0 },
149
- output_tokens_details: { reasoning_tokens: 5 },
150
- total_tokens: 35,
151
- },
152
- }
153
-
154
- const ChatMock: any = openaiModule.Chat
155
- ;(ChatMock.Completions as any).prototype.create = jest.fn().mockResolvedValue(mockOpenAiChatResponse)
156
-
157
- // Mock responses.parse using the same pattern as chat completions
158
- const ResponsesMock: any = openaiModule.Responses
159
- ResponsesMock.prototype.parse.mockResolvedValue(mockOpenAiParsedResponse)
160
- })
161
-
162
- // Wrap each test with conditional skip
163
- const conditionalTest = process.env.OPENAI_API_KEY ? test : test.skip
164
-
165
- conditionalTest('basic completion', async () => {
166
- // We ensure calls to create a completion return our mock
167
- // This is handled by the inherited Chat.Completions mock in openai
168
- const response = await client.chat.completions.create({
169
- model: 'gpt-4',
170
- messages: [{ role: 'user', content: 'Hello' }],
171
- posthogDistinctId: 'test-id',
172
- posthogProperties: { foo: 'bar' },
173
- })
174
-
175
- expect(response).toEqual(mockOpenAiChatResponse)
176
- // We expect 1 capture call
177
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
178
- // Check the capture arguments
179
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
180
- const { distinctId, event, properties } = captureArgs[0]
181
-
182
- expect(distinctId).toBe('test-id')
183
- expect(event).toBe('$ai_generation')
184
- expect(properties['$ai_provider']).toBe('openai')
185
- expect(properties['$ai_model']).toBe('gpt-4')
186
- expect(properties['$ai_input']).toEqual([{ role: 'user', content: 'Hello' }])
187
- expect(properties['$ai_output_choices']).toEqual([{ role: 'assistant', content: 'Hello from OpenAI!' }])
188
- expect(properties['$ai_input_tokens']).toBe(20)
189
- expect(properties['$ai_output_tokens']).toBe(10)
190
- expect(properties['$ai_http_status']).toBe(200)
191
- expect(properties['foo']).toBe('bar')
192
- expect(typeof properties['$ai_latency']).toBe('number')
193
- })
194
-
195
- conditionalTest('groups', async () => {
196
- await client.chat.completions.create({
197
- model: 'gpt-4',
198
- messages: [{ role: 'user', content: 'Hello' }],
199
- posthogDistinctId: 'test-id',
200
- posthogGroups: { company: 'test_company' },
201
- })
202
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
203
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
204
- const { groups } = captureArgs[0]
205
- expect(groups).toEqual({ company: 'test_company' })
206
- })
207
-
208
- conditionalTest('privacy mode local', async () => {
209
- await client.chat.completions.create({
210
- model: 'gpt-4',
211
- messages: [{ role: 'user', content: 'Hello' }],
212
- posthogDistinctId: 'test-id',
213
- posthogPrivacyMode: true,
214
- })
215
-
216
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
217
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
218
- const { properties } = captureArgs[0]
219
- expect(properties['$ai_input']).toBeNull()
220
- expect(properties['$ai_output_choices']).toBeNull()
221
- })
222
-
223
- conditionalTest('privacy mode global', async () => {
224
- // override mock to appear globally in privacy mode
225
- ;(mockPostHogClient as any).privacy_mode = true
226
-
227
- await client.chat.completions.create({
228
- model: 'gpt-4',
229
- messages: [{ role: 'user', content: 'Hello' }],
230
- posthogDistinctId: 'test-id',
231
- // we attempt to override locally, but it should still be null if global is true
232
- posthogPrivacyMode: false,
233
- })
234
-
235
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
236
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
237
- const { properties } = captureArgs[0]
238
- expect(properties['$ai_input']).toBeNull()
239
- expect(properties['$ai_output_choices']).toBeNull()
240
- })
241
-
242
- conditionalTest('core model params', async () => {
243
- mockOpenAiChatResponse.usage = {
244
- prompt_tokens: 20,
245
- completion_tokens: 10,
246
- }
247
-
248
- await client.chat.completions.create({
249
- model: 'gpt-4',
250
- // using openai-like params
251
- temperature: 0.5,
252
- max_completion_tokens: 100,
253
- stream: false,
254
- messages: [{ role: 'user', content: 'Hello' }],
255
- posthogDistinctId: 'test-id',
256
- posthogProperties: { foo: 'bar' },
257
- })
258
-
259
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
260
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
261
- const { properties } = captureArgs[0]
262
-
263
- expect(properties['$ai_model_parameters']).toEqual({
264
- temperature: 0.5,
265
- max_completion_tokens: 100,
266
- stream: false,
267
- })
268
- expect(properties['foo']).toBe('bar')
269
- })
270
-
271
- conditionalTest('reasoning and cache tokens', async () => {
272
- // Set up mock response with standard token usage
273
- mockOpenAiChatResponse.usage = {
274
- prompt_tokens: 20,
275
- completion_tokens: 10,
276
- total_tokens: 30,
277
- // Add the detailed token usage that OpenAI would return
278
- completion_tokens_details: {
279
- reasoning_tokens: 15,
280
- },
281
- prompt_tokens_details: {
282
- cached_tokens: 5,
283
- },
284
- }
285
-
286
- // Create a completion with additional token tracking
287
- await client.chat.completions.create({
288
- model: 'gpt-4',
289
- messages: [{ role: 'user', content: 'Hello' }],
290
- posthogDistinctId: 'test-id',
291
- posthogProperties: { foo: 'bar' },
292
- })
293
-
294
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
295
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
296
- const { properties } = captureArgs[0]
297
-
298
- // Check standard token properties
299
- expect(properties['$ai_input_tokens']).toBe(20)
300
- expect(properties['$ai_output_tokens']).toBe(10)
301
-
302
- // Check the new token properties
303
- expect(properties['$ai_reasoning_tokens']).toBe(15)
304
- expect(properties['$ai_cache_read_input_tokens']).toBe(5)
305
- })
306
-
307
- // New test: ensure captureImmediate is used when flag is set
308
- conditionalTest('captureImmediate flag', async () => {
309
- await client.chat.completions.create({
310
- model: 'gpt-4',
311
- messages: [{ role: 'user', content: 'Hello' }],
312
- posthogDistinctId: 'test-id',
313
- posthogCaptureImmediate: true,
314
- })
315
-
316
- // captureImmediate should be called once, and capture should not be called
317
- expect(mockPostHogClient.captureImmediate).toHaveBeenCalledTimes(1)
318
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(0)
319
- })
320
-
321
- conditionalTest('responses parse', async () => {
322
- const response = await client.responses.parse({
323
- model: 'gpt-4o-2024-08-06',
324
- input: [
325
- { role: 'system', content: 'Extract the event information.' },
326
- { role: 'user', content: 'Alice and Bob are going to a science fair on Friday.' },
327
- ],
328
- text: {
329
- format: {
330
- type: 'json_object',
331
- json_schema: {
332
- name: 'event',
333
- schema: {
334
- type: 'object',
335
- properties: {
336
- name: { type: 'string' },
337
- date: { type: 'string' },
338
- participants: { type: 'array', items: { type: 'string' } },
339
- },
340
- required: ['name', 'date', 'participants'],
341
- },
342
- },
343
- },
344
- },
345
- posthogDistinctId: 'test-id',
346
- posthogProperties: { foo: 'bar' },
347
- })
348
-
349
- expect(response).toEqual(mockOpenAiParsedResponse)
350
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
351
-
352
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
353
- const { distinctId, event, properties } = captureArgs[0]
354
-
355
- expect(distinctId).toBe('test-id')
356
- expect(event).toBe('$ai_generation')
357
- expect(properties['$ai_provider']).toBe('openai')
358
- expect(properties['$ai_model']).toBe('gpt-4o-2024-08-06')
359
- expect(properties['$ai_input']).toEqual([
360
- { role: 'system', content: 'Extract the event information.' },
361
- { role: 'user', content: 'Alice and Bob are going to a science fair on Friday.' },
362
- ])
363
- expect(properties['$ai_output_choices']).toEqual(mockOpenAiParsedResponse.output)
364
- expect(properties['$ai_input_tokens']).toBe(15)
365
- expect(properties['$ai_output_tokens']).toBe(20)
366
- expect(properties['$ai_reasoning_tokens']).toBe(5)
367
- expect(properties['$ai_cache_read_input_tokens']).toBeUndefined()
368
- expect(properties['$ai_http_status']).toBe(200)
369
- expect(properties['foo']).toBe('bar')
370
- expect(typeof properties['$ai_latency']).toBe('number')
371
- })
372
-
373
- conditionalTest('anonymous user - $process_person_profile set to false', async () => {
374
- await client.chat.completions.create({
375
- model: 'gpt-4',
376
- messages: [{ role: 'user', content: 'Hello' }],
377
- posthogTraceId: 'trace-123',
378
- })
379
-
380
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
381
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
382
- const { distinctId, properties } = captureArgs[0]
383
-
384
- expect(distinctId).toBe('trace-123')
385
- expect(properties['$process_person_profile']).toBe(false)
386
- })
387
-
388
- conditionalTest('identified user - $process_person_profile not set', async () => {
389
- await client.chat.completions.create({
390
- model: 'gpt-4',
391
- messages: [{ role: 'user', content: 'Hello' }],
392
- posthogDistinctId: 'user-456',
393
- posthogTraceId: 'trace-123',
394
- })
395
-
396
- expect(mockPostHogClient.capture).toHaveBeenCalledTimes(1)
397
- const [captureArgs] = (mockPostHogClient.capture as jest.Mock).mock.calls
398
- const { distinctId, properties } = captureArgs[0]
399
-
400
- expect(distinctId).toBe('user-456')
401
- expect(properties['$process_person_profile']).toBeUndefined()
402
- })
403
- })
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../tsconfig.json",
3
- "compilerOptions": {
4
- "incremental": false,
5
- "types": ["node", "jest"],
6
- "typeRoots": ["./node_modules/@types", "../node_modules/@types"],
7
- "skipLibCheck": true,
8
- "allowSyntheticDefaultImports": true
9
- }
10
- }
File without changes
File without changes