@posthog/ai 7.13.2 → 7.14.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.d.ts CHANGED
@@ -6,7 +6,7 @@ import { ResponseCreateParamsWithTools, ExtractParsedContentFromParams } from 'o
6
6
  import { LanguageModelV2, LanguageModelV3 } from '@ai-sdk/provider';
7
7
  import AnthropicOriginal, { APIPromise as APIPromise$1 } from '@anthropic-ai/sdk';
8
8
  import { Stream as Stream$1 } from '@anthropic-ai/sdk/streaming';
9
- import { GoogleGenAI, GenerateContentParameters, GenerateContentResponse, GoogleGenAIOptions } from '@google/genai';
9
+ import { GoogleGenAI, GenerateContentParameters, GenerateContentResponse, EmbedContentParameters, EmbedContentResponse, GoogleGenAIOptions } from '@google/genai';
10
10
  import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
11
11
  import { Serialized } from '@langchain/core/load/serializable';
12
12
  import { ChainValues } from '@langchain/core/utils/types';
@@ -228,6 +228,7 @@ declare class WrappedModels {
228
228
  constructor(client: GoogleGenAI, phClient: PostHog);
229
229
  generateContent(params: GenerateContentParameters & MonitoringParams): Promise<GenerateContentResponse>;
230
230
  generateContentStream(params: GenerateContentParameters & MonitoringParams): AsyncGenerator<GenerateContentResponse, void, unknown>;
231
+ embedContent(params: EmbedContentParameters & MonitoringParams): Promise<EmbedContentResponse>;
231
232
  private formatPartsAsContentBlocks;
232
233
  private formatInput;
233
234
  private extractSystemInstruction;
@@ -290,6 +291,7 @@ declare class LangChainCallbackHandler extends BaseCallbackHandler {
290
291
  private _convertLcToolCallsToOai;
291
292
  private _extractRawResponse;
292
293
  private _convertMessageToDict;
294
+ private _extractStopReason;
293
295
  private _parseUsageModel;
294
296
  private parseUsage;
295
297
  }
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { uuidv7 } from '@posthog/core';
5
5
  import AnthropicOriginal from '@anthropic-ai/sdk';
6
6
  import { GoogleGenAI } from '@google/genai';
7
7
 
8
- var version = "7.13.2";
8
+ var version = "7.14.0";
9
9
 
10
10
  // Type guards for safer type checking
11
11
  const isString = value => {
@@ -754,6 +754,7 @@ const sendEventToPosthog = async ({
754
754
  usage = {},
755
755
  error,
756
756
  exceptionId,
757
+ stopReason,
757
758
  tools,
758
759
  captureImmediate = false
759
760
  }) => {
@@ -824,6 +825,9 @@ const sendEventToPosthog = async ({
824
825
  ...(distinctId ? {} : {
825
826
  $process_person_profile: false
826
827
  }),
828
+ ...(stopReason ? {
829
+ $ai_stop_reason: stopReason
830
+ } : {}),
827
831
  ...(tools ? {
828
832
  $ai_tools: tools
829
833
  } : {}),
@@ -947,6 +951,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
947
951
  let accumulatedContent = '';
948
952
  let modelFromResponse;
949
953
  let firstTokenTime;
954
+ let stopReason;
950
955
  let usage = {
951
956
  inputTokens: 0,
952
957
  outputTokens: 0,
@@ -961,6 +966,9 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
961
966
  modelFromResponse = chunk.model;
962
967
  }
963
968
  const choice = chunk?.choices?.[0];
969
+ if (choice?.finish_reason) {
970
+ stopReason = choice.finish_reason;
971
+ }
964
972
  const chunkWebSearchCount = calculateWebSearchCount(chunk);
965
973
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
966
974
  usage.webSearchCount = chunkWebSearchCount;
@@ -1072,6 +1080,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1072
1080
  webSearchCount: usage.webSearchCount,
1073
1081
  rawUsage: rawUsageData
1074
1082
  },
1083
+ stopReason,
1075
1084
  tools: availableTools
1076
1085
  });
1077
1086
  } catch (error) {
@@ -1124,6 +1133,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1124
1133
  webSearchCount: calculateWebSearchCount(result),
1125
1134
  rawUsage: result.usage
1126
1135
  },
1136
+ stopReason: result.choices[0]?.finish_reason ?? undefined,
1127
1137
  tools: availableTools
1128
1138
  });
