@posthog/ai 7.6.1 → 7.8.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
@@ -15,6 +15,26 @@ import { AgentAction, AgentFinish } from '@langchain/core/agents';
15
15
  import { DocumentInterface } from '@langchain/core/documents';
16
16
  import { BaseMessage } from '@langchain/core/messages';
17
17
 
18
+ /**
19
+ * Options for fetching a prompt
20
+ */
21
+ interface GetPromptOptions {
22
+ cacheTtlSeconds?: number;
23
+ fallback?: string;
24
+ }
25
+ /**
26
+ * Variables for prompt compilation
27
+ */
28
+ type PromptVariables = Record<string, string | number | boolean>;
29
+ /**
30
+ * Direct options for initializing Prompts without a PostHog client
31
+ */
32
+ interface PromptsDirectOptions {
33
+ personalApiKey: string;
34
+ host?: string;
35
+ defaultCacheTtlSeconds?: number;
36
+ }
37
+
18
38
  interface MonitoringEventPropertiesWithDefaults {
19
39
  distinctId?: string;
20
40
  traceId: string;
@@ -272,4 +292,71 @@ declare class LangChainCallbackHandler extends BaseCallbackHandler {
272
292
  private parseUsage;
273
293
  }
274
294
 
275
- export { PostHogAnthropic as Anthropic, PostHogAzureOpenAI as AzureOpenAI, PostHogGoogleGenAI as GoogleGenAI, LangChainCallbackHandler, PostHogOpenAI as OpenAI, wrapVercelLanguageModel as withTracing };
295
+ interface PromptsWithPostHogOptions {
296
+ posthog: PostHog;
297
+ defaultCacheTtlSeconds?: number;
298
+ }
299
+ type PromptsOptions = PromptsWithPostHogOptions | PromptsDirectOptions;
300
+ /**
301
+ * Prompts class for fetching and compiling LLM prompts from PostHog
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * // With PostHog client
306
+ * const prompts = new Prompts({ posthog })
307
+ *
308
+ * // Or with direct options (no PostHog client needed)
309
+ * const prompts = new Prompts({
310
+ * personalApiKey: 'phx_xxx',
311
+ * host: 'https://us.i.posthog.com',
312
+ * })
313
+ *
314
+ * // Fetch with caching and fallback
315
+ * const template = await prompts.get('support-system-prompt', {
316
+ * cacheTtlSeconds: 300,
317
+ * fallback: 'You are a helpful assistant.',
318
+ * })
319
+ *
320
+ * // Compile with variables
321
+ * const systemPrompt = prompts.compile(template, {
322
+ * company: 'Acme Corp',
323
+ * tier: 'premium',
324
+ * })
325
+ * ```
326
+ */
327
+ declare class Prompts {
328
+ private personalApiKey;
329
+ private host;
330
+ private defaultCacheTtlSeconds;
331
+ private cache;
332
+ constructor(options: PromptsOptions);
333
+ /**
334
+ * Fetch a prompt by name from the PostHog API
335
+ *
336
+ * @param name - The name of the prompt to fetch
337
+ * @param options - Optional settings for caching and fallback
338
+ * @returns The prompt string
339
+ * @throws Error if the prompt cannot be fetched and no fallback is provided
340
+ */
341
+ get(name: string, options?: GetPromptOptions): Promise<string>;
342
+ /**
343
+ * Compile a prompt template with variable substitution
344
+ *
345
+ * Variables in the format `{{variableName}}` will be replaced with values from the variables object.
346
+ * Unmatched variables are left unchanged.
347
+ *
348
+ * @param prompt - The prompt template string
349
+ * @param variables - Object containing variable values
350
+ * @returns The compiled prompt string
351
+ */
352
+ compile(prompt: string, variables: PromptVariables): string;
353
+ /**
354
+ * Clear the cache for a specific prompt or all prompts
355
+ *
356
+ * @param name - Optional prompt name to clear. If not provided, clears all cached prompts.
357
+ */
358
+ clearCache(name?: string): void;
359
+ private fetchPromptFromApi;
360
+ }
361
+
362
+ export { PostHogAnthropic as Anthropic, PostHogAzureOpenAI as AzureOpenAI, PostHogGoogleGenAI as GoogleGenAI, LangChainCallbackHandler, PostHogOpenAI as OpenAI, Prompts, wrapVercelLanguageModel as withTracing };
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import { uuidv7 } from '@posthog/core';
6
6
  import AnthropicOriginal from '@anthropic-ai/sdk';
7
7
  import { GoogleGenAI } from '@google/genai';
8
8
 
9
- var version = "7.6.1";
9
+ var version = "7.8.0";
10
10
 
11
11
  // Type guards for safer type checking
12
12
  const isString = value => {
@@ -722,6 +722,7 @@ const sendEventToPosthog = async ({
722
722
  input,
723
723
  output,
724
724
  latency,
725
+ timeToFirstToken,
725
726
  baseURL,
726
727
  params,
727
728
  httpStatus = 200,
@@ -788,6 +789,9 @@ const sendEventToPosthog = async ({
788
789
  } : {}),
789
790
  ...additionalTokenValues,
790
791
  $ai_latency: latency,
792
+ ...(timeToFirstToken !== undefined ? {
793
+ $ai_time_to_first_token: timeToFirstToken
794
+ } : {}),
791
795
  $ai_trace_id: traceId,
792
796
  $ai_base_url: baseURL,
793
797
  ...params.posthogProperties,
@@ -859,6 +863,14 @@ function formatOpenAIResponsesInput(input, instructions) {
859
863
  return messages;
860
864
  }
861
865
 
866
+ /**
867
+ * Checks if a ResponseStreamEvent chunk represents the first token/content from the model.
868
+ * This includes various content types like text, reasoning, audio, and refusals.
869
+ */
870
+ function isResponseTokenChunk(chunk) {
871
+ 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';
872
+ }
873
+
862
874
  const Chat = OpenAI.Chat;
863
875
  const Completions = Chat.Completions;
864
876
  const Responses = OpenAI.Responses;
@@ -908,6 +920,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
908
920
  const contentBlocks = [];
909
921
  let accumulatedContent = '';
910
922
  let modelFromResponse;
923
+ let firstTokenTime;
911
924
  let usage = {
912
925
  inputTokens: 0,
913
926
  outputTokens: 0,
@@ -929,11 +942,17 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
929
942
  // Handle text content
930
943
  const deltaContent = choice?.delta?.content;
931
944
  if (deltaContent) {
945
+ if (firstTokenTime === undefined) {
946
+ firstTokenTime = Date.now();
947
+ }
932
948
  accumulatedContent += deltaContent;
933
949
  }
934
950
  // Handle tool calls
935
951
  const deltaToolCalls = choice?.delta?.tool_calls;
936
952
  if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
953
+ if (firstTokenTime === undefined) {
954
+ firstTokenTime = Date.now();
955
+ }
937
956
  for (const toolCall of deltaToolCalls) {
938
957
  const index = toolCall.index;
939
958
  if (index !== undefined) {
@@ -1005,6 +1024,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1005
1024
  }]
1006
1025
  }];
1007
1026
  const latency = (Date.now() - startTime) / 1000;
1027
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1008
1028
  const availableTools = extractAvailableToolCalls('openai', openAIParams);
1009
1029
  await sendEventToPosthog({
1010
1030
  client: this.phClient,
@@ -1014,6 +1034,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1014
1034
  input: sanitizeOpenAI(openAIParams.messages),
1015
1035
  output: formattedOutput,
1016
1036
  latency,
1037
+ timeToFirstToken,
1017
1038
  baseURL: this.baseURL,
1018
1039
  params: body,
1019
1040
  httpStatus: 200,
@@ -1128,6 +1149,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1128
1149
  try {
1129
1150
  let finalContent = [];
1130
1151
  let modelFromResponse;
1152
+ let firstTokenTime;
1131
1153
  let usage = {
1132
1154
  inputTokens: 0,
1133
1155
  outputTokens: 0,
@@ -1135,6 +1157,10 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1135
1157
  };
1136
1158
  let rawUsageData;
1137
1159
  for await (const chunk of stream1) {
1160
+ // Track first token time on content delta events
1161
+ if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
1162
+ firstTokenTime = Date.now();
1163
+ }
1138
1164
  if ('response' in chunk && chunk.response) {
1139
1165
  // Extract model from response object in chunk (for stored prompts)
1140
1166
  if (!modelFromResponse && chunk.response.model) {
@@ -1160,6 +1186,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1160
1186
  }
1161
1187
  }
1162
1188
  const latency = (Date.now() - startTime) / 1000;
1189
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1163
1190
  const availableTools = extractAvailableToolCalls('openai', openAIParams);
1164
1191
  await sendEventToPosthog({
1165
1192
  client: this.phClient,
@@ -1169,6 +1196,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1169
1196
  input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1170
1197
  output: finalContent,
1171
1198
  latency,
1199
+ timeToFirstToken,
1172
1200
  baseURL: this.baseURL,
1173
1201
  params: body,
1174
1202
  httpStatus: 200,
@@ -1407,12 +1435,17 @@ class WrappedTranscriptions extends Transcriptions {
1407
1435
  (async () => {
1408
1436
  try {
1409
1437
  let finalContent = '';
1438
+ let firstTokenTime;
1410
1439
  let usage = {
1411
1440
  inputTokens: 0,
1412
1441
  outputTokens: 0
1413
1442
  };
1414
1443
  const doneEvent = 'transcript.text.done';
1415
1444
  for await (const chunk of stream1) {
1445
+ // Track first token on text delta events
1446
+ if (firstTokenTime === undefined && chunk.type === 'transcript.text.delta') {
1447
+ firstTokenTime = Date.now();
1448
+ }
1416
1449
  if (chunk.type === doneEvent && 'text' in chunk && chunk.text && chunk.text.length > 0) {
1417
1450
  finalContent = chunk.text;
1418
1451
  }
@@ -1425,6 +1458,7 @@ class WrappedTranscriptions extends Transcriptions {
1425
1458
  }
1426
1459
  }
1427
1460
  const latency = (Date.now() - startTime) / 1000;
1461
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1428
1462
  const availableTools = extractAvailableToolCalls('openai', openAIParams);
1429
1463
  await sendEventToPosthog({
1430
1464
  client: this.phClient,
@@ -1434,6 +1468,7 @@ class WrappedTranscriptions extends Transcriptions {
1434
1468
  input: openAIParams.prompt,
1435
1469
  output: finalContent,
1436
1470
  latency,
1471
+ timeToFirstToken,
1437
1472
  baseURL: this.baseURL,
1438
1473
  params: body,
1439
1474
  httpStatus: 200,
@@ -1552,6 +1587,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1552
1587
  const contentBlocks = [];
1553
1588
  let accumulatedContent = '';
1554
1589
  let modelFromResponse;
1590
+ let firstTokenTime;
1555
1591
  let usage = {
1556
1592
  inputTokens: 0,
1557
1593
  outputTokens: 0
@@ -1567,11 +1603,17 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1567
1603
  // Handle text content
1568
1604
  const deltaContent = choice?.delta?.content;
1569
1605
  if (deltaContent) {
1606
+ if (firstTokenTime === undefined) {
1607
+ firstTokenTime = Date.now();
1608
+ }
1570
1609
  accumulatedContent += deltaContent;
1571
1610
  }
1572
1611
  // Handle tool calls
1573
1612
  const deltaToolCalls = choice?.delta?.tool_calls;
1574
1613
  if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
1614
+ if (firstTokenTime === undefined) {
1615
+ firstTokenTime = Date.now();
1616
+ }
1575
1617
  for (const toolCall of deltaToolCalls) {
1576
1618
  const index = toolCall.index;
1577
1619
  if (index !== undefined) {
@@ -1641,6 +1683,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1641
1683
  }]
1642
1684
  }];
1643
1685
  const latency = (Date.now() - startTime) / 1000;
1686
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1644
1687
  await sendEventToPosthog({
1645
1688
  client: this.phClient,
1646
1689
  ...posthogParams,
@@ -1649,6 +1692,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1649
1692
  input: sanitizeOpenAI(openAIParams.messages),
1650
1693
  output: formattedOutput,
1651
1694
  latency,
1695
+ timeToFirstToken,
1652
1696
  baseURL: this.baseURL,
1653
1697
  params: body,
1654
1698
  httpStatus: 200,
@@ -1750,11 +1794,16 @@ class WrappedResponses extends AzureOpenAI.Responses {
1750
1794
  try {
1751
1795
  let finalContent = [];
1752
1796
  let modelFromResponse;
1797
+ let firstTokenTime;
1753
1798
  let usage = {
1754
1799
  inputTokens: 0,
1755
1800
  outputTokens: 0
1756
1801
  };
1757
1802
  for await (const chunk of stream1) {
1803
+ // Track first token time on content delta events
1804
+ if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
1805
+ firstTokenTime = Date.now();
1806
+ }
1758
1807
  if ('response' in chunk && chunk.response) {
1759
1808
  // Extract model from response if not in params (for stored prompts)
1760
1809
  if (!modelFromResponse && chunk.response.model) {
@@ -1774,6 +1823,7 @@ class WrappedResponses extends AzureOpenAI.Responses {
1774
1823
  }
1775
1824
  }
1776
1825
  const latency = (Date.now() - startTime) / 1000;
1826
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1777
1827
  await sendEventToPosthog({
1778
1828
  client: this.phClient,
1779
1829
  ...posthogParams,
@@ -1782,6 +1832,7 @@ class WrappedResponses extends AzureOpenAI.Responses {
1782
1832
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1783
1833
  output: finalContent,
1784
1834
  latency,
1835
+ timeToFirstToken,
1785
1836
  baseURL: this.baseURL,
1786
1837
  params: body,
1787
1838
  httpStatus: 200,
@@ -2351,6 +2402,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2351
2402
  doStream: {
2352
2403
  value: async params => {
2353
2404
  const startTime = Date.now();
2405
+ let firstTokenTime;
2354
2406
  let generatedText = '';
2355
2407
  let reasoningText = '';
2356
2408
  let usage = {};
@@ -2374,13 +2426,22 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2374
2426
  transform(chunk, controller) {
2375
2427
  // Handle streaming patterns - compatible with both V2 and V3
2376
2428
  if (chunk.type === 'text-delta') {
2429
+ if (firstTokenTime === undefined) {
2430
+ firstTokenTime = Date.now();
2431
+ }
2377
2432
  generatedText += chunk.delta;
2378
2433
  }
2379
2434
  if (chunk.type === 'reasoning-delta') {
2435
+ if (firstTokenTime === undefined) {
2436
+ firstTokenTime = Date.now();
2437
+ }
2380
2438
  reasoningText += chunk.delta;
2381
2439
  }
2382
2440
  // Handle tool call chunks
2383
2441
  if (chunk.type === 'tool-input-start') {
2442
+ if (firstTokenTime === undefined) {
2443
+ firstTokenTime = Date.now();
2444
+ }
2384
2445
  // Initialize a new tool call
2385
2446
  toolCallsInProgress.set(chunk.id, {
2386
2447
  toolCallId: chunk.id,
@@ -2399,6 +2460,9 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2399
2460
  // Tool call is complete, keep it in the map for final processing
2400
2461
  }
2401
2462
  if (chunk.type === 'tool-call') {
2463
+ if (firstTokenTime === undefined) {
2464
+ firstTokenTime = Date.now();
2465
+ }
2402
2466
  // Direct tool call chunk (complete tool call)
2403
2467
  toolCallsInProgress.set(chunk.toolCallId, {
2404
2468
  toolCallId: chunk.toolCallId,
@@ -2422,6 +2486,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2422
2486
  },
2423
2487
  flush: async () => {
2424
2488
  const latency = (Date.now() - startTime) / 1000;
2489
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
2425
2490
  // Build content array similar to mapVercelOutput structure
2426
2491
  const content = [];
2427
2492
  if (reasoningText) {
@@ -2474,6 +2539,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
2474
2539
  input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2475
2540
  output: output,
2476
2541
  latency,
2542
+ timeToFirstToken,
2477
2543
  baseURL,
2478
2544
  params: mergedParams,
2479
2545
  httpStatus: 200,
@@ -2548,6 +2614,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2548
2614
  const contentBlocks = [];
2549
2615
  const toolsInProgress = new Map();
2550
2616
  let currentTextBlock = null;
2617
+ let firstTokenTime;
2551
2618
  const usage = {
2552
2619
  inputTokens: 0,
2553
2620
  outputTokens: 0,
@@ -2570,6 +2637,9 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2570
2637
  };
2571
2638
  contentBlocks.push(currentTextBlock);
2572
2639
  } else if (chunk.content_block?.type === 'tool_use') {
2640
+ if (firstTokenTime === undefined) {
2641
+ firstTokenTime = Date.now();
2642
+ }
2573
2643
  const toolBlock = {
2574
2644
  type: 'function',
2575
2645
  id: chunk.content_block.id,
@@ -2590,6 +2660,9 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2590
2660
  if ('delta' in chunk) {
2591
2661
  if ('text' in chunk.delta) {
2592
2662
  const delta = chunk.delta.text;
2663
+ if (firstTokenTime === undefined) {
2664
+ firstTokenTime = Date.now();
2665
+ }
2593
2666
  accumulatedContent += delta;
2594
2667
  if (currentTextBlock) {
2595
2668
  currentTextBlock.text += delta;
@@ -2645,6 +2718,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2645
2718
  }
2646
2719
  usage.rawUsage = lastRawUsage;
2647
2720
  const latency = (Date.now() - startTime) / 1000;
2721
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
2648
2722
  const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
2649
2723
  // Format output to match non-streaming version
2650
2724
  const formattedOutput = contentBlocks.length > 0 ? [{
@@ -2665,6 +2739,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
2665
2739
  input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, 'anthropic')),
2666
2740
  output: formattedOutput,
2667
2741
  latency,
2742
+ timeToFirstToken,
2668
2743
  baseURL: this.baseURL,
2669
2744
  params: body,
2670
2745
  httpStatus: 200,
@@ -2826,6 +2901,7 @@ class WrappedModels {
2826
2901
  } = extractPosthogParams(params);
2827
2902
  const startTime = Date.now();
2828
2903
  const accumulatedContent = [];
2904
+ let firstTokenTime;
2829
2905
  let usage = {
2830
2906
  inputTokens: 0,
2831
2907
  outputTokens: 0,
@@ -2835,6 +2911,10 @@ class WrappedModels {
2835
2911
  try {
2836
2912
  const stream = await this.client.models.generateContentStream(geminiParams);
2837
2913
  for await (const chunk of stream) {
2914
+ // Track first token time when we get text content
2915
+ if (firstTokenTime === undefined && chunk.text) {
2916
+ firstTokenTime = Date.now();
2917
+ }
2838
2918
  const chunkWebSearchCount = calculateGoogleWebSearchCount(chunk);
2839
2919
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
2840
2920
  usage.webSearchCount = chunkWebSearchCount;
@@ -2865,6 +2945,9 @@ class WrappedModels {
2865
2945
  for (const part of candidate.content.parts) {
2866
2946
  // Type-safe check for functionCall
2867
2947
  if ('functionCall' in part) {
2948
+ if (firstTokenTime === undefined) {
2949
+ firstTokenTime = Date.now();
2950
+ }
2868
2951
  const funcCall = part.functionCall;
2869
2952
  if (funcCall?.name) {
2870
2953
  accumulatedContent.push({
@@ -2895,6 +2978,7 @@ class WrappedModels {
2895
2978
  yield chunk;
2896
2979
  }
2897
2980
  const latency = (Date.now() - startTime) / 1000;
2981
+ const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
2898
2982
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
2899
2983
  // Format output similar to formatResponseGemini
2900
2984
  const output = accumulatedContent.length > 0 ? [{
@@ -2909,6 +2993,7 @@ class WrappedModels {
2909
2993
  input: this.formatInputForPostHog(geminiParams),
2910
2994
  output,
2911
2995
  latency,
2996
+ timeToFirstToken,
2912
2997
  baseURL: 'https://generativelanguage.googleapis.com',
2913
2998
  params: params,
2914
2999
  httpStatus: 200,
@@ -4262,5 +4347,157 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
4262
4347
  }
4263
4348
  }
4264
4349
 
4265
- export { PostHogAnthropic as Anthropic, PostHogAzureOpenAI as AzureOpenAI, PostHogGoogleGenAI as GoogleGenAI, LangChainCallbackHandler, PostHogOpenAI as OpenAI, wrapVercelLanguageModel as withTracing };
4350
+ /// <reference lib="dom" />
4351
+ const DEFAULT_CACHE_TTL_SECONDS = 300; // 5 minutes
4352
+ function isPromptApiResponse(data) {
4353
+ return typeof data === 'object' && data !== null && 'prompt' in data && typeof data.prompt === 'string';
4354
+ }
4355
+ function isPromptsWithPostHog(options) {
4356
+ return 'posthog' in options;
4357
+ }
4358
+ /**
4359
+ * Prompts class for fetching and compiling LLM prompts from PostHog
4360
+ *
4361
+ * @example
4362
+ * ```ts
4363
+ * // With PostHog client
4364
+ * const prompts = new Prompts({ posthog })
4365
+ *
4366
+ * // Or with direct options (no PostHog client needed)
4367
+ * const prompts = new Prompts({
4368
+ * personalApiKey: 'phx_xxx',
4369
+ * host: 'https://us.i.posthog.com',
4370
+ * })
4371
+ *
4372
+ * // Fetch with caching and fallback
4373
+ * const template = await prompts.get('support-system-prompt', {
4374
+ * cacheTtlSeconds: 300,
4375
+ * fallback: 'You are a helpful assistant.',
4376
+ * })
4377
+ *
4378
+ * // Compile with variables
4379
+ * const systemPrompt = prompts.compile(template, {
4380
+ * company: 'Acme Corp',
4381
+ * tier: 'premium',
4382
+ * })
4383
+ * ```
4384
+ */
4385
+ class Prompts {
4386
+ constructor(options) {
4387
+ this.cache = new Map();
4388
+ this.defaultCacheTtlSeconds = options.defaultCacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS;
4389
+ if (isPromptsWithPostHog(options)) {
4390
+ this.personalApiKey = options.posthog.options.personalApiKey ?? '';
4391
+ this.host = options.posthog.host;
4392
+ } else {
4393
+ // Direct options
4394
+ this.personalApiKey = options.personalApiKey;
4395
+ this.host = options.host ?? 'https://us.i.posthog.com';
4396
+ }
4397
+ }
4398
+ /**
4399
+ * Fetch a prompt by name from the PostHog API
4400
+ *
4401
+ * @param name - The name of the prompt to fetch
4402
+ * @param options - Optional settings for caching and fallback
4403
+ * @returns The prompt string
4404
+ * @throws Error if the prompt cannot be fetched and no fallback is provided
4405
+ */
4406
+ async get(name, options) {
4407
+ const cacheTtlSeconds = options?.cacheTtlSeconds ?? this.defaultCacheTtlSeconds;
4408
+ const fallback = options?.fallback;
4409
+ // Check cache first
4410
+ const cached = this.cache.get(name);
4411
+ const now = Date.now();
4412
+ if (cached) {
4413
+ const isFresh = now - cached.fetchedAt < cacheTtlSeconds * 1000;
4414
+ if (isFresh) {
4415
+ return cached.prompt;
4416
+ }
4417
+ }
4418
+ // Try to fetch from API
4419
+ try {
4420
+ const prompt = await this.fetchPromptFromApi(name);
4421
+ const fetchedAt = Date.now();
4422
+ // Update cache
4423
+ this.cache.set(name, {
4424
+ prompt,
4425
+ fetchedAt
4426
+ });
4427
+ return prompt;
4428
+ } catch (error) {
4429
+ // Fallback order:
4430
+ // 1. Return stale cache (with warning)
4431
+ if (cached) {
4432
+ console.warn(`[PostHog Prompts] Failed to fetch prompt "${name}", using stale cache:`, error);
4433
+ return cached.prompt;
4434
+ }
4435
+ // 2. Return fallback (with warning)
4436
+ if (fallback !== undefined) {
4437
+ console.warn(`[PostHog Prompts] Failed to fetch prompt "${name}", using fallback:`, error);
4438
+ return fallback;
4439
+ }
4440
+ // 3. Throw error
4441
+ throw error;
4442
+ }
4443
+ }
4444
+ /**
4445
+ * Compile a prompt template with variable substitution
4446
+ *
4447
+ * Variables in the format `{{variableName}}` will be replaced with values from the variables object.
4448
+ * Unmatched variables are left unchanged.
4449
+ *
4450
+ * @param prompt - The prompt template string
4451
+ * @param variables - Object containing variable values
4452
+ * @returns The compiled prompt string
4453
+ */
4454
+ compile(prompt, variables) {
4455
+ return prompt.replace(/\{\{([\w.-]+)\}\}/g, (match, variableName) => {
4456
+ if (variableName in variables) {
4457
+ return String(variables[variableName]);
4458
+ }
4459
+ return match;
4460
+ });
4461
+ }
4462
+ /**
4463
+ * Clear the cache for a specific prompt or all prompts
4464
+ *
4465
+ * @param name - Optional prompt name to clear. If not provided, clears all cached prompts.
4466
+ */
4467
+ clearCache(name) {
4468
+ if (name !== undefined) {
4469
+ this.cache.delete(name);
4470
+ } else {
4471
+ this.cache.clear();
4472
+ }
4473
+ }
4474
+ async fetchPromptFromApi(name) {
4475
+ if (!this.personalApiKey) {
4476
+ throw new Error('[PostHog Prompts] personalApiKey is required to fetch prompts. ' + 'Please provide it when initializing the Prompts instance.');
4477
+ }
4478
+ const url = `${this.host}/api/projects/@current/llm_prompts/name/${encodeURIComponent(name)}/`;
4479
+ const response = await fetch(url, {
4480
+ method: 'GET',
4481
+ headers: {
4482
+ Authorization: `Bearer ${this.personalApiKey}`
4483
+ }
4484
+ });
4485
+ if (!response.ok) {
4486
+ if (response.status === 404) {
4487
+ throw new Error(`[PostHog Prompts] Prompt "${name}" not found`);
4488
+ }
4489
+ if (response.status === 403) {
4490
+ throw new Error(`[PostHog Prompts] Access denied for prompt "${name}". ` + 'Check that your personalApiKey has the correct permissions and the LLM prompts feature is enabled.');
4491
+ }
4492
+ throw new Error(`[PostHog Prompts] Failed to fetch prompt "${name}": HTTP ${response.status}`);
4493
+ }
4494
+ const data = await response.json();
4495
+ if (!isPromptApiResponse(data)) {
4496
+ throw new Error(`[PostHog Prompts] Invalid response format for prompt "${name}"`);
4497
+ }
4498
+ return data.prompt;
4499
+ }
4500
+ }
4501
+
4502
+ export { PostHogAnthropic as Anthropic, PostHogAzureOpenAI as AzureOpenAI, PostHogGoogleGenAI as GoogleGenAI, LangChainCallbackHandler, PostHogOpenAI as OpenAI, Prompts, wrapVercelLanguageModel as withTracing };
4266
4503
  //# sourceMappingURL=index.mjs.map