@posthog/ai 7.20.14 → 8.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.
package/dist/index.mjs CHANGED
@@ -1,9 +1,5 @@
1
- import { OpenAI, AzureOpenAI } from 'openai';
2
- import * as uuid from 'uuid';
3
1
  import { v4 } from 'uuid';
4
2
  import { uuidv7 } from '@posthog/core';
5
- import AnthropicOriginal from '@anthropic-ai/sdk';
6
- import { GoogleGenAI } from '@google/genai';
7
3
 
8
4
  // Type guards for safer type checking
9
5
  const isString = value => {
@@ -158,11 +154,6 @@ const redactor = new BinaryContentRedactor();
158
154
  function redactBase64DataUrl(str) {
159
155
  return redactor.redact(str);
160
156
  }
161
- const sanitizeOpenAI = data => redactor.redact(data);
162
- const sanitizeOpenAIResponse = data => redactor.redact(data);
163
- const sanitizeAnthropic = data => redactor.redact(data);
164
- const sanitizeGemini = data => redactor.redact(data);
165
- const sanitizeLangChain = data => redactor.redact(data);
166
157
 
167
158
  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']);
168
159
  function getTokensSource(posthogProperties) {
@@ -219,236 +210,6 @@ const getModelParams = params => {
219
210
  }
220
211
  return modelParams;
221
212
  };
222
- const formatResponseAnthropic = response => {
223
- const output = [];
224
- const content = [];
225
- for (const choice of response.content ?? []) {
226
- if (choice?.type === 'text' && choice?.text) {
227
- content.push({
228
- type: 'text',
229
- text: choice.text
230
- });
231
- } else if (choice?.type === 'tool_use' && choice?.name && choice?.id) {
232
- content.push({
233
- type: 'function',
234
- id: choice.id,
235
- function: {
236
- name: choice.name,
237
- arguments: choice.input || {}
238
- }
239
- });
240
- }
241
- }
242
- if (content.length > 0) {
243
- output.push({
244
- role: 'assistant',
245
- content
246
- });
247
- }
248
- return output;
249
- };
250
- const formatResponseOpenAI = response => {
251
- const output = [];
252
- if (response.choices) {
253
- for (const choice of response.choices) {
254
- const content = [];
255
- let role = 'assistant';
256
- if (choice.message) {
257
- if (choice.message.role) {
258
- role = choice.message.role;
259
- }
260
- if (choice.message.content) {
261
- content.push({
262
- type: 'text',
263
- text: choice.message.content
264
- });
265
- }
266
- if (choice.message.tool_calls) {
267
- for (const toolCall of choice.message.tool_calls) {
268
- content.push({
269
- type: 'function',
270
- id: toolCall.id,
271
- function: {
272
- name: toolCall.function.name,
273
- arguments: toolCall.function.arguments
274
- }
275
- });
276
- }
277
- }
278
- // Handle audio output (gpt-4o-audio-preview)
279
- if (choice.message.audio) {
280
- content.push({
281
- type: 'audio',
282
- ...choice.message.audio
283
- });
284
- }
285
- }
286
- if (content.length > 0) {
287
- output.push({
288
- role,
289
- content
290
- });
291
- }
292
- }
293
- }
294
- // Handle Responses API format
295
- if (response.output) {
296
- const content = [];
297
- let role = 'assistant';
298
- for (const item of response.output) {
299
- if (item.type === 'message') {
300
- role = item.role;
301
- if (item.content && Array.isArray(item.content)) {
302
- for (const contentItem of item.content) {
303
- if (contentItem.type === 'output_text' && contentItem.text) {
304
- content.push({
305
- type: 'text',
306
- text: contentItem.text
307
- });
308
- } else if (contentItem.text) {
309
- content.push({
310
- type: 'text',
311
- text: contentItem.text
312
- });
313
- } else if (contentItem.type === 'input_image' && contentItem.image_url) {
314
- content.push({
315
- type: 'image',
316
- image: contentItem.image_url
317
- });
318
- }
319
- }
320
- } else if (item.content) {
321
- content.push({
322
- type: 'text',
323
- text: String(item.content)
324
- });
325
- }
326
- } else if (item.type === 'function_call') {
327
- content.push({
328
- type: 'function',
329
- id: item.call_id || item.id || '',
330
- function: {
331
- name: item.name,
332
- arguments: item.arguments || {}
333
- }
334
- });
335
- }
336
- }
337
- if (content.length > 0) {
338
- output.push({
339
- role,
340
- content
341
- });
342
- }
343
- }
344
- return output;
345
- };
346
- const buildInlineDataBlock = (mimeType, data) => {
347
- if (mimeType.startsWith('audio/')) {
348
- return {
349
- type: 'audio',
350
- mime_type: mimeType,
351
- data
352
- };
353
- }
354
- if (mimeType.startsWith('image/')) {
355
- return {
356
- type: 'image',
357
- inline_data: {
358
- mime_type: mimeType,
359
- data
360
- }
361
- };
362
- }
363
- return {
364
- type: 'document',
365
- inline_data: {
366
- mime_type: mimeType,
367
- data
368
- }
369
- };
370
- };
371
- const formatResponseGemini = response => {
372
- const output = [];
373
- if (response.candidates && Array.isArray(response.candidates)) {
374
- for (const candidate of response.candidates) {
375
- if (candidate.content && candidate.content.parts) {
376
- const content = [];
377
- for (const part of candidate.content.parts) {
378
- if (part.text) {
379
- content.push({
380
- type: 'text',
381
- text: part.text
382
- });
383
- } else if (part.functionCall) {
384
- content.push({
385
- type: 'function',
386
- function: {
387
- name: part.functionCall.name,
388
- arguments: part.functionCall.args
389
- }
390
- });
391
- } else if (part.inlineData) {
392
- // Handle inline data (images, audio, documents)
393
- const mimeType = part.inlineData.mimeType || part.inlineData.mime_type || 'application/octet-stream';
394
- let data = part.inlineData.data;
395
- // Handle binary data (Uint8Array/Buffer -> base64)
396
- if (data instanceof Uint8Array) {
397
- if (typeof Buffer !== 'undefined') {
398
- data = Buffer.from(data).toString('base64');
399
- } else {
400
- let binary = '';
401
- for (let i = 0; i < data.length; i++) {
402
- binary += String.fromCharCode(data[i]);
403
- }
404
- data = btoa(binary);
405
- }
406
- }
407
- // Sanitize base64 data for images and other large inline data
408
- data = redactBase64DataUrl(data);
409
- content.push(buildInlineDataBlock(mimeType, data));
410
- }
411
- }
412
- if (content.length > 0) {
413
- output.push({
414
- role: 'assistant',
415
- content
416
- });
417
- }
418
- } else if (candidate.text) {
419
- output.push({
420
- role: 'assistant',
421
- content: [{
422
- type: 'text',
423
- text: candidate.text
424
- }]
425
- });
426
- }
427
- }
428
- } else if (response.text) {
429
- output.push({
430
- role: 'assistant',
431
- content: [{
432
- type: 'text',
433
- text: response.text
434
- }]
435
- });
436
- }
437
- return output;
438
- };
439
- const mergeSystemPrompt = (params, provider) => {
440
- {
441
- const messages = params.messages || [];
442
- if (!params.system) {
443
- return messages;
444
- }
445
- const systemMessage = params.system;
446
- return [{
447
- role: 'system',
448
- content: systemMessage
449
- }, ...messages];
450
- }
451
- };
452
213
  const withPrivacyMode = (client, privacyMode, input) => {
453
214
  return client.privacy_mode || privacyMode ? null : input;
454
215
  };
@@ -586,28 +347,12 @@ function calculateWebSearchCount(result) {
586
347
  * These are the tools provided to the LLM, not the tool calls in the response.
587
348
  */
588
349
  const extractAvailableToolCalls = (provider, params) => {
589
- if (provider === 'anthropic') {
590
- if (params.tools) {
591
- return params.tools;
592
- }
593
- return null;
594
- } else if (provider === 'gemini') {
595
- if (params.config && params.config.tools) {
596
- return params.config.tools;
597
- }
598
- return null;
599
- } else if (provider === 'openai') {
600
- if (params.tools) {
601
- return params.tools;
602
- }
603
- return null;
604
- } else if (provider === 'vercel') {
350
+ {
605
351
  if (params.tools) {
606
352
  return params.tools;
607
353
  }
608
354
  return null;
609
355
  }
610
- return null;
611
356
  };
612
357
  var AIEvent;
613
358
  (function (AIEvent) {
@@ -629,87 +374,8 @@ function sanitizeValues(obj) {
629
374
  }
630
375
  return jsonSafe;
631
376
  }
632
- const POSTHOG_PARAMS_MAP = {
633
- posthogDistinctId: 'distinctId',
634
- posthogTraceId: 'traceId',
635
- posthogProperties: 'properties',
636
- posthogPrivacyMode: 'privacyMode',
637
- posthogGroups: 'groups',
638
- posthogModelOverride: 'modelOverride',
639
- posthogProviderOverride: 'providerOverride',
640
- posthogCostOverride: 'costOverride',
641
- posthogCaptureImmediate: 'captureImmediate'
642
- };
643
- function extractPosthogParams(body) {
644
- const providerParams = {};
645
- const posthogParams = {};
646
- for (const [key, value] of Object.entries(body)) {
647
- if (POSTHOG_PARAMS_MAP[key]) {
648
- posthogParams[POSTHOG_PARAMS_MAP[key]] = value;
649
- } else if (key.startsWith('posthog')) {
650
- console.warn(`Unknown Posthog parameter ${key}`);
651
- } else {
652
- providerParams[key] = value;
653
- }
654
- }
655
- return {
656
- providerParams: providerParams,
657
- posthogParams: addDefaults(posthogParams)
658
- };
659
- }
660
- function addDefaults(params) {
661
- return {
662
- ...params,
663
- privacyMode: params.privacyMode ?? false,
664
- traceId: params.traceId ?? v4()
665
- };
666
- }
667
- function formatOpenAIResponsesInput(input, instructions) {
668
- const messages = [];
669
- if (instructions) {
670
- messages.push({
671
- role: 'system',
672
- content: instructions
673
- });
674
- }
675
- if (Array.isArray(input)) {
676
- for (const item of input) {
677
- if (typeof item === 'string') {
678
- messages.push({
679
- role: 'user',
680
- content: item
681
- });
682
- } else if (item && typeof item === 'object') {
683
- const obj = item;
684
- const role = isString(obj.role) ? obj.role : 'user';
685
- // Handle content properly - preserve structure for objects/arrays
686
- const content = obj.content ?? obj.text ?? item;
687
- messages.push({
688
- role,
689
- content: toContentString(content)
690
- });
691
- } else {
692
- messages.push({
693
- role: 'user',
694
- content: toContentString(item)
695
- });
696
- }
697
- }
698
- } else if (typeof input === 'string') {
699
- messages.push({
700
- role: 'user',
701
- content: input
702
- });
703
- } else if (input) {
704
- messages.push({
705
- role: 'user',
706
- content: toContentString(input)
707
- });
708
- }
709
- return messages;
710
- }
711
377
 
712
- var version = "7.20.14";
378
+ var version = "8.0.0";
713
379
 
714
380
  const DEFAULT_MAX_DEPTH = 3;
715
381
  const MAX_STACK_LINES = 20;
@@ -892,1336 +558,51 @@ const captureAiGeneration = async (client, options) => {
892
558
  }
893
559
  };
894
560
 
895
- /**
896
- * Checks if a ResponseStreamEvent chunk represents the first token/content from the model.
897
- * This includes various content types like text, reasoning, audio, and refusals.
898
- */
899
- function isResponseTokenChunk(chunk) {
900
- return chunk.type === 'response.output_item.added' || chunk.type === 'response.content_part.added' || chunk.type === 'response.output_text.delta' || chunk.type === 'response.reasoning_text.delta' || chunk.type === 'response.reasoning_summary_text.delta' || chunk.type === 'response.audio.delta' || chunk.type === 'response.audio.transcript.delta' || chunk.type === 'response.refusal.delta';
901
- }
902
- /**
903
- * Reads the OpenAI SDK's `_request_id` field from a response object. The SDK
904
- * attaches the `x-request-id` response header here, but it is not part of the
905
- * public response types, so it has to be read through a cast. Used to populate
906
- * `$ai_provider_metadata.request_id`.
907
- */
908
- function extractRequestId(result) {
909
- return result?._request_id ?? undefined;
910
- }
911
- /**
912
- * Assembles the `$ai_provider_metadata` blob for OpenAI / Azure OpenAI events.
913
- * Provider-specific fields (system fingerprint, request id) live here rather
914
- * than in the shared, provider-agnostic `$ai_*` namespace. Only keys with a
915
- * truthy value are included, and `undefined` is returned when there is nothing
916
- * to report so the property can be omitted from the event entirely.
917
- */
918
- function buildProviderMetadata(fields) {
919
- const metadata = {};
920
- if (fields.systemFingerprint) {
921
- metadata.system_fingerprint = fields.systemFingerprint;
922
- }
923
- if (fields.requestId) {
924
- metadata.request_id = fields.requestId;
925
- }
926
- return Object.keys(metadata).length > 0 ? metadata : undefined;
927
- }
928
-
929
- const Chat = OpenAI.Chat;
930
- const Completions = Chat.Completions;
931
- const Responses = OpenAI.Responses;
932
- const Embeddings = OpenAI.Embeddings;
933
- const Audio = OpenAI.Audio;
934
- const Transcriptions = OpenAI.Audio.Transcriptions;
935
- function captureAiGenerationInBackground(...args) {
936
- void captureAiGeneration(...args).catch(() => undefined);
937
- }
938
- async function captureAiGenerationAfterSuccess(...args) {
939
- const [, options] = args;
940
- if (options.captureImmediate) {
941
- await captureAiGeneration(...args);
942
- } else {
943
- captureAiGenerationInBackground(...args);
944
- }
945
- }
946
- function preserveAPIPromiseHelpers(parentPromise, wrappedPromise) {
947
- const apiPromise = wrappedPromise;
948
- if (typeof parentPromise.asResponse === 'function') {
949
- apiPromise.asResponse = () => parentPromise.asResponse();
950
- }
951
- if (typeof parentPromise.withResponse === 'function') {
952
- apiPromise.withResponse = async () => {
953
- const [response, data] = await Promise.all([parentPromise.withResponse(), wrappedPromise]);
954
- return {
955
- ...response,
956
- data
957
- };
958
- };
959
- }
960
- return apiPromise;
961
- }
962
- class PostHogOpenAI extends OpenAI {
963
- constructor(config) {
964
- const {
965
- posthog,
966
- ...openAIConfig
967
- } = config;
968
- super(openAIConfig);
969
- this.phClient = posthog;
970
- this.chat = new WrappedChat$1(this, this.phClient);
971
- this.responses = new WrappedResponses$1(this, this.phClient);
972
- this.embeddings = new WrappedEmbeddings$1(this, this.phClient);
973
- this.audio = new WrappedAudio(this, this.phClient);
974
- }
561
+ // Type guards
562
+ function isV3Model(model) {
563
+ return model.specificationVersion === 'v3';
975
564
  }
976
- let WrappedChat$1 = class WrappedChat extends Chat {
977
- constructor(parentClient, phClient) {
978
- super(parentClient);
979
- this.completions = new WrappedCompletions$1(parentClient, phClient);
980
- }
565
+ const mapVercelParams = params => {
566
+ return {
567
+ temperature: params.temperature,
568
+ max_output_tokens: params.maxOutputTokens,
569
+ top_p: params.topP,
570
+ frequency_penalty: params.frequencyPenalty,
571
+ presence_penalty: params.presencePenalty,
572
+ stop: params.stopSequences,
573
+ stream: params.stream
574
+ };
981
575
  };
982
- let WrappedCompletions$1 = class WrappedCompletions extends Completions {
983
- constructor(client, phClient) {
984
- super(client);
985
- this.phClient = phClient;
986
- this.baseURL = client.baseURL;
987
- }
988
- // --- Implementation Signature
989
- create(body, options) {
990
- const {
991
- providerParams: openAIParams,
992
- posthogParams
993
- } = extractPosthogParams(body);
994
- const startTime = Date.now();
995
- const parentPromise = super.create(openAIParams, options);
996
- if (openAIParams.stream) {
997
- const wrappedPromise = parentPromise.then(value => {
998
- if ('tee' in value) {
999
- const [stream1, stream2] = value.tee();
1000
- (async () => {
1001
- // Hoisted so the catch block can surface whatever was accumulated
1002
- // from the streamed chunks before the failure.
1003
- let completionIdFromResponse;
1004
- let systemFingerprintFromResponse;
1005
- try {
1006
- const contentBlocks = [];
1007
- let accumulatedContent = '';
1008
- let modelFromResponse;
1009
- let firstTokenTime;
1010
- let stopReason;
1011
- let usage = {
1012
- inputTokens: 0,
1013
- outputTokens: 0,
1014
- webSearchCount: 0
1015
- };
1016
- // Map to track in-progress tool calls
1017
- const toolCallsInProgress = new Map();
1018
- let rawUsageData;
1019
- for await (const chunk of stream1) {
1020
- // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
1021
- if (!modelFromResponse && chunk.model) {
1022
- modelFromResponse = chunk.model;
1023
- }
1024
- if (!completionIdFromResponse && chunk.id) {
1025
- completionIdFromResponse = chunk.id;
1026
- }
1027
- if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
1028
- systemFingerprintFromResponse = chunk.system_fingerprint;
1029
- }
1030
- const choice = chunk?.choices?.[0];
1031
- if (choice?.finish_reason) {
1032
- stopReason = choice.finish_reason;
1033
- }
1034
- const chunkWebSearchCount = calculateWebSearchCount(chunk);
1035
- if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
1036
- usage.webSearchCount = chunkWebSearchCount;
1037
- }
1038
- // Handle text content
1039
- const deltaContent = choice?.delta?.content;
1040
- if (deltaContent) {
1041
- if (firstTokenTime === undefined) {
1042
- firstTokenTime = Date.now();
1043
- }
1044
- accumulatedContent += deltaContent;
1045
- }
1046
- // Handle tool calls
1047
- const deltaToolCalls = choice?.delta?.tool_calls;
1048
- if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
1049
- if (firstTokenTime === undefined) {
1050
- firstTokenTime = Date.now();
1051
- }
1052
- for (const toolCall of deltaToolCalls) {
1053
- const index = toolCall.index;
1054
- if (index !== undefined) {
1055
- if (!toolCallsInProgress.has(index)) {
1056
- // New tool call
1057
- toolCallsInProgress.set(index, {
1058
- id: toolCall.id || '',
1059
- name: toolCall.function?.name || '',
1060
- arguments: ''
1061
- });
1062
- }
1063
- const inProgressCall = toolCallsInProgress.get(index);
1064
- if (inProgressCall) {
1065
- // Update tool call data
1066
- if (toolCall.id) {
1067
- inProgressCall.id = toolCall.id;
1068
- }
1069
- if (toolCall.function?.name) {
1070
- inProgressCall.name = toolCall.function.name;
1071
- }
1072
- if (toolCall.function?.arguments) {
1073
- inProgressCall.arguments += toolCall.function.arguments;
1074
- }
1075
- }
1076
- }
1077
- }
1078
- }
1079
- // Handle usage information
1080
- if (chunk.usage) {
1081
- rawUsageData = chunk.usage;
1082
- usage = {
1083
- ...usage,
1084
- inputTokens: chunk.usage.prompt_tokens ?? 0,
1085
- outputTokens: chunk.usage.completion_tokens ?? 0,
1086
- reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
1087
- cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
1088
- };
1089
- }
1090
- }
1091
- // Build final content blocks
1092
- if (accumulatedContent) {
1093
- contentBlocks.push({
1094
- type: 'text',
1095
- text: accumulatedContent
1096
- });
1097
- }
1098
- // Add completed tool calls to content blocks
1099
- for (const toolCall of toolCallsInProgress.values()) {
1100
- if (toolCall.name) {
1101
- contentBlocks.push({
1102
- type: 'function',
1103
- id: toolCall.id,
1104
- function: {
1105
- name: toolCall.name,
1106
- arguments: toolCall.arguments
1107
- }
1108
- });
1109
- }
1110
- }
1111
- // Format output to match non-streaming version
1112
- const formattedOutput = contentBlocks.length > 0 ? [{
1113
- role: 'assistant',
1114
- content: contentBlocks
1115
- }] : [{
1116
- role: 'assistant',
1117
- content: [{
1118
- type: 'text',
1119
- text: ''
1120
- }]
1121
- }];
1122
- const latency = (Date.now() - startTime) / 1000;
1123
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1124
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
1125
- await captureAiGeneration(this.phClient, {
1126
- ...posthogParams,
1127
- model: openAIParams.model ?? modelFromResponse,
1128
- provider: 'openai',
1129
- input: sanitizeOpenAI(openAIParams.messages),
1130
- output: formattedOutput,
1131
- latency,
1132
- timeToFirstToken,
1133
- baseURL: this.baseURL,
1134
- modelParameters: getModelParams(body),
1135
- httpStatus: 200,
1136
- usage: {
1137
- inputTokens: usage.inputTokens,
1138
- outputTokens: usage.outputTokens,
1139
- reasoningTokens: usage.reasoningTokens,
1140
- cacheReadInputTokens: usage.cacheReadInputTokens,
1141
- webSearchCount: usage.webSearchCount,
1142
- rawUsage: rawUsageData
1143
- },
1144
- stopReason,
1145
- tools: availableTools,
1146
- completionId: completionIdFromResponse,
1147
- providerMetadata: buildProviderMetadata({
1148
- systemFingerprint: systemFingerprintFromResponse
1149
- })
1150
- });
1151
- } catch (error) {
1152
- await captureAiGeneration(this.phClient, {
1153
- ...posthogParams,
1154
- model: openAIParams.model,
1155
- provider: 'openai',
1156
- input: sanitizeOpenAI(openAIParams.messages),
1157
- output: [],
1158
- latency: 0,
1159
- baseURL: this.baseURL,
1160
- modelParameters: getModelParams(body),
1161
- usage: {
1162
- inputTokens: 0,
1163
- outputTokens: 0
1164
- },
1165
- // If the stream fails mid-flight, surface whatever completion
1166
- // metadata the consumed chunks already provided so the error
1167
- // event can still be correlated to OpenAI's Logs dashboard.
1168
- completionId: completionIdFromResponse,
1169
- providerMetadata: buildProviderMetadata({
1170
- systemFingerprint: systemFingerprintFromResponse
1171
- }),
1172
- error
1173
- });
1174
- throw error;
1175
- }
1176
- })();
1177
- // Return the other stream to the user
1178
- return stream2;
1179
- }
1180
- return value;
1181
- });
1182
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
576
+ const mapVercelPrompt = messages => {
577
+ // Map and truncate individual content
578
+ const inputs = messages.map(message => {
579
+ let content;
580
+ // Handle system role which has string content
581
+ if (message.role === 'system') {
582
+ content = [{
583
+ type: 'text',
584
+ text: truncate(toContentString(message.content))
585
+ }];
1183
586
  } else {
1184
- const wrappedPromise = parentPromise.then(async result => {
1185
- if ('choices' in result) {
1186
- const latency = (Date.now() - startTime) / 1000;
1187
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
1188
- const formattedOutput = formatResponseOpenAI(result);
1189
- await captureAiGenerationAfterSuccess(this.phClient, {
1190
- ...posthogParams,
1191
- model: openAIParams.model ?? result.model,
1192
- provider: 'openai',
1193
- input: sanitizeOpenAI(openAIParams.messages),
1194
- output: formattedOutput,
1195
- latency,
1196
- baseURL: this.baseURL,
1197
- modelParameters: getModelParams(body),
1198
- httpStatus: 200,
1199
- usage: {
1200
- inputTokens: result.usage?.prompt_tokens ?? 0,
1201
- outputTokens: result.usage?.completion_tokens ?? 0,
1202
- reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
1203
- cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0,
1204
- webSearchCount: calculateWebSearchCount(result),
1205
- rawUsage: result.usage
1206
- },
1207
- stopReason: result.choices[0]?.finish_reason ?? undefined,
1208
- tools: availableTools,
1209
- completionId: result.id,
1210
- providerMetadata: buildProviderMetadata({
1211
- systemFingerprint: result.system_fingerprint,
1212
- requestId: extractRequestId(result)
1213
- })
1214
- });
1215
- }
1216
- return result;
1217
- }, async error => {
1218
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1219
- await captureAiGeneration(this.phClient, {
1220
- ...posthogParams,
1221
- model: openAIParams.model,
1222
- provider: 'openai',
1223
- input: sanitizeOpenAI(openAIParams.messages),
1224
- output: [],
1225
- latency: 0,
1226
- baseURL: this.baseURL,
1227
- modelParameters: getModelParams(body),
1228
- httpStatus,
1229
- usage: {
1230
- inputTokens: 0,
1231
- outputTokens: 0
1232
- },
1233
- error
1234
- });
1235
- throw error;
1236
- });
1237
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
1238
- }
1239
- }
1240
- };
1241
- let WrappedResponses$1 = class WrappedResponses extends Responses {
1242
- constructor(client, phClient) {
1243
- super(client);
1244
- this.phClient = phClient;
1245
- this.baseURL = client.baseURL;
1246
- }
1247
- // --- Implementation Signature
1248
- create(body, options) {
1249
- const {
1250
- providerParams: openAIParams,
1251
- posthogParams
1252
- } = extractPosthogParams(body);
1253
- const startTime = Date.now();
1254
- const parentPromise = super.create(openAIParams, options);
1255
- if (openAIParams.stream) {
1256
- const wrappedPromise = parentPromise.then(value => {
1257
- if ('tee' in value && typeof value.tee === 'function') {
1258
- const [stream1, stream2] = value.tee();
1259
- (async () => {
1260
- // Hoisted so the catch block can surface the completion ID that
1261
- // was accumulated from the streamed chunks before the failure.
1262
- let completionIdFromResponse;
1263
- try {
1264
- let finalContent = [];
1265
- let modelFromResponse;
1266
- let firstTokenTime;
1267
- let stopReason;
1268
- let usage = {
1269
- inputTokens: 0,
1270
- outputTokens: 0,
1271
- webSearchCount: 0
1272
- };
1273
- let rawUsageData;
1274
- for await (const chunk of stream1) {
1275
- // Track first token time on content delta events
1276
- if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
1277
- firstTokenTime = Date.now();
1278
- }
1279
- if ('response' in chunk && chunk.response) {
1280
- // Extract model and completion ID from the response object in the chunk (for stored prompts)
1281
- if (!modelFromResponse && chunk.response.model) {
1282
- modelFromResponse = chunk.response.model;
1283
- }
1284
- if (!completionIdFromResponse && chunk.response.id) {
1285
- completionIdFromResponse = chunk.response.id;
1286
- }
1287
- const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
1288
- if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
1289
- usage.webSearchCount = chunkWebSearchCount;
1290
- }
1291
- }
1292
- if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1293
- finalContent = chunk.response.output;
1294
- if (chunk.response.status) {
1295
- stopReason = chunk.response.status;
1296
- }
1297
- }
1298
- if ('response' in chunk && chunk.response?.usage) {
1299
- rawUsageData = chunk.response.usage;
1300
- usage = {
1301
- ...usage,
1302
- inputTokens: chunk.response.usage.input_tokens ?? 0,
1303
- outputTokens: chunk.response.usage.output_tokens ?? 0,
1304
- reasoningTokens: chunk.response.usage.output_tokens_details?.reasoning_tokens ?? 0,
1305
- cacheReadInputTokens: chunk.response.usage.input_tokens_details?.cached_tokens ?? 0
1306
- };
1307
- }
1308
- }
1309
- const latency = (Date.now() - startTime) / 1000;
1310
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1311
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
1312
- await captureAiGeneration(this.phClient, {
1313
- ...posthogParams,
1314
- model: openAIParams.model ?? modelFromResponse,
1315
- provider: 'openai',
1316
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1317
- output: finalContent,
1318
- latency,
1319
- timeToFirstToken,
1320
- baseURL: this.baseURL,
1321
- modelParameters: getModelParams(body),
1322
- httpStatus: 200,
1323
- usage: {
1324
- inputTokens: usage.inputTokens,
1325
- outputTokens: usage.outputTokens,
1326
- reasoningTokens: usage.reasoningTokens,
1327
- cacheReadInputTokens: usage.cacheReadInputTokens,
1328
- webSearchCount: usage.webSearchCount,
1329
- rawUsage: rawUsageData
1330
- },
1331
- stopReason,
1332
- tools: availableTools,
1333
- completionId: completionIdFromResponse
1334
- });
1335
- } catch (error) {
1336
- await captureAiGeneration(this.phClient, {
1337
- ...posthogParams,
1338
- model: openAIParams.model,
1339
- provider: 'openai',
1340
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1341
- output: [],
1342
- latency: 0,
1343
- baseURL: this.baseURL,
1344
- modelParameters: getModelParams(body),
1345
- usage: {
1346
- inputTokens: 0,
1347
- outputTokens: 0
1348
- },
1349
- // Surface the completion ID from any chunks consumed before
1350
- // the stream failed so the error event remains correlatable.
1351
- completionId: completionIdFromResponse,
1352
- error
1353
- });
1354
- throw error;
1355
- }
1356
- })();
1357
- return stream2;
1358
- }
1359
- return value;
1360
- });
1361
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
1362
- } else {
1363
- const wrappedPromise = parentPromise.then(async result => {
1364
- if ('output' in result) {
1365
- const latency = (Date.now() - startTime) / 1000;
1366
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
1367
- const formattedOutput = formatResponseOpenAI({
1368
- output: result.output
1369
- });
1370
- await captureAiGenerationAfterSuccess(this.phClient, {
1371
- ...posthogParams,
1372
- model: openAIParams.model ?? result.model,
1373
- provider: 'openai',
1374
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1375
- output: formattedOutput,
1376
- latency,
1377
- baseURL: this.baseURL,
1378
- modelParameters: getModelParams(body),
1379
- httpStatus: 200,
1380
- usage: {
1381
- inputTokens: result.usage?.input_tokens ?? 0,
1382
- outputTokens: result.usage?.output_tokens ?? 0,
1383
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1384
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1385
- webSearchCount: calculateWebSearchCount(result),
1386
- rawUsage: result.usage
1387
- },
1388
- stopReason: result.status ?? undefined,
1389
- tools: availableTools,
1390
- completionId: result.id,
1391
- providerMetadata: buildProviderMetadata({
1392
- requestId: extractRequestId(result)
1393
- })
1394
- });
1395
- }
1396
- return result;
1397
- }, async error => {
1398
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1399
- await captureAiGeneration(this.phClient, {
1400
- ...posthogParams,
1401
- model: openAIParams.model,
1402
- provider: 'openai',
1403
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1404
- output: [],
1405
- latency: 0,
1406
- baseURL: this.baseURL,
1407
- modelParameters: getModelParams(body),
1408
- httpStatus,
1409
- usage: {
1410
- inputTokens: 0,
1411
- outputTokens: 0
1412
- },
1413
- error
1414
- });
1415
- throw error;
1416
- });
1417
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
1418
- }
1419
- }
1420
- parse(body, options) {
1421
- const {
1422
- providerParams: openAIParams,
1423
- posthogParams
1424
- } = extractPosthogParams(body);
1425
- const startTime = Date.now();
1426
- const originalCreate = super.create.bind(this);
1427
- const originalSelfRecord = this;
1428
- const tempCreate = originalSelfRecord['create'];
1429
- originalSelfRecord['create'] = originalCreate;
1430
- try {
1431
- const parentPromise = super.parse(openAIParams, options);
1432
- const wrappedPromise = parentPromise.then(async result => {
1433
- const latency = (Date.now() - startTime) / 1000;
1434
- await captureAiGeneration(this.phClient, {
1435
- ...posthogParams,
1436
- model: openAIParams.model ?? result.model,
1437
- provider: 'openai',
1438
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1439
- output: result.output,
1440
- latency,
1441
- baseURL: this.baseURL,
1442
- modelParameters: getModelParams(body),
1443
- httpStatus: 200,
1444
- usage: {
1445
- inputTokens: result.usage?.input_tokens ?? 0,
1446
- outputTokens: result.usage?.output_tokens ?? 0,
1447
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1448
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1449
- rawUsage: result.usage
1450
- },
1451
- stopReason: result.status ?? undefined,
1452
- completionId: result.id,
1453
- providerMetadata: buildProviderMetadata({
1454
- requestId: extractRequestId(result)
1455
- })
1456
- });
1457
- return result;
1458
- }, async error => {
1459
- await captureAiGeneration(this.phClient, {
1460
- ...posthogParams,
1461
- model: openAIParams.model,
1462
- provider: 'openai',
1463
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1464
- output: [],
1465
- latency: 0,
1466
- baseURL: this.baseURL,
1467
- modelParameters: getModelParams(body),
1468
- usage: {
1469
- inputTokens: 0,
1470
- outputTokens: 0
1471
- },
1472
- error
1473
- });
1474
- throw error;
1475
- });
1476
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
1477
- } finally {
1478
- // Restore our wrapped create method
1479
- originalSelfRecord['create'] = tempCreate;
1480
- }
1481
- }
1482
- };
1483
- let WrappedEmbeddings$1 = class WrappedEmbeddings extends Embeddings {
1484
- constructor(client, phClient) {
1485
- super(client);
1486
- this.phClient = phClient;
1487
- this.baseURL = client.baseURL;
1488
- }
1489
- create(body, options) {
1490
- const {
1491
- providerParams: openAIParams,
1492
- posthogParams
1493
- } = extractPosthogParams(body);
1494
- const startTime = Date.now();
1495
- const parentPromise = super.create(openAIParams, options);
1496
- const wrappedPromise = parentPromise.then(async result => {
1497
- const latency = (Date.now() - startTime) / 1000;
1498
- await captureAiGeneration(this.phClient, {
1499
- ...posthogParams,
1500
- eventType: AIEvent.Embedding,
1501
- model: openAIParams.model,
1502
- provider: 'openai',
1503
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
1504
- output: null,
1505
- // Embeddings don't have output content
1506
- latency,
1507
- baseURL: this.baseURL,
1508
- modelParameters: getModelParams(body),
1509
- httpStatus: 200,
1510
- usage: {
1511
- inputTokens: result.usage?.prompt_tokens ?? 0,
1512
- rawUsage: result.usage
1513
- }
1514
- });
1515
- return result;
1516
- }, async error => {
1517
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1518
- await captureAiGeneration(this.phClient, {
1519
- eventType: AIEvent.Embedding,
1520
- ...posthogParams,
1521
- model: openAIParams.model,
1522
- provider: 'openai',
1523
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
1524
- output: null,
1525
- // Embeddings don't have output content
1526
- latency: 0,
1527
- baseURL: this.baseURL,
1528
- modelParameters: getModelParams(body),
1529
- httpStatus,
1530
- usage: {
1531
- inputTokens: 0
1532
- },
1533
- error
1534
- });
1535
- throw error;
1536
- });
1537
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
1538
- }
1539
- };
1540
- class WrappedAudio extends Audio {
1541
- constructor(parentClient, phClient) {
1542
- super(parentClient);
1543
- this.transcriptions = new WrappedTranscriptions(parentClient, phClient);
1544
- }
1545
- }
1546
- class WrappedTranscriptions extends Transcriptions {
1547
- constructor(client, phClient) {
1548
- super(client);
1549
- this.phClient = phClient;
1550
- this.baseURL = client.baseURL;
1551
- }
1552
- // --- Implementation Signature
1553
- create(body, options) {
1554
- const {
1555
- providerParams: openAIParams,
1556
- posthogParams
1557
- } = extractPosthogParams(body);
1558
- const startTime = Date.now();
1559
- const parentPromise = openAIParams.stream ? super.create(openAIParams, options) : super.create(openAIParams, options);
1560
- if (openAIParams.stream) {
1561
- const wrappedPromise = parentPromise.then(value => {
1562
- if ('tee' in value && typeof value.tee === 'function') {
1563
- const [stream1, stream2] = value.tee();
1564
- (async () => {
1565
- try {
1566
- let finalContent = '';
1567
- let firstTokenTime;
1568
- let usage = {
1569
- inputTokens: 0,
1570
- outputTokens: 0
1571
- };
1572
- const doneEvent = 'transcript.text.done';
1573
- for await (const chunk of stream1) {
1574
- // Track first token on text delta events
1575
- if (firstTokenTime === undefined && chunk.type === 'transcript.text.delta') {
1576
- firstTokenTime = Date.now();
1577
- }
1578
- if (chunk.type === doneEvent && 'text' in chunk && chunk.text && chunk.text.length > 0) {
1579
- finalContent = chunk.text;
1580
- }
1581
- if ('usage' in chunk && chunk.usage) {
1582
- usage = {
1583
- inputTokens: chunk.usage?.type === 'tokens' ? chunk.usage.input_tokens ?? 0 : 0,
1584
- outputTokens: chunk.usage?.type === 'tokens' ? chunk.usage.output_tokens ?? 0 : 0,
1585
- rawUsage: chunk.usage
1586
- };
1587
- }
1588
- }
1589
- const latency = (Date.now() - startTime) / 1000;
1590
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1591
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
1592
- await captureAiGeneration(this.phClient, {
1593
- ...posthogParams,
1594
- model: openAIParams.model,
1595
- provider: 'openai',
1596
- input: openAIParams.prompt,
1597
- output: finalContent,
1598
- latency,
1599
- timeToFirstToken,
1600
- baseURL: this.baseURL,
1601
- modelParameters: getModelParams(body),
1602
- httpStatus: 200,
1603
- usage,
1604
- tools: availableTools
1605
- });
1606
- } catch (error) {
1607
- await captureAiGeneration(this.phClient, {
1608
- ...posthogParams,
1609
- model: openAIParams.model,
1610
- provider: 'openai',
1611
- input: openAIParams.prompt,
1612
- output: [],
1613
- latency: 0,
1614
- baseURL: this.baseURL,
1615
- modelParameters: getModelParams(body),
1616
- usage: {
1617
- inputTokens: 0,
1618
- outputTokens: 0
1619
- },
1620
- error
1621
- });
1622
- throw error;
1623
- }
1624
- })();
1625
- return stream2;
1626
- }
1627
- return value;
1628
- });
1629
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
1630
- } else {
1631
- const wrappedPromise = parentPromise.then(async result => {
1632
- if (result && typeof result === 'object' && 'text' in result) {
1633
- const latency = (Date.now() - startTime) / 1000;
1634
- await captureAiGenerationAfterSuccess(this.phClient, {
1635
- ...posthogParams,
1636
- model: openAIParams.model,
1637
- provider: 'openai',
1638
- input: openAIParams.prompt,
1639
- output: result.text,
1640
- latency,
1641
- baseURL: this.baseURL,
1642
- modelParameters: getModelParams(body),
1643
- httpStatus: 200,
1644
- usage: {
1645
- inputTokens: result.usage?.type === 'tokens' ? result.usage.input_tokens ?? 0 : 0,
1646
- outputTokens: result.usage?.type === 'tokens' ? result.usage.output_tokens ?? 0 : 0,
1647
- rawUsage: result.usage
1648
- }
1649
- });
1650
- }
1651
- return result;
1652
- }, async error => {
1653
- await captureAiGeneration(this.phClient, {
1654
- ...posthogParams,
1655
- model: openAIParams.model,
1656
- provider: 'openai',
1657
- input: openAIParams.prompt,
1658
- output: [],
1659
- latency: 0,
1660
- baseURL: this.baseURL,
1661
- modelParameters: getModelParams(body),
1662
- usage: {
1663
- inputTokens: 0,
1664
- outputTokens: 0
1665
- },
1666
- error
1667
- });
1668
- throw error;
1669
- });
1670
- return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
1671
- }
1672
- }
1673
- }
1674
-
1675
- class PostHogAzureOpenAI extends AzureOpenAI {
1676
- constructor(config) {
1677
- const {
1678
- posthog,
1679
- ...openAIConfig
1680
- } = config;
1681
- super(openAIConfig);
1682
- this.phClient = posthog;
1683
- this.chat = new WrappedChat(this, this.phClient);
1684
- this.embeddings = new WrappedEmbeddings(this, this.phClient);
1685
- }
1686
- }
1687
- class WrappedChat extends AzureOpenAI.Chat {
1688
- constructor(parentClient, phClient) {
1689
- super(parentClient);
1690
- this.completions = new WrappedCompletions(parentClient, phClient);
1691
- }
1692
- }
1693
- class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1694
- constructor(client, phClient) {
1695
- super(client);
1696
- this.phClient = phClient;
1697
- this.baseURL = client.baseURL;
1698
- }
1699
- // --- Implementation Signature
1700
- create(body, options) {
1701
- const {
1702
- providerParams: openAIParams,
1703
- posthogParams
1704
- } = extractPosthogParams(body);
1705
- const startTime = Date.now();
1706
- const parentPromise = super.create(openAIParams, options);
1707
- if (openAIParams.stream) {
1708
- return parentPromise.then(value => {
1709
- if ('tee' in value) {
1710
- const [stream1, stream2] = value.tee();
1711
- (async () => {
1712
- // Hoisted so the catch block can surface whatever was accumulated
1713
- // from the streamed chunks before the failure.
1714
- let completionIdFromResponse;
1715
- let systemFingerprintFromResponse;
1716
- try {
1717
- const contentBlocks = [];
1718
- let accumulatedContent = '';
1719
- let modelFromResponse;
1720
- let firstTokenTime;
1721
- let usage = {
1722
- inputTokens: 0,
1723
- outputTokens: 0
1724
- };
1725
- // Map to track in-progress tool calls
1726
- const toolCallsInProgress = new Map();
1727
- for await (const chunk of stream1) {
1728
- // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
1729
- if (!modelFromResponse && chunk.model) {
1730
- modelFromResponse = chunk.model;
1731
- }
1732
- if (!completionIdFromResponse && chunk.id) {
1733
- completionIdFromResponse = chunk.id;
1734
- }
1735
- if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
1736
- systemFingerprintFromResponse = chunk.system_fingerprint;
1737
- }
1738
- const choice = chunk?.choices?.[0];
1739
- // Handle text content
1740
- const deltaContent = choice?.delta?.content;
1741
- if (deltaContent) {
1742
- if (firstTokenTime === undefined) {
1743
- firstTokenTime = Date.now();
1744
- }
1745
- accumulatedContent += deltaContent;
1746
- }
1747
- // Handle tool calls
1748
- const deltaToolCalls = choice?.delta?.tool_calls;
1749
- if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
1750
- if (firstTokenTime === undefined) {
1751
- firstTokenTime = Date.now();
1752
- }
1753
- for (const toolCall of deltaToolCalls) {
1754
- const index = toolCall.index;
1755
- if (index !== undefined) {
1756
- if (!toolCallsInProgress.has(index)) {
1757
- // New tool call
1758
- toolCallsInProgress.set(index, {
1759
- id: toolCall.id || '',
1760
- name: toolCall.function?.name || '',
1761
- arguments: ''
1762
- });
1763
- }
1764
- const inProgressCall = toolCallsInProgress.get(index);
1765
- if (inProgressCall) {
1766
- // Update tool call data
1767
- if (toolCall.id) {
1768
- inProgressCall.id = toolCall.id;
1769
- }
1770
- if (toolCall.function?.name) {
1771
- inProgressCall.name = toolCall.function.name;
1772
- }
1773
- if (toolCall.function?.arguments) {
1774
- inProgressCall.arguments += toolCall.function.arguments;
1775
- }
1776
- }
1777
- }
1778
- }
1779
- }
1780
- // Handle usage information
1781
- if (chunk.usage) {
1782
- usage = {
1783
- inputTokens: chunk.usage.prompt_tokens ?? 0,
1784
- outputTokens: chunk.usage.completion_tokens ?? 0,
1785
- reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
1786
- cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
1787
- };
1788
- }
1789
- }
1790
- // Build final content blocks
1791
- if (accumulatedContent) {
1792
- contentBlocks.push({
1793
- type: 'text',
1794
- text: accumulatedContent
1795
- });
1796
- }
1797
- // Add completed tool calls to content blocks
1798
- for (const toolCall of toolCallsInProgress.values()) {
1799
- if (toolCall.name) {
1800
- contentBlocks.push({
1801
- type: 'function',
1802
- id: toolCall.id,
1803
- function: {
1804
- name: toolCall.name,
1805
- arguments: toolCall.arguments
1806
- }
1807
- });
1808
- }
1809
- }
1810
- // Format output to match non-streaming version
1811
- const formattedOutput = contentBlocks.length > 0 ? [{
1812
- role: 'assistant',
1813
- content: contentBlocks
1814
- }] : [{
1815
- role: 'assistant',
1816
- content: [{
1817
- type: 'text',
1818
- text: ''
1819
- }]
1820
- }];
1821
- const latency = (Date.now() - startTime) / 1000;
1822
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1823
- await captureAiGeneration(this.phClient, {
1824
- ...posthogParams,
1825
- model: openAIParams.model ?? modelFromResponse,
1826
- provider: 'azure',
1827
- input: sanitizeOpenAI(openAIParams.messages),
1828
- output: formattedOutput,
1829
- latency,
1830
- timeToFirstToken,
1831
- baseURL: this.baseURL,
1832
- modelParameters: getModelParams(body),
1833
- httpStatus: 200,
1834
- usage,
1835
- completionId: completionIdFromResponse,
1836
- providerMetadata: buildProviderMetadata({
1837
- systemFingerprint: systemFingerprintFromResponse
1838
- })
1839
- });
1840
- } catch (error) {
1841
- await captureAiGeneration(this.phClient, {
1842
- ...posthogParams,
1843
- model: openAIParams.model,
1844
- provider: 'azure',
1845
- input: sanitizeOpenAI(openAIParams.messages),
1846
- output: [],
1847
- latency: 0,
1848
- baseURL: this.baseURL,
1849
- modelParameters: getModelParams(body),
1850
- usage: {
1851
- inputTokens: 0,
1852
- outputTokens: 0
1853
- },
1854
- // If the stream fails mid-flight, surface whatever completion
1855
- // metadata the consumed chunks already provided so the error
1856
- // event can still be correlated to OpenAI's Logs dashboard.
1857
- completionId: completionIdFromResponse,
1858
- providerMetadata: buildProviderMetadata({
1859
- systemFingerprint: systemFingerprintFromResponse
1860
- }),
1861
- error: error
1862
- });
1863
- throw error;
1864
- }
1865
- })();
1866
- // Return the other stream to the user
1867
- return stream2;
1868
- }
1869
- return value;
1870
- });
1871
- } else {
1872
- const wrappedPromise = parentPromise.then(async result => {
1873
- if ('choices' in result) {
1874
- const latency = (Date.now() - startTime) / 1000;
1875
- await captureAiGeneration(this.phClient, {
1876
- ...posthogParams,
1877
- model: openAIParams.model ?? result.model,
1878
- provider: 'azure',
1879
- input: openAIParams.messages,
1880
- output: formatResponseOpenAI(result),
1881
- latency,
1882
- baseURL: this.baseURL,
1883
- modelParameters: getModelParams(body),
1884
- httpStatus: 200,
1885
- usage: {
1886
- inputTokens: result.usage?.prompt_tokens ?? 0,
1887
- outputTokens: result.usage?.completion_tokens ?? 0,
1888
- reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
1889
- cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0
1890
- },
1891
- completionId: result.id,
1892
- providerMetadata: buildProviderMetadata({
1893
- systemFingerprint: result.system_fingerprint,
1894
- requestId: extractRequestId(result)
1895
- })
1896
- });
1897
- }
1898
- return result;
1899
- }, async error => {
1900
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1901
- await captureAiGeneration(this.phClient, {
1902
- ...posthogParams,
1903
- model: openAIParams.model,
1904
- provider: 'azure',
1905
- input: openAIParams.messages,
1906
- output: [],
1907
- latency: 0,
1908
- baseURL: this.baseURL,
1909
- modelParameters: getModelParams(body),
1910
- httpStatus,
1911
- usage: {
1912
- inputTokens: 0,
1913
- outputTokens: 0
1914
- },
1915
- error
1916
- });
1917
- throw error;
1918
- });
1919
- return wrappedPromise;
1920
- }
1921
- }
1922
- }
1923
- class WrappedResponses extends AzureOpenAI.Responses {
1924
- constructor(client, phClient) {
1925
- super(client);
1926
- this.phClient = phClient;
1927
- this.baseURL = client.baseURL;
1928
- }
1929
- // --- Implementation Signature
1930
- create(body, options) {
1931
- const {
1932
- providerParams: openAIParams,
1933
- posthogParams
1934
- } = extractPosthogParams(body);
1935
- const startTime = Date.now();
1936
- const parentPromise = super.create(openAIParams, options);
1937
- if (openAIParams.stream) {
1938
- return parentPromise.then(value => {
1939
- if ('tee' in value && typeof value.tee === 'function') {
1940
- const [stream1, stream2] = value.tee();
1941
- (async () => {
1942
- // Hoisted so the catch block can surface the completion ID that
1943
- // was accumulated from the streamed chunks before the failure.
1944
- let completionIdFromResponse;
1945
- try {
1946
- let finalContent = [];
1947
- let modelFromResponse;
1948
- let firstTokenTime;
1949
- let usage = {
1950
- inputTokens: 0,
1951
- outputTokens: 0
1952
- };
1953
- for await (const chunk of stream1) {
1954
- // Track first token time on content delta events
1955
- if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
1956
- firstTokenTime = Date.now();
1957
- }
1958
- if ('response' in chunk && chunk.response) {
1959
- // Extract model and completion ID from the response object in the chunk (for stored prompts)
1960
- if (!modelFromResponse && chunk.response.model) {
1961
- modelFromResponse = chunk.response.model;
1962
- }
1963
- if (!completionIdFromResponse && chunk.response.id) {
1964
- completionIdFromResponse = chunk.response.id;
1965
- }
1966
- }
1967
- if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1968
- finalContent = chunk.response.output;
1969
- }
1970
- if ('usage' in chunk && chunk.usage) {
1971
- usage = {
1972
- inputTokens: chunk.usage.input_tokens ?? 0,
1973
- outputTokens: chunk.usage.output_tokens ?? 0,
1974
- reasoningTokens: chunk.usage.output_tokens_details?.reasoning_tokens ?? 0,
1975
- cacheReadInputTokens: chunk.usage.input_tokens_details?.cached_tokens ?? 0
1976
- };
1977
- }
1978
- }
1979
- const latency = (Date.now() - startTime) / 1000;
1980
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1981
- await captureAiGeneration(this.phClient, {
1982
- ...posthogParams,
1983
- model: openAIParams.model ?? modelFromResponse,
1984
- provider: 'azure',
1985
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1986
- output: finalContent,
1987
- latency,
1988
- timeToFirstToken,
1989
- baseURL: this.baseURL,
1990
- modelParameters: getModelParams(body),
1991
- httpStatus: 200,
1992
- usage,
1993
- completionId: completionIdFromResponse
1994
- });
1995
- } catch (error) {
1996
- await captureAiGeneration(this.phClient, {
1997
- ...posthogParams,
1998
- model: openAIParams.model,
1999
- provider: 'azure',
2000
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
2001
- output: [],
2002
- latency: 0,
2003
- baseURL: this.baseURL,
2004
- modelParameters: getModelParams(body),
2005
- usage: {
2006
- inputTokens: 0,
2007
- outputTokens: 0
2008
- },
2009
- // Surface the completion ID from any chunks consumed before
2010
- // the stream failed so the error event remains correlatable.
2011
- completionId: completionIdFromResponse,
2012
- error: error
2013
- });
2014
- throw error;
2015
- }
2016
- })();
2017
- return stream2;
2018
- }
2019
- return value;
2020
- });
2021
- } else {
2022
- const wrappedPromise = parentPromise.then(async result => {
2023
- if ('output' in result) {
2024
- const latency = (Date.now() - startTime) / 1000;
2025
- await captureAiGeneration(this.phClient, {
2026
- ...posthogParams,
2027
- model: openAIParams.model ?? result.model,
2028
- provider: 'azure',
2029
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
2030
- output: result.output,
2031
- latency,
2032
- baseURL: this.baseURL,
2033
- modelParameters: getModelParams(body),
2034
- httpStatus: 200,
2035
- usage: {
2036
- inputTokens: result.usage?.input_tokens ?? 0,
2037
- outputTokens: result.usage?.output_tokens ?? 0,
2038
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
2039
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
2040
- },
2041
- completionId: result.id,
2042
- providerMetadata: buildProviderMetadata({
2043
- requestId: extractRequestId(result)
2044
- })
2045
- });
2046
- }
2047
- return result;
2048
- }, async error => {
2049
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
2050
- await captureAiGeneration(this.phClient, {
2051
- ...posthogParams,
2052
- model: openAIParams.model,
2053
- provider: 'azure',
2054
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
2055
- output: [],
2056
- latency: 0,
2057
- baseURL: this.baseURL,
2058
- modelParameters: getModelParams(body),
2059
- httpStatus,
2060
- usage: {
2061
- inputTokens: 0,
2062
- outputTokens: 0
2063
- },
2064
- error
2065
- });
2066
- throw error;
2067
- });
2068
- return wrappedPromise;
2069
- }
2070
- }
2071
- parse(body, options) {
2072
- const {
2073
- providerParams: openAIParams,
2074
- posthogParams
2075
- } = extractPosthogParams(body);
2076
- const startTime = Date.now();
2077
- const parentPromise = super.parse(openAIParams, options);
2078
- const wrappedPromise = parentPromise.then(async result => {
2079
- const latency = (Date.now() - startTime) / 1000;
2080
- await captureAiGeneration(this.phClient, {
2081
- ...posthogParams,
2082
- model: openAIParams.model ?? result.model,
2083
- provider: 'azure',
2084
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
2085
- output: result.output,
2086
- latency,
2087
- baseURL: this.baseURL,
2088
- modelParameters: getModelParams(body),
2089
- httpStatus: 200,
2090
- usage: {
2091
- inputTokens: result.usage?.input_tokens ?? 0,
2092
- outputTokens: result.usage?.output_tokens ?? 0,
2093
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
2094
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
2095
- },
2096
- completionId: result.id,
2097
- providerMetadata: buildProviderMetadata({
2098
- requestId: extractRequestId(result)
2099
- })
2100
- });
2101
- return result;
2102
- }, async error => {
2103
- await captureAiGeneration(this.phClient, {
2104
- ...posthogParams,
2105
- model: openAIParams.model,
2106
- provider: 'azure',
2107
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
2108
- output: [],
2109
- latency: 0,
2110
- baseURL: this.baseURL,
2111
- modelParameters: getModelParams(body),
2112
- httpStatus: error?.status ? error.status : 500,
2113
- usage: {
2114
- inputTokens: 0,
2115
- outputTokens: 0
2116
- },
2117
- error
2118
- });
2119
- throw error;
2120
- });
2121
- return wrappedPromise;
2122
- }
2123
- }
2124
- class WrappedEmbeddings extends AzureOpenAI.Embeddings {
2125
- constructor(client, phClient) {
2126
- super(client);
2127
- this.phClient = phClient;
2128
- this.baseURL = client.baseURL;
2129
- }
2130
- create(body, options) {
2131
- const {
2132
- providerParams: openAIParams,
2133
- posthogParams
2134
- } = extractPosthogParams(body);
2135
- const startTime = Date.now();
2136
- const parentPromise = super.create(openAIParams, options);
2137
- const wrappedPromise = parentPromise.then(async result => {
2138
- const latency = (Date.now() - startTime) / 1000;
2139
- await captureAiGeneration(this.phClient, {
2140
- eventType: AIEvent.Embedding,
2141
- ...posthogParams,
2142
- model: openAIParams.model,
2143
- provider: 'azure',
2144
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
2145
- output: null,
2146
- // Embeddings don't have output content
2147
- latency,
2148
- baseURL: this.baseURL,
2149
- modelParameters: getModelParams(body),
2150
- httpStatus: 200,
2151
- usage: {
2152
- inputTokens: result.usage?.prompt_tokens ?? 0
2153
- }
2154
- });
2155
- return result;
2156
- }, async error => {
2157
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
2158
- await captureAiGeneration(this.phClient, {
2159
- eventType: AIEvent.Embedding,
2160
- ...posthogParams,
2161
- model: openAIParams.model,
2162
- provider: 'azure',
2163
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
2164
- output: null,
2165
- latency: 0,
2166
- baseURL: this.baseURL,
2167
- modelParameters: getModelParams(body),
2168
- httpStatus,
2169
- usage: {
2170
- inputTokens: 0
2171
- },
2172
- error
2173
- });
2174
- throw error;
2175
- });
2176
- return wrappedPromise;
2177
- }
2178
- }
2179
-
2180
- // Type guards
2181
- function isV3Model(model) {
2182
- return model.specificationVersion === 'v3';
2183
- }
2184
- const mapVercelParams = params => {
2185
- return {
2186
- temperature: params.temperature,
2187
- max_output_tokens: params.maxOutputTokens,
2188
- top_p: params.topP,
2189
- frequency_penalty: params.frequencyPenalty,
2190
- presence_penalty: params.presencePenalty,
2191
- stop: params.stopSequences,
2192
- stream: params.stream
2193
- };
2194
- };
2195
- const mapVercelPrompt = messages => {
2196
- // Map and truncate individual content
2197
- const inputs = messages.map(message => {
2198
- let content;
2199
- // Handle system role which has string content
2200
- if (message.role === 'system') {
2201
- content = [{
2202
- type: 'text',
2203
- text: truncate(toContentString(message.content))
2204
- }];
2205
- } else {
2206
- // Handle other roles which have array content
2207
- if (Array.isArray(message.content)) {
2208
- content = message.content.map(c => {
2209
- if (c.type === 'text') {
2210
- return {
2211
- type: 'text',
2212
- text: truncate(c.text)
2213
- };
2214
- } else if (c.type === 'file') {
2215
- // For file type, check if it's a data URL and redact if needed
2216
- let fileData;
2217
- const contentData = c.data;
2218
- if (contentData instanceof URL) {
2219
- fileData = contentData.toString();
2220
- } else if (isString(contentData)) {
2221
- // Redact base64 data URLs and raw base64 to prevent oversized events
2222
- fileData = redactBase64DataUrl(contentData);
2223
- } else {
2224
- fileData = 'raw files not supported';
587
+ // Handle other roles which have array content
588
+ if (Array.isArray(message.content)) {
589
+ content = message.content.map(c => {
590
+ if (c.type === 'text') {
591
+ return {
592
+ type: 'text',
593
+ text: truncate(c.text)
594
+ };
595
+ } else if (c.type === 'file') {
596
+ // For file type, check if it's a data URL and redact if needed
597
+ let fileData;
598
+ const contentData = c.data;
599
+ if (contentData instanceof URL) {
600
+ fileData = contentData.toString();
601
+ } else if (isString(contentData)) {
602
+ // Redact base64 data URLs and raw base64 to prevent oversized events
603
+ fileData = redactBase64DataUrl(contentData);
604
+ } else {
605
+ fileData = 'raw files not supported';
2225
606
  }
2226
607
  return {
2227
608
  type: 'file',
@@ -2798,1794 +1179,19 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2798
1179
  return wrappedModel;
2799
1180
  };
2800
1181
 
2801
- class PostHogAnthropic extends AnthropicOriginal {
2802
- constructor(config) {
2803
- const {
2804
- posthog,
2805
- ...anthropicConfig
2806
- } = config;
2807
- super(anthropicConfig);
2808
- this.phClient = posthog;
2809
- this.messages = new WrappedMessages(this, this.phClient);
2810
- }
1182
+ /// <reference lib="dom" />
1183
+ const DEFAULT_CACHE_TTL_SECONDS = 300; // 5 minutes
1184
+ const DEFAULT_PROMPTS_HOST = 'https://us.posthog.com';
1185
+ function normalizeApiKey(value) {
1186
+ return typeof value === 'string' ? value.trim() : '';
2811
1187
  }
2812
- class WrappedMessages extends AnthropicOriginal.Messages {
2813
- constructor(parentClient, phClient) {
2814
- super(parentClient);
2815
- this.phClient = phClient;
2816
- this.baseURL = parentClient.baseURL;
2817
- }
2818
- create(body, options) {
2819
- const {
2820
- providerParams: anthropicParams,
2821
- posthogParams
2822
- } = extractPosthogParams(body);
2823
- const startTime = Date.now();
2824
- const parentPromise = super.create(anthropicParams, options);
2825
- if (anthropicParams.stream) {
2826
- return parentPromise.then(value => {
2827
- let accumulatedContent = '';
2828
- const contentBlocks = [];
2829
- const toolsInProgress = new Map();
2830
- let currentTextBlock = null;
2831
- let firstTokenTime;
2832
- let stopReason;
2833
- const usage = {
2834
- inputTokens: 0,
2835
- outputTokens: 0,
2836
- cacheCreationInputTokens: 0,
2837
- cacheReadInputTokens: 0,
2838
- webSearchCount: 0
2839
- };
2840
- let lastRawUsage;
2841
- if ('tee' in value) {
2842
- const [stream1, stream2] = value.tee();
2843
- (async () => {
2844
- try {
2845
- for await (const chunk of stream1) {
2846
- // Handle content block start events
2847
- if (chunk.type === 'content_block_start') {
2848
- if (chunk.content_block?.type === 'text') {
2849
- currentTextBlock = {
2850
- type: 'text',
2851
- text: ''
2852
- };
2853
- contentBlocks.push(currentTextBlock);
2854
- } else if (chunk.content_block?.type === 'tool_use') {
2855
- if (firstTokenTime === undefined) {
2856
- firstTokenTime = Date.now();
2857
- }
2858
- const toolBlock = {
2859
- type: 'function',
2860
- id: chunk.content_block.id,
2861
- function: {
2862
- name: chunk.content_block.name,
2863
- arguments: {}
2864
- }
2865
- };
2866
- contentBlocks.push(toolBlock);
2867
- toolsInProgress.set(chunk.content_block.id, {
2868
- block: toolBlock,
2869
- inputString: ''
2870
- });
2871
- currentTextBlock = null;
2872
- }
2873
- }
2874
- // Handle text delta events
2875
- if ('delta' in chunk) {
2876
- if ('text' in chunk.delta) {
2877
- const delta = chunk.delta.text;
2878
- if (firstTokenTime === undefined) {
2879
- firstTokenTime = Date.now();
2880
- }
2881
- accumulatedContent += delta;
2882
- if (currentTextBlock) {
2883
- currentTextBlock.text += delta;
2884
- }
2885
- }
2886
- }
2887
- // Handle tool input delta events
2888
- if (chunk.type === 'content_block_delta' && chunk.delta?.type === 'input_json_delta') {
2889
- const block = chunk.index !== undefined ? contentBlocks[chunk.index] : undefined;
2890
- const toolId = block?.type === 'function' ? block.id : undefined;
2891
- if (toolId && toolsInProgress.has(toolId)) {
2892
- const tool = toolsInProgress.get(toolId);
2893
- if (tool) {
2894
- tool.inputString += chunk.delta.partial_json || '';
2895
- }
2896
- }
2897
- }
2898
- // Handle content block stop events
2899
- if (chunk.type === 'content_block_stop') {
2900
- currentTextBlock = null;
2901
- // Parse accumulated tool input
2902
- if (chunk.index !== undefined) {
2903
- const block = contentBlocks[chunk.index];
2904
- if (block?.type === 'function' && block.id && toolsInProgress.has(block.id)) {
2905
- const tool = toolsInProgress.get(block.id);
2906
- if (tool) {
2907
- try {
2908
- block.function.arguments = JSON.parse(tool.inputString);
2909
- } catch (e) {
2910
- // Keep empty object if parsing fails
2911
- console.error('Error parsing tool input:', e);
2912
- }
2913
- }
2914
- toolsInProgress.delete(block.id);
2915
- }
2916
- }
2917
- }
2918
- if (chunk.type == 'message_start') {
2919
- lastRawUsage = chunk.message.usage;
2920
- usage.inputTokens = chunk.message.usage.input_tokens ?? 0;
2921
- usage.cacheCreationInputTokens = chunk.message.usage.cache_creation_input_tokens ?? 0;
2922
- usage.cacheReadInputTokens = chunk.message.usage.cache_read_input_tokens ?? 0;
2923
- usage.webSearchCount = chunk.message.usage.server_tool_use?.web_search_requests ?? 0;
2924
- }
2925
- if ('usage' in chunk) {
2926
- lastRawUsage = chunk.usage;
2927
- usage.outputTokens = chunk.usage.output_tokens ?? 0;
2928
- // Update web search count if present in delta
2929
- if (chunk.usage.server_tool_use?.web_search_requests !== undefined) {
2930
- usage.webSearchCount = chunk.usage.server_tool_use.web_search_requests;
2931
- }
2932
- }
2933
- if (chunk.type === 'message_delta' && 'delta' in chunk) {
2934
- const delta = chunk.delta;
2935
- if ('stop_reason' in delta && typeof delta.stop_reason === 'string' && delta.stop_reason) {
2936
- stopReason = delta.stop_reason;
2937
- }
2938
- }
2939
- }
2940
- usage.rawUsage = lastRawUsage;
2941
- const latency = (Date.now() - startTime) / 1000;
2942
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
2943
- const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
2944
- // Format output to match non-streaming version
2945
- const formattedOutput = contentBlocks.length > 0 ? [{
2946
- role: 'assistant',
2947
- content: contentBlocks
2948
- }] : [{
2949
- role: 'assistant',
2950
- content: [{
2951
- type: 'text',
2952
- text: accumulatedContent
2953
- }]
2954
- }];
2955
- await captureAiGeneration(this.phClient, {
2956
- ...posthogParams,
2957
- model: anthropicParams.model,
2958
- provider: 'anthropic',
2959
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, 'anthropic')),
2960
- output: formattedOutput,
2961
- latency,
2962
- timeToFirstToken,
2963
- baseURL: this.baseURL,
2964
- modelParameters: getModelParams(body),
2965
- httpStatus: 200,
2966
- usage,
2967
- stopReason,
2968
- tools: availableTools
2969
- });
2970
- } catch (error) {
2971
- await captureAiGeneration(this.phClient, {
2972
- ...posthogParams,
2973
- model: anthropicParams.model,
2974
- provider: 'anthropic',
2975
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams)),
2976
- output: [],
2977
- latency: 0,
2978
- baseURL: this.baseURL,
2979
- modelParameters: getModelParams(body),
2980
- usage: {
2981
- inputTokens: 0,
2982
- outputTokens: 0
2983
- },
2984
- error: error
2985
- });
2986
- throw error;
2987
- }
2988
- })();
2989
- // Return the other stream to the user
2990
- return stream2;
2991
- }
2992
- return value;
2993
- });
2994
- } else {
2995
- const wrappedPromise = parentPromise.then(async result => {
2996
- if ('content' in result) {
2997
- const latency = (Date.now() - startTime) / 1000;
2998
- const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
2999
- await captureAiGeneration(this.phClient, {
3000
- ...posthogParams,
3001
- model: anthropicParams.model,
3002
- provider: 'anthropic',
3003
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams)),
3004
- output: formatResponseAnthropic(result),
3005
- latency,
3006
- baseURL: this.baseURL,
3007
- modelParameters: getModelParams(body),
3008
- httpStatus: 200,
3009
- usage: {
3010
- inputTokens: result.usage.input_tokens ?? 0,
3011
- outputTokens: result.usage.output_tokens ?? 0,
3012
- cacheCreationInputTokens: result.usage.cache_creation_input_tokens ?? 0,
3013
- cacheReadInputTokens: result.usage.cache_read_input_tokens ?? 0,
3014
- webSearchCount: result.usage.server_tool_use?.web_search_requests ?? 0,
3015
- rawUsage: result.usage
3016
- },
3017
- stopReason: result.stop_reason ?? undefined,
3018
- tools: availableTools
3019
- });
3020
- }
3021
- return result;
3022
- }, async error => {
3023
- await captureAiGeneration(this.phClient, {
3024
- ...posthogParams,
3025
- model: anthropicParams.model,
3026
- provider: 'anthropic',
3027
- input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams)),
3028
- output: [],
3029
- latency: 0,
3030
- baseURL: this.baseURL,
3031
- modelParameters: getModelParams(body),
3032
- httpStatus: error?.status ? error.status : 500,
3033
- usage: {
3034
- inputTokens: 0,
3035
- outputTokens: 0
3036
- },
3037
- error: error
3038
- });
3039
- throw error;
3040
- });
3041
- return wrappedPromise;
3042
- }
3043
- }
1188
+ function normalizeHost(value) {
1189
+ const normalizedHost = typeof value === 'string' ? value.trim() : '';
1190
+ return (normalizedHost || DEFAULT_PROMPTS_HOST).replace(/\/+$/, '');
3044
1191
  }
3045
-
3046
- class PostHogGoogleGenAI {
3047
- constructor(config) {
3048
- const {
3049
- posthog,
3050
- ...geminiConfig
3051
- } = config;
3052
- this.phClient = posthog;
3053
- this.client = new GoogleGenAI(geminiConfig);
3054
- this.models = new WrappedModels(this.client, this.phClient);
3055
- }
3056
- }
3057
- class WrappedModels {
3058
- constructor(client, phClient) {
3059
- this.client = client;
3060
- this.phClient = phClient;
3061
- }
3062
- async generateContent(params) {
3063
- const {
3064
- providerParams: geminiParams,
3065
- posthogParams
3066
- } = extractPosthogParams(params);
3067
- const startTime = Date.now();
3068
- try {
3069
- const response = await this.client.models.generateContent(geminiParams);
3070
- const latency = (Date.now() - startTime) / 1000;
3071
- const availableTools = extractAvailableToolCalls('gemini', geminiParams);
3072
- const metadata = response.usageMetadata;
3073
- const finishReason = response.candidates?.[0]?.finishReason;
3074
- await captureAiGeneration(this.phClient, {
3075
- ...posthogParams,
3076
- model: geminiParams.model,
3077
- provider: 'gemini',
3078
- input: this.formatInputForPostHog(geminiParams),
3079
- output: formatResponseGemini(response),
3080
- latency,
3081
- baseURL: 'https://generativelanguage.googleapis.com',
3082
- modelParameters: getModelParams(params),
3083
- httpStatus: 200,
3084
- usage: {
3085
- inputTokens: metadata?.promptTokenCount ?? 0,
3086
- outputTokens: metadata?.candidatesTokenCount ?? 0,
3087
- reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
3088
- cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0,
3089
- webSearchCount: calculateGoogleWebSearchCount(response),
3090
- rawUsage: metadata
3091
- },
3092
- stopReason: finishReason ?? undefined,
3093
- tools: availableTools
3094
- });
3095
- return response;
3096
- } catch (error) {
3097
- const latency = (Date.now() - startTime) / 1000;
3098
- await captureAiGeneration(this.phClient, {
3099
- ...posthogParams,
3100
- model: geminiParams.model,
3101
- provider: 'gemini',
3102
- input: this.formatInputForPostHog(geminiParams),
3103
- output: [],
3104
- latency,
3105
- baseURL: 'https://generativelanguage.googleapis.com',
3106
- modelParameters: getModelParams(params),
3107
- usage: {
3108
- inputTokens: 0,
3109
- outputTokens: 0
3110
- },
3111
- error
3112
- });
3113
- throw error;
3114
- }
3115
- }
3116
- async *generateContentStream(params) {
3117
- const {
3118
- providerParams: geminiParams,
3119
- posthogParams
3120
- } = extractPosthogParams(params);
3121
- const startTime = Date.now();
3122
- const accumulatedContent = [];
3123
- let firstTokenTime;
3124
- let stopReason;
3125
- let usage = {
3126
- inputTokens: 0,
3127
- outputTokens: 0,
3128
- webSearchCount: 0,
3129
- rawUsage: undefined
3130
- };
3131
- try {
3132
- const stream = await this.client.models.generateContentStream(geminiParams);
3133
- for await (const chunk of stream) {
3134
- // Track first token time when we get text content
3135
- if (firstTokenTime === undefined && chunk.text) {
3136
- firstTokenTime = Date.now();
3137
- }
3138
- const chunkWebSearchCount = calculateGoogleWebSearchCount(chunk);
3139
- if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
3140
- usage.webSearchCount = chunkWebSearchCount;
3141
- }
3142
- // Handle text content
3143
- if (chunk.text) {
3144
- // Find if we already have a text item to append to
3145
- let lastTextItem;
3146
- for (let i = accumulatedContent.length - 1; i >= 0; i--) {
3147
- if (accumulatedContent[i].type === 'text') {
3148
- lastTextItem = accumulatedContent[i];
3149
- break;
3150
- }
3151
- }
3152
- if (lastTextItem && lastTextItem.type === 'text') {
3153
- lastTextItem.text += chunk.text;
3154
- } else {
3155
- accumulatedContent.push({
3156
- type: 'text',
3157
- text: chunk.text
3158
- });
3159
- }
3160
- }
3161
- // Track finish reason from candidates
3162
- if (chunk.candidates?.[0]?.finishReason) {
3163
- stopReason = chunk.candidates[0].finishReason;
3164
- }
3165
- // Handle function calls from candidates
3166
- if (chunk.candidates && Array.isArray(chunk.candidates)) {
3167
- for (const candidate of chunk.candidates) {
3168
- if (candidate.content && candidate.content.parts) {
3169
- for (const part of candidate.content.parts) {
3170
- // Type-safe check for functionCall
3171
- if ('functionCall' in part) {
3172
- if (firstTokenTime === undefined) {
3173
- firstTokenTime = Date.now();
3174
- }
3175
- const funcCall = part.functionCall;
3176
- if (funcCall?.name) {
3177
- accumulatedContent.push({
3178
- type: 'function',
3179
- function: {
3180
- name: funcCall.name,
3181
- arguments: funcCall.args || {}
3182
- }
3183
- });
3184
- }
3185
- }
3186
- }
3187
- }
3188
- }
3189
- }
3190
- // Update usage metadata - handle both old and new field names
3191
- if (chunk.usageMetadata) {
3192
- const metadata = chunk.usageMetadata;
3193
- usage = {
3194
- inputTokens: metadata.promptTokenCount ?? 0,
3195
- outputTokens: metadata.candidatesTokenCount ?? 0,
3196
- reasoningTokens: metadata.thoughtsTokenCount ?? 0,
3197
- cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0,
3198
- webSearchCount: usage.webSearchCount,
3199
- rawUsage: metadata
3200
- };
3201
- }
3202
- yield chunk;
3203
- }
3204
- const latency = (Date.now() - startTime) / 1000;
3205
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
3206
- const availableTools = extractAvailableToolCalls('gemini', geminiParams);
3207
- // Format output similar to formatResponseGemini
3208
- const output = accumulatedContent.length > 0 ? [{
3209
- role: 'assistant',
3210
- content: accumulatedContent
3211
- }] : [];
3212
- await captureAiGeneration(this.phClient, {
3213
- ...posthogParams,
3214
- model: geminiParams.model,
3215
- provider: 'gemini',
3216
- input: this.formatInputForPostHog(geminiParams),
3217
- output,
3218
- latency,
3219
- timeToFirstToken,
3220
- baseURL: 'https://generativelanguage.googleapis.com',
3221
- modelParameters: getModelParams(params),
3222
- httpStatus: 200,
3223
- usage: {
3224
- ...usage,
3225
- webSearchCount: usage.webSearchCount,
3226
- rawUsage: usage.rawUsage
3227
- },
3228
- stopReason,
3229
- tools: availableTools
3230
- });
3231
- } catch (error) {
3232
- const latency = (Date.now() - startTime) / 1000;
3233
- await captureAiGeneration(this.phClient, {
3234
- ...posthogParams,
3235
- model: geminiParams.model,
3236
- provider: 'gemini',
3237
- input: this.formatInputForPostHog(geminiParams),
3238
- output: [],
3239
- latency,
3240
- baseURL: 'https://generativelanguage.googleapis.com',
3241
- modelParameters: getModelParams(params),
3242
- usage: {
3243
- inputTokens: 0,
3244
- outputTokens: 0
3245
- },
3246
- error
3247
- });
3248
- throw error;
3249
- }
3250
- }
3251
- async embedContent(params) {
3252
- const {
3253
- providerParams: geminiParams,
3254
- posthogParams
3255
- } = extractPosthogParams(params);
3256
- const startTime = Date.now();
3257
- try {
3258
- const response = await this.client.models.embedContent(geminiParams);
3259
- const latency = (Date.now() - startTime) / 1000;
3260
- const inputTokens = extractEmbeddingTokenCount(response);
3261
- await captureAiGeneration(this.phClient, {
3262
- ...posthogParams,
3263
- eventType: AIEvent.Embedding,
3264
- model: geminiParams.model,
3265
- provider: 'gemini',
3266
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
3267
- output: null,
3268
- latency,
3269
- baseURL: 'https://generativelanguage.googleapis.com',
3270
- modelParameters: getModelParams(params),
3271
- httpStatus: 200,
3272
- usage: {
3273
- inputTokens
3274
- }
3275
- });
3276
- return response;
3277
- } catch (error) {
3278
- const latency = (Date.now() - startTime) / 1000;
3279
- await captureAiGeneration(this.phClient, {
3280
- ...posthogParams,
3281
- eventType: AIEvent.Embedding,
3282
- model: geminiParams.model,
3283
- provider: 'gemini',
3284
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
3285
- output: null,
3286
- latency,
3287
- baseURL: 'https://generativelanguage.googleapis.com',
3288
- modelParameters: getModelParams(params),
3289
- usage: {
3290
- inputTokens: 0
3291
- },
3292
- error
3293
- });
3294
- throw error;
3295
- }
3296
- }
3297
- formatPartsAsContentBlocks(parts) {
3298
- const blocks = [];
3299
- for (const part of parts) {
3300
- // Handle dict/object with text field
3301
- if (part && typeof part === 'object' && 'text' in part && part.text) {
3302
- blocks.push({
3303
- type: 'text',
3304
- text: String(part.text)
3305
- });
3306
- }
3307
- // Handle string parts
3308
- else if (typeof part === 'string') {
3309
- blocks.push({
3310
- type: 'text',
3311
- text: part
3312
- });
3313
- }
3314
- // Handle inlineData (images, audio, PDFs)
3315
- else if (part && typeof part === 'object' && 'inlineData' in part) {
3316
- const inlineData = part.inlineData;
3317
- const mimeType = inlineData.mimeType || inlineData.mime_type || 'application/octet-stream';
3318
- blocks.push(buildInlineDataBlock(mimeType, inlineData.data));
3319
- }
3320
- }
3321
- return blocks;
3322
- }
3323
- formatInput(contents) {
3324
- if (typeof contents === 'string') {
3325
- return [{
3326
- role: 'user',
3327
- content: contents
3328
- }];
3329
- }
3330
- if (Array.isArray(contents)) {
3331
- return contents.map(item => {
3332
- if (typeof item === 'string') {
3333
- return {
3334
- role: 'user',
3335
- content: item
3336
- };
3337
- }
3338
- if (item && typeof item === 'object') {
3339
- const obj = item;
3340
- if ('text' in obj && obj.text) {
3341
- return {
3342
- role: isString(obj.role) ? obj.role : 'user',
3343
- content: obj.text
3344
- };
3345
- }
3346
- if ('content' in obj && obj.content) {
3347
- // If content is a list, format it as content blocks
3348
- if (Array.isArray(obj.content)) {
3349
- const contentBlocks = this.formatPartsAsContentBlocks(obj.content);
3350
- return {
3351
- role: isString(obj.role) ? obj.role : 'user',
3352
- content: contentBlocks
3353
- };
3354
- }
3355
- return {
3356
- role: isString(obj.role) ? obj.role : 'user',
3357
- content: obj.content
3358
- };
3359
- }
3360
- if ('parts' in obj && Array.isArray(obj.parts)) {
3361
- const contentBlocks = this.formatPartsAsContentBlocks(obj.parts);
3362
- return {
3363
- role: isString(obj.role) ? obj.role : 'user',
3364
- content: contentBlocks
3365
- };
3366
- }
3367
- }
3368
- return {
3369
- role: 'user',
3370
- content: toContentString(item)
3371
- };
3372
- });
3373
- }
3374
- if (contents && typeof contents === 'object') {
3375
- const obj = contents;
3376
- if ('text' in obj && obj.text) {
3377
- return [{
3378
- role: 'user',
3379
- content: obj.text
3380
- }];
3381
- }
3382
- if ('content' in obj && obj.content) {
3383
- return [{
3384
- role: 'user',
3385
- content: obj.content
3386
- }];
3387
- }
3388
- }
3389
- return [{
3390
- role: 'user',
3391
- content: toContentString(contents)
3392
- }];
3393
- }
3394
- extractSystemInstruction(params) {
3395
- if (!params || typeof params !== 'object' || !params.config) {
3396
- return null;
3397
- }
3398
- const config = params.config;
3399
- if (!('systemInstruction' in config)) {
3400
- return null;
3401
- }
3402
- const systemInstruction = config.systemInstruction;
3403
- if (typeof systemInstruction === 'string') {
3404
- return systemInstruction;
3405
- }
3406
- if (systemInstruction && typeof systemInstruction === 'object' && 'text' in systemInstruction) {
3407
- return systemInstruction.text;
3408
- }
3409
- if (systemInstruction && typeof systemInstruction === 'object' && 'parts' in systemInstruction && Array.isArray(systemInstruction.parts)) {
3410
- for (const part of systemInstruction.parts) {
3411
- if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
3412
- return part.text;
3413
- }
3414
- }
3415
- }
3416
- if (Array.isArray(systemInstruction)) {
3417
- for (const part of systemInstruction) {
3418
- if (typeof part === 'string') {
3419
- return part;
3420
- }
3421
- if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
3422
- return part.text;
3423
- }
3424
- }
3425
- }
3426
- return null;
3427
- }
3428
- formatInputForPostHog(params) {
3429
- const sanitized = sanitizeGemini(params.contents);
3430
- const messages = this.formatInput(sanitized);
3431
- const systemInstruction = this.extractSystemInstruction(params);
3432
- if (systemInstruction) {
3433
- const hasSystemMessage = messages.some(msg => msg.role === 'system');
3434
- if (!hasSystemMessage) {
3435
- return [{
3436
- role: 'system',
3437
- content: systemInstruction
3438
- }, ...messages];
3439
- }
3440
- }
3441
- return messages;
3442
- }
3443
- }
3444
- /**
3445
- * Extract total token count from a Gemini embed_content response.
3446
- * Token counts are only available per-embedding via Vertex AI's statistics.tokenCount.
3447
- * Returns 0 if no token counts are available.
3448
- */
3449
- function extractEmbeddingTokenCount(response) {
3450
- let total = 0;
3451
- if (response.embeddings) {
3452
- for (const embedding of response.embeddings) {
3453
- if (embedding.statistics?.tokenCount != null) {
3454
- total += embedding.statistics.tokenCount;
3455
- }
3456
- }
3457
- }
3458
- return total;
3459
- }
3460
- /**
3461
- * Detect if Google Search grounding was used in the response.
3462
- * Gemini bills per request that uses grounding, not per individual query.
3463
- * Returns 1 if grounding was used, 0 otherwise.
3464
- */
3465
- function calculateGoogleWebSearchCount(response) {
3466
- if (!response || typeof response !== 'object' || !('candidates' in response)) {
3467
- return 0;
3468
- }
3469
- const candidates = response.candidates;
3470
- if (!Array.isArray(candidates)) {
3471
- return 0;
3472
- }
3473
- const hasGrounding = candidates.some(candidate => {
3474
- if (!candidate || typeof candidate !== 'object') {
3475
- return false;
3476
- }
3477
- // Check for grounding metadata
3478
- if ('groundingMetadata' in candidate && candidate.groundingMetadata) {
3479
- const metadata = candidate.groundingMetadata;
3480
- if (typeof metadata === 'object') {
3481
- // Check if web_search_queries exists and is non-empty
3482
- if ('webSearchQueries' in metadata && Array.isArray(metadata.webSearchQueries) && metadata.webSearchQueries.length > 0) {
3483
- return true;
3484
- }
3485
- // Check if grounding_chunks exists and is non-empty
3486
- if ('groundingChunks' in metadata && Array.isArray(metadata.groundingChunks) && metadata.groundingChunks.length > 0) {
3487
- return true;
3488
- }
3489
- }
3490
- }
3491
- // Check for google search in function calls
3492
- if ('content' in candidate && candidate.content && typeof candidate.content === 'object') {
3493
- const content = candidate.content;
3494
- if ('parts' in content && Array.isArray(content.parts)) {
3495
- return content.parts.some(part => {
3496
- if (!part || typeof part !== 'object' || !('functionCall' in part)) {
3497
- return false;
3498
- }
3499
- const functionCall = part.functionCall;
3500
- if (functionCall && typeof functionCall === 'object' && 'name' in functionCall && typeof functionCall.name === 'string') {
3501
- return functionCall.name.includes('google_search') || functionCall.name.includes('grounding');
3502
- }
3503
- return false;
3504
- });
3505
- }
3506
- }
3507
- return false;
3508
- });
3509
- return hasGrounding ? 1 : 0;
3510
- }
3511
-
3512
- function getDefaultExportFromCjs (x) {
3513
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3514
- }
3515
-
3516
- var decamelize;
3517
- var hasRequiredDecamelize;
3518
-
3519
- function requireDecamelize () {
3520
- if (hasRequiredDecamelize) return decamelize;
3521
- hasRequiredDecamelize = 1;
3522
- decamelize = function (str, sep) {
3523
- if (typeof str !== 'string') {
3524
- throw new TypeError('Expected a string');
3525
- }
3526
-
3527
- sep = typeof sep === 'undefined' ? '_' : sep;
3528
-
3529
- return str
3530
- .replace(/([a-z\d])([A-Z])/g, '$1' + sep + '$2')
3531
- .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + sep + '$2')
3532
- .toLowerCase();
3533
- };
3534
- return decamelize;
3535
- }
3536
-
3537
- var decamelizeExports = requireDecamelize();
3538
- var snakeCase = /*@__PURE__*/getDefaultExportFromCjs(decamelizeExports);
3539
-
3540
- var camelcase = {exports: {}};
3541
-
3542
- var hasRequiredCamelcase;
3543
-
3544
- function requireCamelcase () {
3545
- if (hasRequiredCamelcase) return camelcase.exports;
3546
- hasRequiredCamelcase = 1;
3547
-
3548
- const UPPERCASE = /[\p{Lu}]/u;
3549
- const LOWERCASE = /[\p{Ll}]/u;
3550
- const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
3551
- const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
3552
- const SEPARATORS = /[_.\- ]+/;
3553
-
3554
- const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
3555
- const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
3556
- const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
3557
-
3558
- const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
3559
- let isLastCharLower = false;
3560
- let isLastCharUpper = false;
3561
- let isLastLastCharUpper = false;
3562
-
3563
- for (let i = 0; i < string.length; i++) {
3564
- const character = string[i];
3565
-
3566
- if (isLastCharLower && UPPERCASE.test(character)) {
3567
- string = string.slice(0, i) + '-' + string.slice(i);
3568
- isLastCharLower = false;
3569
- isLastLastCharUpper = isLastCharUpper;
3570
- isLastCharUpper = true;
3571
- i++;
3572
- } else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character)) {
3573
- string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
3574
- isLastLastCharUpper = isLastCharUpper;
3575
- isLastCharUpper = false;
3576
- isLastCharLower = true;
3577
- } else {
3578
- isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
3579
- isLastLastCharUpper = isLastCharUpper;
3580
- isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
3581
- }
3582
- }
3583
-
3584
- return string;
3585
- };
3586
-
3587
- const preserveConsecutiveUppercase = (input, toLowerCase) => {
3588
- LEADING_CAPITAL.lastIndex = 0;
3589
-
3590
- return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
3591
- };
3592
-
3593
- const postProcess = (input, toUpperCase) => {
3594
- SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
3595
- NUMBERS_AND_IDENTIFIER.lastIndex = 0;
3596
-
3597
- return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier))
3598
- .replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
3599
- };
3600
-
3601
- const camelCase = (input, options) => {
3602
- if (!(typeof input === 'string' || Array.isArray(input))) {
3603
- throw new TypeError('Expected the input to be `string | string[]`');
3604
- }
3605
-
3606
- options = {
3607
- pascalCase: false,
3608
- preserveConsecutiveUppercase: false,
3609
- ...options
3610
- };
3611
-
3612
- if (Array.isArray(input)) {
3613
- input = input.map(x => x.trim())
3614
- .filter(x => x.length)
3615
- .join('-');
3616
- } else {
3617
- input = input.trim();
3618
- }
3619
-
3620
- if (input.length === 0) {
3621
- return '';
3622
- }
3623
-
3624
- const toLowerCase = options.locale === false ?
3625
- string => string.toLowerCase() :
3626
- string => string.toLocaleLowerCase(options.locale);
3627
- const toUpperCase = options.locale === false ?
3628
- string => string.toUpperCase() :
3629
- string => string.toLocaleUpperCase(options.locale);
3630
-
3631
- if (input.length === 1) {
3632
- return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
3633
- }
3634
-
3635
- const hasUpperCase = input !== toLowerCase(input);
3636
-
3637
- if (hasUpperCase) {
3638
- input = preserveCamelCase(input, toLowerCase, toUpperCase);
3639
- }
3640
-
3641
- input = input.replace(LEADING_SEPARATORS, '');
3642
-
3643
- if (options.preserveConsecutiveUppercase) {
3644
- input = preserveConsecutiveUppercase(input, toLowerCase);
3645
- } else {
3646
- input = toLowerCase(input);
3647
- }
3648
-
3649
- if (options.pascalCase) {
3650
- input = toUpperCase(input.charAt(0)) + input.slice(1);
3651
- }
3652
-
3653
- return postProcess(input, toUpperCase);
3654
- };
3655
-
3656
- camelcase.exports = camelCase;
3657
- // TODO: Remove this for the next major release
3658
- camelcase.exports.default = camelCase;
3659
- return camelcase.exports;
3660
- }
3661
-
3662
- requireCamelcase();
3663
-
3664
- //#region src/load/map_keys.ts
3665
- function keyToJson(key, map) {
3666
- return map?.[key] || snakeCase(key);
3667
- }
3668
- function mapKeys(fields, mapper, map) {
3669
- const mapped = {};
3670
- for (const key in fields) if (Object.hasOwn(fields, key)) mapped[mapper(key, map)] = fields[key];
3671
- return mapped;
3672
- }
3673
-
3674
- //#region src/load/validation.ts
3675
- /**
3676
- * Sentinel key used to mark escaped user objects during serialization.
3677
- *
3678
- * When a plain object contains 'lc' key (which could be confused with LC objects),
3679
- * we wrap it as `{"__lc_escaped__": {...original...}}`.
3680
- */
3681
- const LC_ESCAPED_KEY = "__lc_escaped__";
3682
- /**
3683
- * Check if an object needs escaping to prevent confusion with LC objects.
3684
- *
3685
- * An object needs escaping if:
3686
- * 1. It has an `'lc'` key (could be confused with LC serialization format)
3687
- * 2. It has only the escape key (would be mistaken for an escaped object)
3688
- */
3689
- function needsEscaping(obj) {
3690
- return "lc" in obj || Object.keys(obj).length === 1 && LC_ESCAPED_KEY in obj;
3691
- }
3692
- /**
3693
- * Wrap an object in the escape marker.
3694
- *
3695
- * @example
3696
- * ```typescript
3697
- * {"key": "value"} // becomes {"__lc_escaped__": {"key": "value"}}
3698
- * ```
3699
- */
3700
- function escapeObject(obj) {
3701
- return { [LC_ESCAPED_KEY]: obj };
3702
- }
3703
- /**
3704
- * Check if an object looks like a Serializable instance (duck typing).
3705
- */
3706
- function isSerializableLike(obj) {
3707
- return obj !== null && typeof obj === "object" && "lc_serializable" in obj && typeof obj.toJSON === "function";
3708
- }
3709
- /**
3710
- * Create a "not_implemented" serialization result for objects that cannot be serialized.
3711
- */
3712
- function createNotImplemented(obj) {
3713
- let id;
3714
- if (obj !== null && typeof obj === "object") if ("lc_id" in obj && Array.isArray(obj.lc_id)) id = obj.lc_id;
3715
- else id = [obj.constructor?.name ?? "Object"];
3716
- else id = [typeof obj];
3717
- return {
3718
- lc: 1,
3719
- type: "not_implemented",
3720
- id
3721
- };
3722
- }
3723
- /**
3724
- * Escape a value if it needs escaping (contains `lc` key).
3725
- *
3726
- * This is a simpler version of `serializeValue` that doesn't handle Serializable
3727
- * objects - it's meant to be called on kwargs values that have already been
3728
- * processed by `toJSON()`.
3729
- *
3730
- * @param value - The value to potentially escape.
3731
- * @param pathSet - WeakSet to track ancestor objects in the current path to detect circular references.
3732
- * Objects are removed after processing to allow shared references (same object in
3733
- * multiple places) while still detecting true circular references (ancestor in descendant).
3734
- * @returns The value with any `lc`-containing objects wrapped in escape markers.
3735
- */
3736
- function escapeIfNeeded(value, pathSet = /* @__PURE__ */ new WeakSet()) {
3737
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
3738
- if (pathSet.has(value)) return createNotImplemented(value);
3739
- if (isSerializableLike(value)) return value;
3740
- pathSet.add(value);
3741
- const record = value;
3742
- if (needsEscaping(record)) {
3743
- pathSet.delete(value);
3744
- return escapeObject(record);
3745
- }
3746
- const result = {};
3747
- for (const [key, val] of Object.entries(record)) result[key] = escapeIfNeeded(val, pathSet);
3748
- pathSet.delete(value);
3749
- return result;
3750
- }
3751
- if (Array.isArray(value)) return value.map((item) => escapeIfNeeded(item, pathSet));
3752
- return value;
3753
- }
3754
-
3755
- function shallowCopy(obj) {
3756
- return Array.isArray(obj) ? [...obj] : { ...obj };
3757
- }
3758
- function replaceSecrets(root, secretsMap) {
3759
- const result = shallowCopy(root);
3760
- for (const [path, secretId] of Object.entries(secretsMap)) {
3761
- const [last, ...partsReverse] = path.split(".").reverse();
3762
- let current = result;
3763
- for (const part of partsReverse.reverse()) {
3764
- if (current[part] === void 0) break;
3765
- current[part] = shallowCopy(current[part]);
3766
- current = current[part];
3767
- }
3768
- if (current[last] !== void 0) current[last] = {
3769
- lc: 1,
3770
- type: "secret",
3771
- id: [secretId]
3772
- };
3773
- }
3774
- return result;
3775
- }
3776
- /**
3777
- * Get a unique name for the module, rather than parent class implementations.
3778
- * Should not be subclassed, subclass lc_name above instead.
3779
- */
3780
- function get_lc_unique_name(serializableClass) {
3781
- const parentClass = Object.getPrototypeOf(serializableClass);
3782
- if (typeof serializableClass.lc_name === "function" && (typeof parentClass.lc_name !== "function" || serializableClass.lc_name() !== parentClass.lc_name())) return serializableClass.lc_name();
3783
- else return serializableClass.name;
3784
- }
3785
- var Serializable = class Serializable {
3786
- lc_serializable = false;
3787
- lc_kwargs;
3788
- /**
3789
- * The name of the serializable. Override to provide an alias or
3790
- * to preserve the serialized module name in minified environments.
3791
- *
3792
- * Implemented as a static method to support loading logic.
3793
- */
3794
- static lc_name() {
3795
- return this.name;
3796
- }
3797
- /**
3798
- * The final serialized identifier for the module.
3799
- */
3800
- get lc_id() {
3801
- return [...this.lc_namespace, get_lc_unique_name(this.constructor)];
3802
- }
3803
- /**
3804
- * A map of secrets, which will be omitted from serialization.
3805
- * Keys are paths to the secret in constructor args, e.g. "foo.bar.baz".
3806
- * Values are the secret ids, which will be used when deserializing.
3807
- */
3808
- get lc_secrets() {}
3809
- /**
3810
- * A map of additional attributes to merge with constructor args.
3811
- * Keys are the attribute names, e.g. "foo".
3812
- * Values are the attribute values, which will be serialized.
3813
- * These attributes need to be accepted by the constructor as arguments.
3814
- */
3815
- get lc_attributes() {}
3816
- /**
3817
- * A map of aliases for constructor args.
3818
- * Keys are the attribute names, e.g. "foo".
3819
- * Values are the alias that will replace the key in serialization.
3820
- * This is used to eg. make argument names match Python.
3821
- */
3822
- get lc_aliases() {}
3823
- /**
3824
- * A manual list of keys that should be serialized.
3825
- * If not overridden, all fields passed into the constructor will be serialized.
3826
- */
3827
- get lc_serializable_keys() {}
3828
- constructor(kwargs, ..._args) {
3829
- if (this.lc_serializable_keys !== void 0) this.lc_kwargs = Object.fromEntries(Object.entries(kwargs || {}).filter(([key]) => this.lc_serializable_keys?.includes(key)));
3830
- else this.lc_kwargs = kwargs ?? {};
3831
- }
3832
- toJSON() {
3833
- if (!this.lc_serializable) return this.toJSONNotImplemented();
3834
- if (this.lc_kwargs instanceof Serializable || typeof this.lc_kwargs !== "object" || Array.isArray(this.lc_kwargs)) return this.toJSONNotImplemented();
3835
- const aliases = {};
3836
- const secrets = {};
3837
- const kwargs = Object.keys(this.lc_kwargs).reduce((acc, key) => {
3838
- acc[key] = key in this ? this[key] : this.lc_kwargs[key];
3839
- return acc;
3840
- }, {});
3841
- for (let current = Object.getPrototypeOf(this); current; current = Object.getPrototypeOf(current)) {
3842
- Object.assign(aliases, Reflect.get(current, "lc_aliases", this));
3843
- Object.assign(secrets, Reflect.get(current, "lc_secrets", this));
3844
- Object.assign(kwargs, Reflect.get(current, "lc_attributes", this));
3845
- }
3846
- Object.keys(secrets).forEach((keyPath) => {
3847
- let read = this;
3848
- let write = kwargs;
3849
- const [last, ...partsReverse] = keyPath.split(".").reverse();
3850
- for (const key of partsReverse.reverse()) {
3851
- if (!(key in read) || read[key] === void 0) return;
3852
- if (!(key in write) || write[key] === void 0) {
3853
- if (typeof read[key] === "object" && read[key] != null) write[key] = {};
3854
- else if (Array.isArray(read[key])) write[key] = [];
3855
- }
3856
- read = read[key];
3857
- write = write[key];
3858
- }
3859
- if (last in read && read[last] !== void 0) write[last] = write[last] || read[last];
3860
- });
3861
- const escapedKwargs = {};
3862
- const pathSet = /* @__PURE__ */ new WeakSet();
3863
- pathSet.add(this);
3864
- for (const [key, value] of Object.entries(kwargs)) escapedKwargs[key] = escapeIfNeeded(value, pathSet);
3865
- const processedKwargs = mapKeys(Object.keys(secrets).length ? replaceSecrets(escapedKwargs, secrets) : escapedKwargs, keyToJson, aliases);
3866
- return {
3867
- lc: 1,
3868
- type: "constructor",
3869
- id: this.lc_id,
3870
- kwargs: processedKwargs
3871
- };
3872
- }
3873
- toJSONNotImplemented() {
3874
- return {
3875
- lc: 1,
3876
- type: "not_implemented",
3877
- id: this.lc_id
3878
- };
3879
- }
3880
- };
3881
-
3882
- const isDeno = () => typeof Deno !== "undefined";
3883
- function getEnvironmentVariable(name) {
3884
- try {
3885
- if (typeof process !== "undefined") return process.env?.[name];
3886
- else if (isDeno()) return Deno?.env.get(name);
3887
- else return;
3888
- } catch {
3889
- return;
3890
- }
3891
- }
3892
-
3893
- /**
3894
- * Abstract class that provides a set of optional methods that can be
3895
- * overridden in derived classes to handle various events during the
3896
- * execution of a LangChain application.
3897
- */
3898
- var BaseCallbackHandlerMethodsClass = class {};
3899
- /**
3900
- * Abstract base class for creating callback handlers in the LangChain
3901
- * framework. It provides a set of optional methods that can be overridden
3902
- * in derived classes to handle various events during the execution of a
3903
- * LangChain application.
3904
- */
3905
- var BaseCallbackHandler = class extends BaseCallbackHandlerMethodsClass {
3906
- lc_serializable = false;
3907
- get lc_namespace() {
3908
- return [
3909
- "langchain_core",
3910
- "callbacks",
3911
- this.name
3912
- ];
3913
- }
3914
- get lc_secrets() {}
3915
- get lc_attributes() {}
3916
- get lc_aliases() {}
3917
- get lc_serializable_keys() {}
3918
- /**
3919
- * The name of the serializable. Override to provide an alias or
3920
- * to preserve the serialized module name in minified environments.
3921
- *
3922
- * Implemented as a static method to support loading logic.
3923
- */
3924
- static lc_name() {
3925
- return this.name;
3926
- }
3927
- /**
3928
- * The final serialized identifier for the module.
3929
- */
3930
- get lc_id() {
3931
- return [...this.lc_namespace, get_lc_unique_name(this.constructor)];
3932
- }
3933
- lc_kwargs;
3934
- ignoreLLM = false;
3935
- ignoreChain = false;
3936
- ignoreAgent = false;
3937
- ignoreRetriever = false;
3938
- ignoreCustomEvent = false;
3939
- raiseError = false;
3940
- awaitHandlers = getEnvironmentVariable("LANGCHAIN_CALLBACKS_BACKGROUND") === "false";
3941
- constructor(input) {
3942
- super();
3943
- this.lc_kwargs = input || {};
3944
- if (input) {
3945
- this.ignoreLLM = input.ignoreLLM ?? this.ignoreLLM;
3946
- this.ignoreChain = input.ignoreChain ?? this.ignoreChain;
3947
- this.ignoreAgent = input.ignoreAgent ?? this.ignoreAgent;
3948
- this.ignoreRetriever = input.ignoreRetriever ?? this.ignoreRetriever;
3949
- this.ignoreCustomEvent = input.ignoreCustomEvent ?? this.ignoreCustomEvent;
3950
- this.raiseError = input.raiseError ?? this.raiseError;
3951
- this.awaitHandlers = this.raiseError || (input._awaitHandler ?? this.awaitHandlers);
3952
- }
3953
- }
3954
- copy() {
3955
- return new this.constructor(this);
3956
- }
3957
- toJSON() {
3958
- return Serializable.prototype.toJSON.call(this);
3959
- }
3960
- toJSONNotImplemented() {
3961
- return Serializable.prototype.toJSONNotImplemented.call(this);
3962
- }
3963
- static fromMethods(methods) {
3964
- class Handler extends BaseCallbackHandler {
3965
- name = uuid.v7();
3966
- constructor() {
3967
- super();
3968
- Object.assign(this, methods);
3969
- }
3970
- }
3971
- return new Handler();
3972
- }
3973
- };
3974
-
3975
- class LangChainCallbackHandler extends BaseCallbackHandler {
3976
- constructor(options) {
3977
- if (!options.client) {
3978
- throw new Error('PostHog client is required');
3979
- }
3980
- super();
3981
- this.name = 'PosthogCallbackHandler';
3982
- this.runs = {};
3983
- this.parentTree = {};
3984
- this.client = options.client;
3985
- this.distinctId = options.distinctId;
3986
- this.traceId = options.traceId;
3987
- this.properties = options.properties || {};
3988
- this.privacyMode = options.privacyMode || false;
3989
- this.groups = options.groups || {};
3990
- this.debug = options.debug || false;
3991
- }
3992
- // ===== CALLBACK METHODS =====
3993
- handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, runName) {
3994
- this._logDebugEvent('on_chain_start', runId, parentRunId, {
3995
- inputs,
3996
- tags
3997
- });
3998
- this._setParentOfRun(runId, parentRunId);
3999
- this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName);
4000
- }
4001
- handleChainEnd(outputs, runId, parentRunId, tags, _kwargs) {
4002
- this._logDebugEvent('on_chain_end', runId, parentRunId, {
4003
- outputs,
4004
- tags
4005
- });
4006
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs);
4007
- }
4008
- handleChainError(error, runId, parentRunId, tags, _kwargs) {
4009
- this._logDebugEvent('on_chain_error', runId, parentRunId, {
4010
- error,
4011
- tags
4012
- });
4013
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, error);
4014
- }
4015
- handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata, runName) {
4016
- this._logDebugEvent('on_chat_model_start', runId, parentRunId, {
4017
- messages,
4018
- tags
4019
- });
4020
- this._setParentOfRun(runId, parentRunId);
4021
- // Flatten the two-dimensional messages and convert each message to a plain object
4022
- const input = messages.flat().map(m => this._convertMessageToDict(m));
4023
- this._setLLMMetadata(serialized, runId, input, metadata, extraParams, runName);
4024
- }
4025
- handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata, runName) {
4026
- this._logDebugEvent('on_llm_start', runId, parentRunId, {
4027
- prompts,
4028
- tags
4029
- });
4030
- this._setParentOfRun(runId, parentRunId);
4031
- this._setLLMMetadata(serialized, runId, prompts, metadata, extraParams, runName);
4032
- }
4033
- handleLLMEnd(output, runId, parentRunId, tags, _extraParams) {
4034
- this._logDebugEvent('on_llm_end', runId, parentRunId, {
4035
- output,
4036
- tags
4037
- });
4038
- this._popRunAndCaptureGeneration(runId, parentRunId, output);
4039
- }
4040
- handleLLMError(err, runId, parentRunId, tags, _extraParams) {
4041
- this._logDebugEvent('on_llm_error', runId, parentRunId, {
4042
- err,
4043
- tags
4044
- });
4045
- this._popRunAndCaptureGeneration(runId, parentRunId, err);
4046
- }
4047
- handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
4048
- this._logDebugEvent('on_tool_start', runId, parentRunId, {
4049
- input,
4050
- tags
4051
- });
4052
- this._setParentOfRun(runId, parentRunId);
4053
- this._setTraceOrSpanMetadata(tool, input, runId, parentRunId, metadata, tags, runName);
4054
- }
4055
- handleToolEnd(output, runId, parentRunId, tags) {
4056
- this._logDebugEvent('on_tool_end', runId, parentRunId, {
4057
- output,
4058
- tags
4059
- });
4060
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, output);
4061
- }
4062
- handleToolError(err, runId, parentRunId, tags) {
4063
- this._logDebugEvent('on_tool_error', runId, parentRunId, {
4064
- err,
4065
- tags
4066
- });
4067
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, err);
4068
- }
4069
- handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
4070
- this._logDebugEvent('on_retriever_start', runId, parentRunId, {
4071
- query,
4072
- tags
4073
- });
4074
- this._setParentOfRun(runId, parentRunId);
4075
- this._setTraceOrSpanMetadata(retriever, query, runId, parentRunId, metadata, tags, name);
4076
- }
4077
- handleRetrieverEnd(documents, runId, parentRunId, tags) {
4078
- this._logDebugEvent('on_retriever_end', runId, parentRunId, {
4079
- documents,
4080
- tags
4081
- });
4082
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, documents);
4083
- }
4084
- handleRetrieverError(err, runId, parentRunId, tags) {
4085
- this._logDebugEvent('on_retriever_error', runId, parentRunId, {
4086
- err,
4087
- tags
4088
- });
4089
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, err);
4090
- }
4091
- handleAgentAction(action, runId, parentRunId, tags) {
4092
- this._logDebugEvent('on_agent_action', runId, parentRunId, {
4093
- action,
4094
- tags
4095
- });
4096
- this._setParentOfRun(runId, parentRunId);
4097
- this._setTraceOrSpanMetadata(null, action, runId, parentRunId);
4098
- }
4099
- handleAgentEnd(action, runId, parentRunId, tags) {
4100
- this._logDebugEvent('on_agent_finish', runId, parentRunId, {
4101
- action,
4102
- tags
4103
- });
4104
- this._popRunAndCaptureTraceOrSpan(runId, parentRunId, action);
4105
- }
4106
- // ===== PRIVATE HELPERS =====
4107
- _setParentOfRun(runId, parentRunId) {
4108
- if (parentRunId) {
4109
- this.parentTree[runId] = parentRunId;
4110
- }
4111
- }
4112
- _popParentOfRun(runId) {
4113
- delete this.parentTree[runId];
4114
- }
4115
- _findRootRun(runId) {
4116
- let id = runId;
4117
- while (this.parentTree[id]) {
4118
- id = this.parentTree[id];
4119
- }
4120
- return id;
4121
- }
4122
- _setTraceOrSpanMetadata(serialized, input, runId, parentRunId, ...args) {
4123
- // Use default names if not provided: if this is a top-level run, we mark it as a trace, otherwise as a span.
4124
- const defaultName = parentRunId ? 'span' : 'trace';
4125
- const runName = this._getLangchainRunName(serialized, ...args) || defaultName;
4126
- this.runs[runId] = {
4127
- name: runName,
4128
- input,
4129
- startTime: Date.now()
4130
- };
4131
- }
4132
- _setLLMMetadata(serialized, runId, messages, metadata, extraParams, runName) {
4133
- const runNameFound = this._getLangchainRunName(serialized, {
4134
- extraParams,
4135
- runName
4136
- }) || 'generation';
4137
- const generation = {
4138
- name: runNameFound,
4139
- input: sanitizeLangChain(messages),
4140
- startTime: Date.now()
4141
- };
4142
- if (extraParams) {
4143
- generation.modelParams = getModelParams(extraParams.invocation_params);
4144
- if (extraParams.invocation_params && extraParams.invocation_params.tools) {
4145
- generation.tools = extraParams.invocation_params.tools;
4146
- }
4147
- }
4148
- if (metadata) {
4149
- if (metadata.ls_model_name) {
4150
- generation.model = metadata.ls_model_name;
4151
- }
4152
- if (metadata.ls_provider) {
4153
- generation.provider = metadata.ls_provider;
4154
- }
4155
- }
4156
- if (serialized && 'kwargs' in serialized && serialized.kwargs.openai_api_base) {
4157
- generation.baseUrl = serialized.kwargs.openai_api_base;
4158
- }
4159
- this.runs[runId] = generation;
4160
- }
4161
- _popRunMetadata(runId) {
4162
- const endTime = Date.now();
4163
- const run = this.runs[runId];
4164
- if (!run) {
4165
- console.warn(`No run metadata found for run ${runId}`);
4166
- return undefined;
4167
- }
4168
- run.endTime = endTime;
4169
- delete this.runs[runId];
4170
- return run;
4171
- }
4172
- _getTraceId(runId) {
4173
- return this.traceId ? String(this.traceId) : this._findRootRun(runId);
4174
- }
4175
- _getParentRunId(traceId, _runId, parentRunId) {
4176
- // Replace the parent-run if not found in our stored parent tree.
4177
- if (parentRunId && !this.parentTree[parentRunId]) {
4178
- return traceId;
4179
- }
4180
- return parentRunId;
4181
- }
4182
- _popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs) {
4183
- const traceId = this._getTraceId(runId);
4184
- this._popParentOfRun(runId);
4185
- const run = this._popRunMetadata(runId);
4186
- if (!run) {
4187
- return;
4188
- }
4189
- if ('modelParams' in run) {
4190
- console.warn(`Run ${runId} is a generation, but attempted to be captured as a trace/span.`);
4191
- return;
4192
- }
4193
- const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
4194
- this._captureTraceOrSpan(traceId, runId, run, outputs, actualParentRunId);
4195
- }
4196
- _captureTraceOrSpan(traceId, runId, run, outputs, parentRunId) {
4197
- const eventName = parentRunId ? '$ai_span' : '$ai_trace';
4198
- const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
4199
- const eventProperties = {
4200
- $ai_lib: 'posthog-ai',
4201
- $ai_lib_version: version,
4202
- $ai_trace_id: traceId,
4203
- $ai_input_state: withPrivacyMode(this.client, this.privacyMode, run.input),
4204
- $ai_latency: latency,
4205
- $ai_span_name: run.name,
4206
- $ai_span_id: runId,
4207
- $ai_framework: 'langchain'
4208
- };
4209
- if (parentRunId) {
4210
- eventProperties['$ai_parent_id'] = parentRunId;
4211
- }
4212
- Object.assign(eventProperties, this.properties);
4213
- if (!this.distinctId) {
4214
- eventProperties['$process_person_profile'] = false;
4215
- }
4216
- if (outputs instanceof Error) {
4217
- eventProperties['$ai_error'] = stringifyError(outputs);
4218
- eventProperties['$ai_is_error'] = true;
4219
- } else if (outputs !== undefined) {
4220
- eventProperties['$ai_output_state'] = withPrivacyMode(this.client, this.privacyMode, outputs);
4221
- }
4222
- this.client.capture({
4223
- distinctId: this.distinctId ? this.distinctId.toString() : runId,
4224
- event: eventName,
4225
- properties: eventProperties,
4226
- groups: this.groups
4227
- });
4228
- }
4229
- _popRunAndCaptureGeneration(runId, parentRunId, response) {
4230
- const traceId = this._getTraceId(runId);
4231
- this._popParentOfRun(runId);
4232
- const run = this._popRunMetadata(runId);
4233
- if (!run || typeof run !== 'object' || !('modelParams' in run)) {
4234
- console.warn(`Run ${runId} is not a generation, but attempted to be captured as such.`);
4235
- return;
4236
- }
4237
- const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
4238
- this._captureGeneration(traceId, runId, run, response, actualParentRunId);
4239
- }
4240
- _captureGeneration(traceId, runId, run, output, parentRunId) {
4241
- const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
4242
- const eventProperties = {
4243
- $ai_lib: 'posthog-ai',
4244
- $ai_lib_version: version,
4245
- $ai_trace_id: traceId,
4246
- $ai_span_id: runId,
4247
- $ai_span_name: run.name,
4248
- $ai_parent_id: parentRunId,
4249
- $ai_provider: run.provider,
4250
- $ai_model: run.model,
4251
- $ai_model_parameters: run.modelParams,
4252
- $ai_input: withPrivacyMode(this.client, this.privacyMode, run.input),
4253
- $ai_http_status: 200,
4254
- $ai_latency: latency,
4255
- $ai_base_url: run.baseUrl,
4256
- $ai_framework: 'langchain'
4257
- };
4258
- if (run.tools) {
4259
- eventProperties['$ai_tools'] = run.tools;
4260
- }
4261
- if (output instanceof Error) {
4262
- eventProperties['$ai_http_status'] = output.status || 500;
4263
- eventProperties['$ai_error'] = stringifyError(output);
4264
- eventProperties['$ai_is_error'] = true;
4265
- } else {
4266
- // Handle token usage
4267
- const [inputTokens, outputTokens, additionalTokenData] = this.parseUsage(output, run.provider, run.model);
4268
- eventProperties['$ai_input_tokens'] = inputTokens;
4269
- eventProperties['$ai_output_tokens'] = outputTokens;
4270
- // Add additional token data to properties
4271
- if (additionalTokenData.cacheReadInputTokens) {
4272
- eventProperties['$ai_cache_read_input_tokens'] = additionalTokenData.cacheReadInputTokens;
4273
- }
4274
- if (additionalTokenData.cacheWriteInputTokens) {
4275
- eventProperties['$ai_cache_creation_input_tokens'] = additionalTokenData.cacheWriteInputTokens;
4276
- }
4277
- if (additionalTokenData.reasoningTokens) {
4278
- eventProperties['$ai_reasoning_tokens'] = additionalTokenData.reasoningTokens;
4279
- }
4280
- if (additionalTokenData.webSearchCount !== undefined) {
4281
- eventProperties['$ai_web_search_count'] = additionalTokenData.webSearchCount;
4282
- }
4283
- // Extract stop reason from generation info
4284
- const stopReason = this._extractStopReason(output);
4285
- if (stopReason) {
4286
- eventProperties['$ai_stop_reason'] = stopReason;
4287
- }
4288
- // Handle generations/completions
4289
- let completions;
4290
- if (output.generations && Array.isArray(output.generations)) {
4291
- const lastGeneration = output.generations[output.generations.length - 1];
4292
- if (Array.isArray(lastGeneration) && lastGeneration.length > 0) {
4293
- // Check if this is a ChatGeneration by looking at the first item
4294
- const isChatGeneration = 'message' in lastGeneration[0] && lastGeneration[0].message;
4295
- if (isChatGeneration) {
4296
- // For ChatGeneration, convert messages to dict format
4297
- completions = lastGeneration.map(gen => {
4298
- return this._convertMessageToDict(gen.message);
4299
- });
4300
- } else {
4301
- // For non-ChatGeneration, extract raw response
4302
- completions = lastGeneration.map(gen => {
4303
- return this._extractRawResponse(gen);
4304
- });
4305
- }
4306
- }
4307
- }
4308
- if (completions) {
4309
- eventProperties['$ai_output_choices'] = withPrivacyMode(this.client, this.privacyMode, completions);
4310
- }
4311
- }
4312
- Object.assign(eventProperties, this.properties);
4313
- if (!this.distinctId) {
4314
- eventProperties['$process_person_profile'] = false;
4315
- }
4316
- this.client.capture({
4317
- distinctId: this.distinctId ? this.distinctId.toString() : traceId,
4318
- event: '$ai_generation',
4319
- properties: eventProperties,
4320
- groups: this.groups
4321
- });
4322
- }
4323
- _logDebugEvent(eventName, runId, parentRunId, extra) {
4324
- if (this.debug) {
4325
- console.log(`Event: ${eventName}, runId: ${runId}, parentRunId: ${parentRunId}, extra:`, extra);
4326
- }
4327
- }
4328
- _getLangchainRunName(serialized, ...args) {
4329
- if (args && args.length > 0) {
4330
- for (const arg of args) {
4331
- if (arg && typeof arg === 'object' && 'name' in arg) {
4332
- return arg.name;
4333
- } else if (arg && typeof arg === 'object' && 'runName' in arg) {
4334
- return arg.runName;
4335
- }
4336
- }
4337
- }
4338
- if (serialized && serialized.name) {
4339
- return serialized.name;
4340
- }
4341
- if (serialized && serialized.id) {
4342
- return Array.isArray(serialized.id) ? serialized.id[serialized.id.length - 1] : serialized.id;
4343
- }
4344
- return undefined;
4345
- }
4346
- _convertLcToolCallsToOai(toolCalls) {
4347
- return toolCalls.map(toolCall => ({
4348
- type: 'function',
4349
- id: toolCall.id,
4350
- function: {
4351
- name: toolCall.name,
4352
- arguments: JSON.stringify(toolCall.args)
4353
- }
4354
- }));
4355
- }
4356
- _extractRawResponse(generation) {
4357
- // Extract the response from the last response of the LLM call
4358
- // We return the text of the response if not empty
4359
- if (generation.text != null && generation.text.trim() !== '') {
4360
- return generation.text.trim();
4361
- } else if (generation.message) {
4362
- // Additional kwargs contains the response in case of tool usage
4363
- return generation.message.additional_kwargs || generation.message.additionalKwargs || {};
4364
- } else {
4365
- // Not tool usage, some LLM responses can be simply empty
4366
- return '';
4367
- }
4368
- }
4369
- _convertMessageToDict(message) {
4370
- let messageDict = {};
4371
- const messageType = message.getType();
4372
- switch (messageType) {
4373
- case 'human':
4374
- messageDict = {
4375
- role: 'user',
4376
- content: message.content
4377
- };
4378
- break;
4379
- case 'ai':
4380
- messageDict = {
4381
- role: 'assistant',
4382
- content: message.content
4383
- };
4384
- if (message.tool_calls) {
4385
- messageDict.tool_calls = this._convertLcToolCallsToOai(message.tool_calls);
4386
- }
4387
- break;
4388
- case 'system':
4389
- messageDict = {
4390
- role: 'system',
4391
- content: message.content
4392
- };
4393
- break;
4394
- case 'tool':
4395
- messageDict = {
4396
- role: 'tool',
4397
- content: message.content
4398
- };
4399
- break;
4400
- case 'function':
4401
- messageDict = {
4402
- role: 'function',
4403
- content: message.content
4404
- };
4405
- break;
4406
- default:
4407
- messageDict = {
4408
- role: messageType,
4409
- content: toContentString(message.content)
4410
- };
4411
- break;
4412
- }
4413
- if (message.additional_kwargs) {
4414
- messageDict = {
4415
- ...messageDict,
4416
- ...message.additional_kwargs
4417
- };
4418
- }
4419
- // Sanitize the message content to redact base64 images
4420
- return sanitizeLangChain(messageDict);
4421
- }
4422
- _extractStopReason(output) {
4423
- if (!output.generations || !Array.isArray(output.generations)) {
4424
- return undefined;
4425
- }
4426
- const lastGeneration = output.generations[output.generations.length - 1];
4427
- if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) {
4428
- return undefined;
4429
- }
4430
- const gen = lastGeneration[0];
4431
- // Check generationInfo for finish_reason (OpenAI format)
4432
- if (gen.generationInfo?.finish_reason) {
4433
- return String(gen.generationInfo.finish_reason);
4434
- }
4435
- // Check generationInfo for response_metadata.stop_reason (Anthropic format)
4436
- if (gen.generationInfo?.response_metadata?.stop_reason) {
4437
- return String(gen.generationInfo.response_metadata.stop_reason);
4438
- }
4439
- // Check message response_metadata for finish_reason (common LangChain format)
4440
- if (gen.generationInfo?.response_metadata?.finish_reason) {
4441
- return String(gen.generationInfo.response_metadata.finish_reason);
4442
- }
4443
- // Check for stop_reason directly in generationInfo
4444
- if (gen.generationInfo?.stop_reason) {
4445
- return String(gen.generationInfo.stop_reason);
4446
- }
4447
- return undefined;
4448
- }
4449
- _parseUsageModel(usage, provider, model) {
4450
- const conversionList = [['promptTokens', 'input'], ['completionTokens', 'output'], ['input_tokens', 'input'], ['output_tokens', 'output'], ['prompt_token_count', 'input'], ['candidates_token_count', 'output'], ['inputTokenCount', 'input'], ['outputTokenCount', 'output'], ['input_token_count', 'input'], ['generated_token_count', 'output']];
4451
- const parsedUsage = conversionList.reduce((acc, [modelKey, typeKey]) => {
4452
- const value = usage[modelKey];
4453
- if (value != null) {
4454
- const finalCount = Array.isArray(value) ? value.reduce((sum, tokenCount) => sum + tokenCount, 0) : value;
4455
- acc[typeKey] = finalCount;
4456
- }
4457
- return acc;
4458
- }, {
4459
- input: 0,
4460
- output: 0
4461
- });
4462
- // Extract additional token details like cached tokens and reasoning tokens
4463
- const additionalTokenData = {};
4464
- // Check for cached tokens in various formats
4465
- if (usage.prompt_tokens_details?.cached_tokens != null) {
4466
- additionalTokenData.cacheReadInputTokens = usage.prompt_tokens_details.cached_tokens;
4467
- } else if (usage.input_token_details?.cache_read != null) {
4468
- additionalTokenData.cacheReadInputTokens = usage.input_token_details.cache_read;
4469
- } else if (usage.cachedPromptTokens != null) {
4470
- additionalTokenData.cacheReadInputTokens = usage.cachedPromptTokens;
4471
- } else if (usage.cache_read_input_tokens != null) {
4472
- additionalTokenData.cacheReadInputTokens = usage.cache_read_input_tokens;
4473
- }
4474
- // Check for cache write/creation tokens in various formats
4475
- if (usage.cache_creation_input_tokens != null) {
4476
- additionalTokenData.cacheWriteInputTokens = usage.cache_creation_input_tokens;
4477
- } else if (usage.input_token_details?.cache_creation != null) {
4478
- additionalTokenData.cacheWriteInputTokens = usage.input_token_details.cache_creation;
4479
- }
4480
- // Check for reasoning tokens in various formats
4481
- if (usage.completion_tokens_details?.reasoning_tokens != null) {
4482
- additionalTokenData.reasoningTokens = usage.completion_tokens_details.reasoning_tokens;
4483
- } else if (usage.output_token_details?.reasoning != null) {
4484
- additionalTokenData.reasoningTokens = usage.output_token_details.reasoning;
4485
- } else if (usage.reasoningTokens != null) {
4486
- additionalTokenData.reasoningTokens = usage.reasoningTokens;
4487
- }
4488
- // Extract web search counts from various provider formats
4489
- let webSearchCount;
4490
- // Priority 1: Exact Count
4491
- // Check Anthropic format (server_tool_use.web_search_requests)
4492
- if (usage.server_tool_use?.web_search_requests !== undefined) {
4493
- webSearchCount = usage.server_tool_use.web_search_requests;
4494
- }
4495
- // Priority 2: Binary Detection (1 or 0)
4496
- // Check for citations array (Perplexity)
4497
- else if (usage.citations && Array.isArray(usage.citations) && usage.citations.length > 0) {
4498
- webSearchCount = 1;
4499
- }
4500
- // Check for search_results array (Perplexity via OpenRouter)
4501
- else if (usage.search_results && Array.isArray(usage.search_results) && usage.search_results.length > 0) {
4502
- webSearchCount = 1;
4503
- }
4504
- // Check for search_context_size (Perplexity via OpenRouter)
4505
- else if (usage.search_context_size) {
4506
- webSearchCount = 1;
4507
- }
4508
- // Check for annotations with url_citation type
4509
- else if (usage.annotations && Array.isArray(usage.annotations)) {
4510
- const hasUrlCitation = usage.annotations.some(ann => {
4511
- return ann && typeof ann === 'object' && 'type' in ann && ann.type === 'url_citation';
4512
- });
4513
- if (hasUrlCitation) {
4514
- webSearchCount = 1;
4515
- }
4516
- }
4517
- // Check Gemini format (grounding metadata - binary 0 or 1)
4518
- else if (usage.grounding_metadata?.grounding_support !== undefined || usage.grounding_metadata?.web_search_queries !== undefined) {
4519
- webSearchCount = 1;
4520
- }
4521
- if (webSearchCount !== undefined) {
4522
- additionalTokenData.webSearchCount = webSearchCount;
4523
- }
4524
- // For Anthropic providers, LangChain reports input_tokens as the sum of all input tokens.
4525
- // Our cost calculation expects them to be separate for Anthropic, so we subtract cache tokens.
4526
- // Both cache_read and cache_write tokens should be subtracted since Anthropic's raw API
4527
- // reports input_tokens as tokens NOT read from or used to create a cache.
4528
- // For other providers (OpenAI, etc.), input_tokens already excludes cache tokens as expected.
4529
- // Match logic consistent with plugin-server: exact match on provider OR substring match on model
4530
- let isAnthropic = false;
4531
- if (provider && provider.toLowerCase() === 'anthropic') {
4532
- isAnthropic = true;
4533
- } else if (model && model.toLowerCase().includes('anthropic')) {
4534
- isAnthropic = true;
4535
- }
4536
- if (isAnthropic && parsedUsage.input) {
4537
- const cacheTokens = (additionalTokenData.cacheReadInputTokens || 0) + (additionalTokenData.cacheWriteInputTokens || 0);
4538
- if (cacheTokens > 0) {
4539
- parsedUsage.input = Math.max(parsedUsage.input - cacheTokens, 0);
4540
- }
4541
- }
4542
- return [parsedUsage.input, parsedUsage.output, additionalTokenData];
4543
- }
4544
- parseUsage(response, provider, model) {
4545
- let llmUsage = [0, 0, {}];
4546
- const llmUsageKeys = ['token_usage', 'usage', 'tokenUsage'];
4547
- if (response.llmOutput != null) {
4548
- const key = llmUsageKeys.find(k => response.llmOutput?.[k] != null);
4549
- if (key) {
4550
- llmUsage = this._parseUsageModel(response.llmOutput[key], provider, model);
4551
- }
4552
- }
4553
- // If top-level usage info was not found, try checking the generations.
4554
- if (llmUsage[0] === 0 && llmUsage[1] === 0 && response.generations) {
4555
- for (const generation of response.generations) {
4556
- for (const genChunk of generation) {
4557
- // Check other paths for usage information
4558
- if (genChunk.generationInfo?.usage_metadata) {
4559
- llmUsage = this._parseUsageModel(genChunk.generationInfo.usage_metadata, provider, model);
4560
- return llmUsage;
4561
- }
4562
- const messageChunk = genChunk.generationInfo ?? {};
4563
- const responseMetadata = messageChunk.response_metadata ?? {};
4564
- const chunkUsage = responseMetadata['usage'] ?? responseMetadata['amazon-bedrock-invocationMetrics'] ?? messageChunk.usage_metadata;
4565
- if (chunkUsage) {
4566
- llmUsage = this._parseUsageModel(chunkUsage, provider, model);
4567
- return llmUsage;
4568
- }
4569
- }
4570
- }
4571
- }
4572
- return llmUsage;
4573
- }
4574
- }
4575
-
4576
- /// <reference lib="dom" />
4577
- const DEFAULT_CACHE_TTL_SECONDS = 300; // 5 minutes
4578
- const DEFAULT_PROMPTS_HOST = 'https://us.posthog.com';
4579
- function normalizeApiKey(value) {
4580
- return typeof value === 'string' ? value.trim() : '';
4581
- }
4582
- function normalizeHost(value) {
4583
- const normalizedHost = typeof value === 'string' ? value.trim() : '';
4584
- return (normalizedHost || DEFAULT_PROMPTS_HOST).replace(/\/+$/, '');
4585
- }
4586
- function isPromptApiResponse(data) {
4587
- if (typeof data !== 'object' || data === null) {
4588
- return false;
1192
+ function isPromptApiResponse(data) {
1193
+ if (typeof data !== 'object' || data === null) {
1194
+ return false;
4589
1195
  }
4590
1196
  const record = data;
4591
1197
  return typeof record.prompt === 'string' && typeof record.name === 'string' && typeof record.version === 'number';
@@ -4609,18 +1215,18 @@ function isPromptsWithPostHog(options) {
4609
1215
  * })
4610
1216
  *
4611
1217
  * // Fetch with caching and fallback
4612
- * const template = await prompts.get('support-system-prompt', {
1218
+ * const result = await prompts.get('support-system-prompt', {
4613
1219
  * cacheTtlSeconds: 300,
4614
1220
  * fallback: 'You are a helpful assistant.',
4615
1221
  * })
4616
1222
  *
4617
1223
  * // Or fetch an exact published version
4618
- * const v3Template = await prompts.get('support-system-prompt', {
1224
+ * const v3 = await prompts.get('support-system-prompt', {
4619
1225
  * version: 3,
4620
1226
  * })
4621
1227
  *
4622
1228
  * // Compile with variables
4623
- * const systemPrompt = prompts.compile(template, {
1229
+ * const systemPrompt = prompts.compile(result.prompt, {
4624
1230
  * company: 'Acme Corp',
4625
1231
  * tier: 'premium',
4626
1232
  * })
@@ -4629,7 +1235,6 @@ function isPromptsWithPostHog(options) {
4629
1235
  class Prompts {
4630
1236
  constructor(options) {
4631
1237
  this.cache = new Map();
4632
- this.hasWarnedDeprecation = false;
4633
1238
  this.defaultCacheTtlSeconds = options.defaultCacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS;
4634
1239
  if (isPromptsWithPostHog(options)) {
4635
1240
  this.personalApiKey = options.posthog.options.personalApiKey ?? '';
@@ -4657,32 +1262,26 @@ class Prompts {
4657
1262
  getPromptLabel(name, version) {
4658
1263
  return version === undefined ? `"${name}"` : `"${name}" version ${version}`;
4659
1264
  }
1265
+ /**
1266
+ * Fetch a prompt by name from the PostHog API.
1267
+ *
1268
+ * Returns a `PromptResult` object carrying the prompt text alongside `source`,
1269
+ * `name`, and `version` metadata. Read `result.prompt` for the template string.
1270
+ */
4660
1271
  async get(name, options) {
4661
- const withMetadata = options?.withMetadata;
4662
- if (withMetadata === undefined && !this.hasWarnedDeprecation) {
4663
- this.hasWarnedDeprecation = true;
4664
- console.warn('[PostHog Prompts] Calling get() without { withMetadata: true } is deprecated and will be ' + 'removed in a future major version. Pass { withMetadata: true } to receive a PromptResult ' + 'object with source, name, and version metadata. ' + 'You can pass { withMetadata: false } to silence this warning, but the plain-string return ' + 'will still be removed in the next major version.');
4665
- }
4666
1272
  try {
4667
- const result = await this.getInternal(name, options);
4668
- if (withMetadata) {
4669
- return result;
4670
- }
4671
- return result.prompt;
1273
+ return await this.getInternal(name, options);
4672
1274
  } catch (error) {
4673
1275
  const fallback = options?.fallback;
4674
1276
  if (fallback !== undefined) {
4675
1277
  const promptLabel = this.getPromptLabel(name, options?.version);
4676
1278
  console.warn(`[PostHog Prompts] Failed to fetch prompt ${promptLabel}, using fallback:`, error);
4677
- if (withMetadata) {
4678
- return {
4679
- source: 'code_fallback',
4680
- prompt: fallback,
4681
- name: undefined,
4682
- version: undefined
4683
- };
4684
- }
4685
- return fallback;
1279
+ return {
1280
+ source: 'code_fallback',
1281
+ prompt: fallback,
1282
+ name: undefined,
1283
+ version: undefined
1284
+ };
4686
1285
  }
4687
1286
  throw error;
4688
1287
  }
@@ -4820,5 +1419,5 @@ class Prompts {
4820
1419
  }
4821
1420
  }
4822
1421
 
4823
- export { AIEvent, PostHogAnthropic as Anthropic, PostHogAzureOpenAI as AzureOpenAI, PostHogGoogleGenAI as GoogleGenAI, LangChainCallbackHandler, PostHogOpenAI as OpenAI, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };
1422
+ export { AIEvent, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };
4824
1423
  //# sourceMappingURL=index.mjs.map