1129
1139
  }
@@ -1176,6 +1186,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1176
1186
  let finalContent = [];
1177
1187
  let modelFromResponse;
1178
1188
  let firstTokenTime;
1189
+ let stopReason;
1179
1190
  let usage = {
1180
1191
  inputTokens: 0,
1181
1192
  outputTokens: 0,
@@ -1199,6 +1210,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1199
1210
  }
1200
1211
  if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1201
1212
  finalContent = chunk.response.output;
1213
+ if (chunk.response.status) {
1214
+ stopReason = chunk.response.status;
1215
+ }
1202
1216
  }
1203
1217
  if ('response' in chunk && chunk.response?.usage) {
1204
1218
  rawUsageData = chunk.response.usage;
@@ -1234,6 +1248,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1234
1248
  webSearchCount: usage.webSearchCount,
1235
1249
  rawUsage: rawUsageData
1236
1250
  },
1251
+ stopReason,
1237
1252
  tools: availableTools
1238
1253
  });
1239
1254
  } catch (error) {
@@ -1287,6 +1302,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1287
1302
  webSearchCount: calculateWebSearchCount(result),
1288
1303
  rawUsage: result.usage
1289
1304
  },
1305
+ stopReason: result.status ?? undefined,
1290
1306
  tools: availableTools
1291
1307
  });
1292
1308
  }
@@ -1346,7 +1362,8 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1346
1362
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1347
1363
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1348
1364
  rawUsage: result.usage
1349
- }
1365
+ },
1366
+ stopReason: result.status ?? undefined
1350
1367
  });
1351
1368
  return result;
1352
1369
  }, async error => {
@@ -2382,6 +2399,9 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2382
2399
  rawUsage: rawUsageData
2383
2400
  };
2384
2401
  adjustAnthropicV3CacheTokens(model, provider, usage);
2402
+ // Extract finish reason - V2 returns a string, V3 returns an object with .unified
2403
+ const rawFinishReason = result.finishReason;
2404
+ const finishReasonStr = typeof rawFinishReason === 'string' ? rawFinishReason : rawFinishReason && typeof rawFinishReason === 'object' && 'unified' in rawFinishReason ? String(rawFinishReason.unified) : undefined;
2385
2405
  await sendEventToPosthog({
2386
2406
  client: phClient,
2387
2407
  distinctId: mergedOptions.posthogDistinctId,
@@ -2395,6 +2415,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2395
2415
  params: mergedParams,
2396
2416
  httpStatus: 200,
2397
2417
  usage,
2418
+ stopReason: finishReasonStr,
2398
2419
  tools: availableTools,
2399
2420
  captureImmediate: mergedOptions.posthogCaptureImmediate
2400
2421
  });
@@ -2433,6 +2454,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2433
2454
  let firstTokenTime;
2434
2455
  let generatedText = '';
2435
2456
  let reasoningText = '';
2457
+ let stopReason;
2436
2458
  let usage = {};
2437
2459
  let providerMetadata = undefined;
2438
2460
  const mergedParams = {
@@ -2509,6 +2531,13 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2509
2531
  cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
2510
2532
  ...additionalTokenValues
2511
2533
  };
2534
+ // Extract finish reason - V2 returns a string, V3 returns an object with .unified
2535
+ const rawFinishReason = chunk.finishReason;
2536
+ if (typeof rawFinishReason === 'string') {
2537
+ stopReason = rawFinishReason;
2538
+ } else if (rawFinishReason && typeof rawFinishReason === 'object' && 'unified' in rawFinishReason) {
2539
+ stopReason = String(rawFinishReason.unified);
2540
+ }
2512
2541
  }
2513
2542
  controller.enqueue(chunk);
2514
2543
  },
@@ -2572,6 +2601,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2572
2601
  params: mergedParams,
2573
2602
  httpStatus: 200,
2574
2603
  usage: finalUsage,
2604
+ stopReason,
2575
2605
  tools: availableTools,
2576
2606
  captureImmediate: mergedOptions.posthogCaptureImmediate
2577
2607
  });
@@ -2643,6 +2673,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2643
2673
  const toolsInProgress = new Map();
2644
2674
  let currentTextBlock = null;
2645
2675
  let firstTokenTime;
2676
+ let stopReason;
2646
2677
  const usage = {
2647
2678
  inputTokens: 0,
2648
2679
  outputTokens: 0,
@@ -2743,6 +2774,12 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2743
2774
  usage.webSearchCount = chunk.usage.server_tool_use.web_search_requests;
2744
2775
  }
2745
2776
  }
2777
+ if (chunk.type === 'message_delta' && 'delta' in chunk) {
2778
+ const delta = chunk.delta;
2779
+ if ('stop_reason' in delta && typeof delta.stop_reason === 'string' && delta.stop_reason) {
2780
+ stopReason = delta.stop_reason;
2781
+ }
2782
+ }
2746
2783
  }
2747
2784
  usage.rawUsage = lastRawUsage;
2748
2785
  const latency = (Date.now() - startTime) / 1000;
@@ -2772,6 +2809,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2772
2809
  params: body,
2773
2810
  httpStatus: 200,
2774
2811
  usage,
2812
+ stopReason,
2775
2813
  tools: availableTools
2776
2814
  });
2777
2815
  } catch (error) {
@@ -2823,6 +2861,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2823
2861
  webSearchCount: result.usage.server_tool_use?.web_search_requests ?? 0,
2824
2862
  rawUsage: result.usage
2825
2863
  },
2864
+ stopReason: result.stop_reason ?? undefined,
2826
2865
  tools: availableTools
2827
2866
  });
2828
2867
  }
@@ -2879,6 +2918,7 @@ class WrappedModels {
2879
2918
  const latency = (Date.now() - startTime) / 1000;
2880
2919
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
2881
2920
  const metadata = response.usageMetadata;
2921
+ const finishReason = response.candidates?.[0]?.finishReason;
2882
2922
  await sendEventToPosthog({
2883
2923
  client: this.phClient,
2884
2924
  ...posthogParams,
@@ -2898,6 +2938,7 @@ class WrappedModels {
2898
2938
  webSearchCount: calculateGoogleWebSearchCount(response),
2899
2939
  rawUsage: metadata
2900
2940
  },
2941
+ stopReason: finishReason ?? undefined,
2901
2942
  tools: availableTools
2902
2943
  });
2903
2944
  return response;
@@ -2930,6 +2971,7 @@ class WrappedModels {
2930
2971
  const startTime = Date.now();
2931
2972
  const accumulatedContent = [];
2932
2973
  let firstTokenTime;
2974
+ let stopReason;
2933
2975
  let usage = {
2934
2976
  inputTokens: 0,
2935
2977
  outputTokens: 0,
@@ -2966,6 +3008,10 @@ class WrappedModels {
2966
3008
  });
2967
3009
  }
2968
3010
  }
3011
+ // Track finish reason from candidates
3012
+ if (chunk.candidates?.[0]?.finishReason) {
3013
+ stopReason = chunk.candidates[0].finishReason;
3014
+ }
2969
3015
  // Handle function calls from candidates
2970
3016
  if (chunk.candidates && Array.isArray(chunk.candidates)) {
2971
3017
  for (const candidate of chunk.candidates) {
@@ -3030,6 +3076,7 @@ class WrappedModels {
3030
3076
  webSearchCount: usage.webSearchCount,
3031
3077
  rawUsage: usage.rawUsage
3032
3078
  },
3079
+ stopReason,
3033
3080
  tools: availableTools
3034
3081
  });
3035
3082
  } catch (error) {
@@ -3053,6 +3100,54 @@ class WrappedModels {
3053
3100
  throw enrichedError;
3054
3101
  }
3055
3102
  }
3103
+ async embedContent(params) {
3104
+ const {
3105
+ providerParams: geminiParams,
3106
+ posthogParams
3107
+ } = extractPosthogParams(params);
3108
+ const startTime = Date.now();
3109
+ try {
3110
+ const response = await this.client.models.embedContent(geminiParams);
3111
+ const latency = (Date.now() - startTime) / 1000;
3112
+ const inputTokens = extractEmbeddingTokenCount(response);
3113
+ await sendEventToPosthog({
3114
+ client: this.phClient,
3115
+ ...posthogParams,
3116
+ eventType: AIEvent.Embedding,
3117
+ model: geminiParams.model,
3118
+ provider: 'gemini',
3119
+ input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
3120
+ output: null,
3121
+ latency,
3122
+ baseURL: 'https://generativelanguage.googleapis.com',
3123
+ params: params,
3124
+ httpStatus: 200,
3125
+ usage: {
3126
+ inputTokens
3127
+ }
3128
+ });
3129
+ return response;
3130
+ } catch (error) {
3131
+ const latency = (Date.now() - startTime) / 1000;
3132
+ const enrichedError = await sendEventWithErrorToPosthog({
3133
+ client: this.phClient,
3134
+ ...posthogParams,
3135
+ eventType: AIEvent.Embedding,
3136
+ model: geminiParams.model,
3137
+ provider: 'gemini',
3138
+ input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
3139
+ output: null,
3140
+ latency,
3141
+ baseURL: 'https://generativelanguage.googleapis.com',
3142
+ params: params,
3143
+ usage: {
3144
+ inputTokens: 0
3145
+ },
3146
+ error: error
3147
+ });
3148
+ throw enrichedError;
3149
+ }
3150
+ }
3056
3151
  formatPartsAsContentBlocks(parts) {
3057
3152
  const blocks = [];
3058
3153
  for (const part of parts) {
@@ -3207,6 +3302,22 @@ class WrappedModels {
3207
3302
  return messages;
3208
3303
  }
3209
3304
  }
3305
+ /**
3306
+ * Extract total token count from a Gemini embed_content response.
3307
+ * Token counts are only available per-embedding via Vertex AI's statistics.tokenCount.
3308
+ * Returns 0 if no token counts are available.
3309
+ */
3310
+ function extractEmbeddingTokenCount(response) {
3311
+ let total = 0;
3312
+ if (response.embeddings) {
3313
+ for (const embedding of response.embeddings) {
3314
+ if (embedding.statistics?.tokenCount != null) {
3315
+ total += embedding.statistics.tokenCount;
3316
+ }
3317
+ }
3318
+ }
3319
+ return total;
3320
+ }
3210
3321
  /**
3211
3322
  * Detect if Google Search grounding was used in the response.
3212
3323
  * Gemini bills per request that uses grounding, not per individual query.
@@ -4030,6 +4141,11 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
4030
4141
  if (additionalTokenData.webSearchCount !== undefined) {
4031
4142
  eventProperties['$ai_web_search_count'] = additionalTokenData.webSearchCount;
4032
4143
  }
4144
+ // Extract stop reason from generation info
4145
+ const stopReason = this._extractStopReason(output);
4146
+ if (stopReason) {
4147
+ eventProperties['$ai_stop_reason'] = stopReason;
4148
+ }
4033
4149
  // Handle generations/completions
4034
4150
  let completions;
4035
4151
  if (output.generations && Array.isArray(output.generations)) {
@@ -4164,6 +4280,33 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
4164
4280
  // Sanitize the message content to redact base64 images
4165
4281
  return sanitizeLangChain(messageDict);
4166
4282
  }
4283
+ _extractStopReason(output) {
4284
+ if (!output.generations || !Array.isArray(output.generations)) {
4285
+ return undefined;
4286
+ }
4287
+ const lastGeneration = output.generations[output.generations.length - 1];
4288
+ if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) {
4289
+ return undefined;
4290
+ }
4291
+ const gen = lastGeneration[0];
4292
+ // Check generationInfo for finish_reason (OpenAI format)
4293
+ if (gen.generationInfo?.finish_reason) {
4294
+ return String(gen.generationInfo.finish_reason);
4295
+ }
4296
+ // Check generationInfo for response_metadata.stop_reason (Anthropic format)
4297
+ if (gen.generationInfo?.response_metadata?.stop_reason) {
4298
+ return String(gen.generationInfo.response_metadata.stop_reason);
4299
+ }
4300
+ // Check message response_metadata for finish_reason (common LangChain format)
4301
+ if (gen.generationInfo?.response_metadata?.finish_reason) {
4302
+ return String(gen.generationInfo.response_metadata.finish_reason);
4303
+ }
4304
+ // Check for stop_reason directly in generationInfo
4305
+ if (gen.generationInfo?.stop_reason) {
4306
+ return String(gen.generationInfo.stop_reason);
4307
+ }
4308
+ return undefined;
4309
+ }
4167
4310
  _parseUsageModel(usage, provider, model) {
4168
4311
  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']];
4169
4312
  const parsedUsage = conversionList.reduce((acc, [modelKey, typeKey]) => {