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