@posthog/ai 7.7.0 → 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/anthropic/index.cjs +14 -1
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +14 -1
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +15 -1
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.mjs +15 -1
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +86 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +86 -1
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +1 -1
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +1 -1
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +36 -1
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.mjs +36 -1
- package/dist/openai/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +20 -1
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.mjs +20 -1
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +3 -3
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.
|
|
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,
|