@posthog/ai 5.2.2 → 5.2.3

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 (54) hide show
  1. package/LICENSE +245 -0
  2. package/{lib → dist}/anthropic/index.cjs +7 -12
  3. package/{lib → dist}/anthropic/index.cjs.map +1 -1
  4. package/{lib → dist}/anthropic/index.mjs +4 -5
  5. package/{lib → dist}/anthropic/index.mjs.map +1 -1
  6. package/{lib → dist}/gemini/index.cjs +1 -1
  7. package/{lib → dist}/gemini/index.cjs.map +1 -1
  8. package/{lib → dist}/gemini/index.mjs.map +1 -1
  9. package/{lib → dist}/index.cjs +547 -479
  10. package/dist/index.cjs.map +1 -0
  11. package/{lib → dist}/index.mjs +530 -456
  12. package/dist/index.mjs.map +1 -0
  13. package/{lib → dist}/langchain/index.cjs +150 -110
  14. package/dist/langchain/index.cjs.map +1 -0
  15. package/{lib → dist}/langchain/index.mjs +147 -104
  16. package/dist/langchain/index.mjs.map +1 -0
  17. package/{lib → dist}/openai/index.cjs +7 -1
  18. package/dist/openai/index.cjs.map +1 -0
  19. package/{lib → dist}/openai/index.mjs +6 -0
  20. package/dist/openai/index.mjs.map +1 -0
  21. package/{lib → dist}/vercel/index.cjs +0 -2
  22. package/{lib → dist}/vercel/index.cjs.map +1 -1
  23. package/{lib → dist}/vercel/index.mjs.map +1 -1
  24. package/package.json +42 -33
  25. package/CHANGELOG.md +0 -89
  26. package/index.ts +0 -1
  27. package/lib/index.cjs.map +0 -1
  28. package/lib/index.mjs.map +0 -1
  29. package/lib/langchain/index.cjs.map +0 -1
  30. package/lib/langchain/index.mjs.map +0 -1
  31. package/lib/openai/index.cjs.map +0 -1
  32. package/lib/openai/index.mjs.map +0 -1
  33. package/src/anthropic/index.ts +0 -211
  34. package/src/gemini/index.ts +0 -254
  35. package/src/index.ts +0 -13
  36. package/src/langchain/callbacks.ts +0 -640
  37. package/src/langchain/index.ts +0 -1
  38. package/src/openai/azure.ts +0 -481
  39. package/src/openai/index.ts +0 -498
  40. package/src/utils.ts +0 -287
  41. package/src/vercel/index.ts +0 -1
  42. package/src/vercel/middleware.ts +0 -393
  43. package/tests/callbacks.test.ts +0 -48
  44. package/tests/gemini.test.ts +0 -344
  45. package/tests/openai.test.ts +0 -403
  46. package/tsconfig.json +0 -10
  47. /package/{lib → dist}/anthropic/index.d.ts +0 -0
  48. /package/{lib → dist}/gemini/index.d.ts +0 -0
  49. /package/{lib → dist}/gemini/index.mjs +0 -0
  50. /package/{lib → dist}/index.d.ts +0 -0
  51. /package/{lib → dist}/langchain/index.d.ts +0 -0
  52. /package/{lib → dist}/openai/index.d.ts +0 -0
  53. /package/{lib → dist}/vercel/index.d.ts +0 -0
  54. /package/{lib → dist}/vercel/index.mjs +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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes