@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.cjs CHANGED
@@ -29,7 +29,7 @@ function _interopNamespace(e) {
29
29
  var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
30
30
  var AnthropicOriginal__default = /*#__PURE__*/_interopDefault(AnthropicOriginal);
31
31
 
32
- var version = "7.13.2";
32
+ var version = "7.14.0";
33
33
 
34
34
  // Type guards for safer type checking
35
35
  const isString = value => {
@@ -778,6 +778,7 @@ const sendEventToPosthog = async ({
778
778
  usage = {},
779
779
  error,
780
780
  exceptionId,
781
+ stopReason,
781
782
  tools,
782
783
  captureImmediate = false
783
784
  }) => {
@@ -848,6 +849,9 @@ const sendEventToPosthog = async ({
848
849
  ...(distinctId ? {} : {
849
850
  $process_person_profile: false
850
851
  }),
852
+ ...(stopReason ? {
853
+ $ai_stop_reason: stopReason
854
+ } : {}),
851
855
  ...(tools ? {
852
856
  $ai_tools: tools
853
857
  } : {}),
@@ -971,6 +975,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
971
975
  let accumulatedContent = '';
972
976
  let modelFromResponse;
973
977
  let firstTokenTime;
978
+ let stopReason;
974
979
  let usage = {
975
980
  inputTokens: 0,
976
981
  outputTokens: 0,
@@ -985,6 +990,9 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
985
990
  modelFromResponse = chunk.model;
986
991
  }
987
992
  const choice = chunk?.choices?.[0];
993
+ if (choice?.finish_reason) {
994
+ stopReason = choice.finish_reason;
995
+ }
988
996
  const chunkWebSearchCount = calculateWebSearchCount(chunk);
989
997
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
990
998
  usage.webSearchCount = chunkWebSearchCount;
@@ -1096,6 +1104,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1096
1104
  webSearchCount: usage.webSearchCount,
1097
1105
  rawUsage: rawUsageData
1098
1106
  },
1107
+ stopReason,
1099
1108
  tools: availableTools
1100
1109
  });
1101
1110
  } catch (error) {
@@ -1148,6 +1157,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1148
1157
  webSearchCount: calculateWebSearchCount(result),
1149
1158
  rawUsage: result.usage
1150
1159
  },
1160
+ stopReason: result.choices[0]?.finish_reason ?? undefined,
1151
1161
  tools: availableTools
1152
1162
  });
1153
1163
  }
@@ -1200,6 +1210,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1200
1210
  let finalContent = [];
1201
1211
  let modelFromResponse;
1202
1212
  let firstTokenTime;
1213
+ let stopReason;
1203
1214
  let usage = {
1204
1215
  inputTokens: 0,
1205
1216
  outputTokens: 0,
@@ -1223,6 +1234,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1223
1234
  }
1224
1235
  if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1225
1236
  finalContent = chunk.response.output;
1237
+ if (chunk.response.status) {
1238
+ stopReason = chunk.response.status;
1239
+ }
1226
1240
  }
1227
1241
  if ('response' in chunk && chunk.response?.usage) {
1228
1242
  rawUsageData = chunk.response.usage;
@@ -1258,6 +1272,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1258
1272
  webSearchCount: usage.webSearchCount,
1259
1273
  rawUsage: rawUsageData
1260
1274
  },
1275
+ stopReason,
1261
1276
  tools: availableTools
1262
1277
  });
1263
1278
  } catch (error) {
@@ -1311,6 +1326,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1311
1326
  webSearchCount: calculateWebSearchCount(result),
1312
1327
  rawUsage: result.usage
1313
1328
  },
1329
+ stopReason: result.status ?? undefined,
1314
1330
  tools: availableTools
1315
1331
  });
1316
1332
  }
@@ -1370,7 +1386,8 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1370
1386
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1371
1387
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1372
1388
  rawUsage: result.usage
1373
- }
1389
+ },
1390
+ stopReason: result.status ?? undefined
1374
1391
  });
1375
1392
  return result;
1376
1393
  }, async error => {
@@ -2406,6 +2423,9 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2406
2423
  rawUsage: rawUsageData
2407
2424
  };
2408
2425
  adjustAnthropicV3CacheTokens(model, provider, usage);
2426
+ // Extract finish reason - V2 returns a string, V3 returns an object with .unified
2427
+ const rawFinishReason = result.finishReason;
2428
+ const finishReasonStr = typeof rawFinishReason === 'string' ? rawFinishReason : rawFinishReason && typeof rawFinishReason === 'object' && 'unified' in rawFinishReason ? String(rawFinishReason.unified) : undefined;
2409
2429
  await sendEventToPosthog({
2410
2430
  client: phClient,
2411
2431
  distinctId: mergedOptions.posthogDistinctId,
@@ -2419,6 +2439,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2419
2439
  params: mergedParams,
2420
2440
  httpStatus: 200,
2421
2441
  usage,
2442
+ stopReason: finishReasonStr,
2422
2443
  tools: availableTools,
2423
2444
  captureImmediate: mergedOptions.posthogCaptureImmediate
2424
2445
  });
@@ -2457,6 +2478,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2457
2478
  let firstTokenTime;
2458
2479
  let generatedText = '';
2459
2480
  let reasoningText = '';
2481
+ let stopReason;
2460
2482
  let usage = {};
2461
2483
  let providerMetadata = undefined;
2462
2484
  const mergedParams = {
@@ -2533,6 +2555,13 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2533
2555
  cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
2534
2556
  ...additionalTokenValues
2535
2557
  };
2558
+ // Extract finish reason - V2 returns a string, V3 returns an object with .unified
2559
+ const rawFinishReason = chunk.finishReason;
2560
+ if (typeof rawFinishReason === 'string') {
2561
+ stopReason = rawFinishReason;
2562
+ } else if (rawFinishReason && typeof rawFinishReason === 'object' && 'unified' in rawFinishReason) {
2563
+ stopReason = String(rawFinishReason.unified);
2564
+ }
2536
2565
  }
2537
2566
  controller.enqueue(chunk);
2538
2567
  },
@@ -2596,6 +2625,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2596
2625
  params: mergedParams,
2597
2626
  httpStatus: 200,
2598
2627
  usage: finalUsage,
2628
+ stopReason,
2599
2629
  tools: availableTools,
2600
2630
  captureImmediate: mergedOptions.posthogCaptureImmediate
2601
2631
  });
@@ -2667,6 +2697,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
2667
2697
  const toolsInProgress = new Map();
2668
2698
  let currentTextBlock = null;
2669
2699
  let firstTokenTime;
2700
+ let stopReason;
2670
2701
  const usage = {
2671
2702
  inputTokens: 0,
2672
2703
  outputTokens: 0,
@@ -2767,6 +2798,12 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
2767
2798
  usage.webSearchCount = chunk.usage.server_tool_use.web_search_requests;
2768
2799
  }
2769
2800
  }
2801
+ if (chunk.type === 'message_delta' && 'delta' in chunk) {
2802
+ const delta = chunk.delta;
2803
+ if ('stop_reason' in delta && typeof delta.stop_reason === 'string' && delta.stop_reason) {
2804
+ stopReason = delta.stop_reason;
2805
+ }
2806
+ }
2770
2807
  }
2771
2808
  usage.rawUsage = lastRawUsage;
2772
2809
  const latency = (Date.now() - startTime) / 1000;
@@ -2796,6 +2833,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
2796
2833
  params: body,
2797
2834
  httpStatus: 200,
2798
2835
  usage,
2836
+ stopReason,
2799
2837
  tools: availableTools
2800
2838
  });
2801
2839
  } catch (error) {
@@ -2847,6 +2885,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
2847
2885
  webSearchCount: result.usage.server_tool_use?.web_search_requests ?? 0,
2848
2886
  rawUsage: result.usage
2849
2887
  },
2888
+ stopReason: result.stop_reason ?? undefined,
2850
2889
  tools: availableTools
2851
2890
  });
2852
2891
  }
@@ -2903,6 +2942,7 @@ class WrappedModels {
2903
2942
  const latency = (Date.now() - startTime) / 1000;
2904
2943
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
2905
2944
  const metadata = response.usageMetadata;
2945
+ const finishReason = response.candidates?.[0]?.finishReason;
2906
2946
  await sendEventToPosthog({
2907
2947
  client: this.phClient,
2908
2948
  ...posthogParams,
@@ -2922,6 +2962,7 @@ class WrappedModels {
2922
2962
  webSearchCount: calculateGoogleWebSearchCount(response),
2923
2963
  rawUsage: metadata
2924
2964
  },
2965
+ stopReason: finishReason ?? undefined,
2925
2966
  tools: availableTools
2926
2967
  });
2927
2968
  return response;
@@ -2954,6 +2995,7 @@ class WrappedModels {
2954
2995
  const startTime = Date.now();
2955
2996
  const accumulatedContent = [];
2956
2997
  let firstTokenTime;
2998
+ let stopReason;
2957
2999
  let usage = {
2958
3000
  inputTokens: 0,
2959
3001
  outputTokens: 0,
@@ -2990,6 +3032,10 @@ class WrappedModels {
2990
3032
  });
2991
3033
  }
2992
3034
  }
3035
+ // Track finish reason from candidates
3036
+ if (chunk.candidates?.[0]?.finishReason) {
3037
+ stopReason = chunk.candidates[0].finishReason;
3038
+ }
2993
3039
  // Handle function calls from candidates
2994
3040
  if (chunk.candidates && Array.isArray(chunk.candidates)) {
2995
3041
  for (const candidate of chunk.candidates) {
@@ -3054,6 +3100,7 @@ class WrappedModels {
3054
3100
  webSearchCount: usage.webSearchCount,
3055
3101
  rawUsage: usage.rawUsage
3056
3102
  },
3103
+ stopReason,
3057
3104
  tools: availableTools
3058
3105
  });
3059
3106
  } catch (error) {
@@ -3077,6 +3124,54 @@ class WrappedModels {
3077
3124
  throw enrichedError;
3078
3125
  }
3079
3126
  }
3127
+ async embedContent(params) {
3128
+ const {
3129
+ providerParams: geminiParams,
3130
+ posthogParams
3131
+ } = extractPosthogParams(params);
3132
+ const startTime = Date.now();
3133
+ try {
3134
+ const response = await this.client.models.embedContent(geminiParams);
3135
+ const latency = (Date.now() - startTime) / 1000;
3136
+ const inputTokens = extractEmbeddingTokenCount(response);
3137
+ await sendEventToPosthog({
3138
+ client: this.phClient,
3139
+ ...posthogParams,
3140
+ eventType: AIEvent.Embedding,
3141
+ model: geminiParams.model,
3142
+ provider: 'gemini',
3143
+ input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
3144
+ output: null,
3145
+ latency,
3146
+ baseURL: 'https://generativelanguage.googleapis.com',
3147
+ params: params,
3148
+ httpStatus: 200,
3149
+ usage: {
3150
+ inputTokens
3151
+ }
3152
+ });
3153
+ return response;
3154
+ } catch (error) {
3155
+ const latency = (Date.now() - startTime) / 1000;
3156
+ const enrichedError = await sendEventWithErrorToPosthog({
3157
+ client: this.phClient,
3158
+ ...posthogParams,
3159
+ eventType: AIEvent.Embedding,
3160
+ model: geminiParams.model,
3161
+ provider: 'gemini',
3162
+ input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
3163
+ output: null,
3164
+ latency,
3165
+ baseURL: 'https://generativelanguage.googleapis.com',
3166
+ params: params,
3167
+ usage: {
3168
+ inputTokens: 0
3169
+ },
3170
+ error: error
3171
+ });
3172
+ throw enrichedError;
3173
+ }
3174
+ }
3080
3175
  formatPartsAsContentBlocks(parts) {
3081
3176
  const blocks = [];
3082
3177
  for (const part of parts) {
@@ -3231,6 +3326,22 @@ class WrappedModels {
3231
3326
  return messages;
3232
3327
  }
3233
3328
  }
3329
+ /**
3330
+ * Extract total token count from a Gemini embed_content response.
3331
+ * Token counts are only available per-embedding via Vertex AI's statistics.tokenCount.
3332
+ * Returns 0 if no token counts are available.
3333
+ */
3334
+ function extractEmbeddingTokenCount(response) {
3335
+ let total = 0;
3336
+ if (response.embeddings) {
3337
+ for (const embedding of response.embeddings) {
3338
+ if (embedding.statistics?.tokenCount != null) {
3339
+ total += embedding.statistics.tokenCount;
3340
+ }
3341
+ }
3342
+ }
3343
+ return total;
3344
+ }
3234
3345
  /**
3235
3346
  * Detect if Google Search grounding was used in the response.
3236
3347
  * Gemini bills per request that uses grounding, not per individual query.
@@ -4054,6 +4165,11 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
4054
4165
  if (additionalTokenData.webSearchCount !== undefined) {
4055
4166
  eventProperties['$ai_web_search_count'] = additionalTokenData.webSearchCount;
4056
4167
  }
4168
+ // Extract stop reason from generation info
4169
+ const stopReason = this._extractStopReason(output);
4170
+ if (stopReason) {
4171
+ eventProperties['$ai_stop_reason'] = stopReason;
4172
+ }
4057
4173
  // Handle generations/completions
4058
4174
  let completions;
4059
4175
  if (output.generations && Array.isArray(output.generations)) {
@@ -4188,6 +4304,33 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
4188
4304
  // Sanitize the message content to redact base64 images
4189
4305
  return sanitizeLangChain(messageDict);
4190
4306
  }
4307
+ _extractStopReason(output) {
4308
+ if (!output.generations || !Array.isArray(output.generations)) {
4309
+ return undefined;
4310
+ }
4311
+ const lastGeneration = output.generations[output.generations.length - 1];
4312
+ if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) {
4313
+ return undefined;
4314
+ }
4315
+ const gen = lastGeneration[0];
4316
+ // Check generationInfo for finish_reason (OpenAI format)
4317
+ if (gen.generationInfo?.finish_reason) {
4318
+ return String(gen.generationInfo.finish_reason);
4319
+ }
4320
+ // Check generationInfo for response_metadata.stop_reason (Anthropic format)
4321
+ if (gen.generationInfo?.response_metadata?.stop_reason) {
4322
+ return String(gen.generationInfo.response_metadata.stop_reason);
4323
+ }
4324
+ // Check message response_metadata for finish_reason (common LangChain format)
4325
+ if (gen.generationInfo?.response_metadata?.finish_reason) {
4326
+ return String(gen.generationInfo.response_metadata.finish_reason);
4327
+ }
4328
+ // Check for stop_reason directly in generationInfo
4329
+ if (gen.generationInfo?.stop_reason) {
4330
+ return String(gen.generationInfo.stop_reason);
4331
+ }
4332
+ return undefined;
4333
+ }
4191
4334
  _parseUsageModel(usage, provider, model) {
4192
4335
  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']];
4193
4336
  const parsedUsage = conversionList.reduce((acc, [modelKey, typeKey]) => {