@posthog/ai 8.7.1 → 8.8.1

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.
@@ -9,6 +9,33 @@ const isString = value => {
9
9
  return typeof value === 'string';
10
10
  };
11
11
 
12
+ /** @internal */
13
+
14
+ /** @internal */
15
+
16
+ /** @internal */
17
+ function isFullAiCaptureEnabled(client) {
18
+ return client?.enableFullAiCapture === true;
19
+ }
20
+
21
+ /** @internal */
22
+ function captureAiEvent(client, event) {
23
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAi === 'function') {
24
+ client.captureAi(event);
25
+ return;
26
+ }
27
+ client.capture(event);
28
+ }
29
+
30
+ /** @internal */
31
+ async function captureAiEventImmediate(client, event) {
32
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAiImmediate === 'function') {
33
+ await client.captureAiImmediate(event);
34
+ return;
35
+ }
36
+ await client.captureImmediate(event);
37
+ }
38
+
12
39
  const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
13
40
  const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
14
41
  class Base64Recognizer {
@@ -114,7 +141,6 @@ class BinaryContentRedactor {
114
141
  this.recognizer = recognizer;
115
142
  }
116
143
  redact(value, mediaType) {
117
- if (this.isMultimodalEnabled()) return value;
118
144
  this.visited = new WeakSet();
119
145
  return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
120
146
  }
@@ -158,15 +184,12 @@ class BinaryContentRedactor {
158
184
  if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
159
185
  return `[base64 ${mediaType} redacted]`;
160
186
  }
161
- isMultimodalEnabled() {
162
- const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
163
- return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
164
- }
165
187
  }
166
188
 
167
189
  const redactor = new BinaryContentRedactor();
168
- const sanitizeOpenAI = data => redactor.redact(data);
169
- const sanitizeOpenAIResponse = data => redactor.redact(data);
190
+ const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
191
+ const sanitizeOpenAI = (data, client) => sanitize(data, client);
192
+ const sanitizeOpenAIResponse = (data, client) => sanitize(data, client);
170
193
 
171
194
  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']);
172
195
 
@@ -317,6 +340,11 @@ const formatResponseOpenAI = response => {
317
340
  arguments: item.arguments || {}
318
341
  }
319
342
  });
343
+ } else if (item.type === 'image_generation_call' && item.result) {
344
+ content.push({
345
+ type: 'image',
346
+ image: item.result
347
+ });
320
348
  }
321
349
  }
322
350
  if (content.length > 0) {
@@ -549,7 +577,7 @@ function formatOpenAIResponsesInput(input, instructions) {
549
577
  return messages;
550
578
  }
551
579
 
552
- var version = "8.7.1";
580
+ var version = "8.8.1";
553
581
 
554
582
  const DEFAULT_MAX_DEPTH = 3;
555
583
  const MAX_STACK_LINES = 20;
@@ -798,9 +826,9 @@ const captureAiGeneration$1 = async (client, options) => {
798
826
  groups: options.groups
799
827
  };
800
828
  if (options.captureImmediate) {
801
- await client.captureImmediate(event);
829
+ await captureAiEventImmediate(client, event);
802
830
  } else {
803
- client.capture(event);
831
+ captureAiEvent(client, event);
804
832
  }
805
833
  } catch (error) {
806
834
  // Telemetry failures must never affect the instrumented provider call.
@@ -1295,6 +1323,340 @@ function monitoredStreamTee(source, createStream) {
1295
1323
  return [monitoringStream, callerStream];
1296
1324
  }
1297
1325
 
1326
+ /** Pure state accumulator for OpenAI-compatible Chat Completions chunks. */
1327
+ class OpenAIChatStreamAccumulator {
1328
+ accumulatedContent = '';
1329
+ usage = {
1330
+ inputTokens: 0,
1331
+ outputTokens: 0,
1332
+ webSearchCount: 0
1333
+ };
1334
+ toolCalls = new Map();
1335
+ consume(chunk, receivedAt = Date.now()) {
1336
+ this.model ||= chunk.model || undefined;
1337
+ this.completionId ||= chunk.id || undefined;
1338
+ this.systemFingerprint ||= chunk.system_fingerprint || undefined;
1339
+ if (chunk.service_tier != null) {
1340
+ this.serviceTier = chunk.service_tier;
1341
+ }
1342
+ const choice = chunk.choices?.[0];
1343
+ if (choice?.finish_reason) {
1344
+ this.stopReason = choice.finish_reason;
1345
+ }
1346
+ const webSearchCount = calculateWebSearchCount(chunk);
1347
+ if (webSearchCount > (this.usage.webSearchCount ?? 0)) {
1348
+ this.usage.webSearchCount = webSearchCount;
1349
+ }
1350
+ if (choice?.delta?.content) {
1351
+ this.firstTokenTime ??= receivedAt;
1352
+ this.accumulatedContent += choice.delta.content;
1353
+ }
1354
+ if (Array.isArray(choice?.delta?.tool_calls)) {
1355
+ this.firstTokenTime ??= receivedAt;
1356
+ for (const toolCall of choice.delta.tool_calls) {
1357
+ if (toolCall.index === undefined) {
1358
+ continue;
1359
+ }
1360
+ const current = this.toolCalls.get(toolCall.index) ?? {
1361
+ id: '',
1362
+ name: '',
1363
+ arguments: ''
1364
+ };
1365
+ if (toolCall.id) {
1366
+ current.id = toolCall.id;
1367
+ }
1368
+ if (toolCall.function?.name) {
1369
+ current.name = toolCall.function.name;
1370
+ }
1371
+ if (toolCall.function?.arguments) {
1372
+ current.arguments += toolCall.function.arguments;
1373
+ }
1374
+ this.toolCalls.set(toolCall.index, current);
1375
+ }
1376
+ }
1377
+ if (chunk.usage) {
1378
+ this.usage = {
1379
+ ...this.usage,
1380
+ inputTokens: chunk.usage.prompt_tokens ?? 0,
1381
+ outputTokens: chunk.usage.completion_tokens ?? 0,
1382
+ reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
1383
+ cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0,
1384
+ cacheCreationInputTokens: extractCacheWriteTokens(chunk.usage.prompt_tokens_details),
1385
+ rawUsage: chunk.usage
1386
+ };
1387
+ }
1388
+ }
1389
+ result() {
1390
+ const content = [];
1391
+ if (this.accumulatedContent) {
1392
+ content.push({
1393
+ type: 'text',
1394
+ text: this.accumulatedContent
1395
+ });
1396
+ }
1397
+ for (const toolCall of this.toolCalls.values()) {
1398
+ if (toolCall.name) {
1399
+ content.push({
1400
+ type: 'function',
1401
+ id: toolCall.id,
1402
+ function: {
1403
+ name: toolCall.name,
1404
+ arguments: toolCall.arguments
1405
+ }
1406
+ });
1407
+ }
1408
+ }
1409
+ return {
1410
+ output: [{
1411
+ role: 'assistant',
1412
+ content: content.length > 0 ? content : [{
1413
+ type: 'text',
1414
+ text: ''
1415
+ }]
1416
+ }],
1417
+ model: this.model,
1418
+ completionId: this.completionId,
1419
+ systemFingerprint: this.systemFingerprint,
1420
+ serviceTier: this.serviceTier,
1421
+ firstTokenTime: this.firstTokenTime,
1422
+ stopReason: this.stopReason,
1423
+ usage: {
1424
+ ...this.usage
1425
+ }
1426
+ };
1427
+ }
1428
+ }
1429
+ /** Pure state accumulator for OpenAI-compatible Responses stream events. */
1430
+ class OpenAIResponsesStreamAccumulator {
1431
+ output = [];
1432
+ usage = {
1433
+ inputTokens: 0,
1434
+ outputTokens: 0,
1435
+ webSearchCount: 0
1436
+ };
1437
+ consume(event, receivedAt = Date.now()) {
1438
+ if (this.firstTokenTime === undefined && isResponseTokenChunk(event)) {
1439
+ this.firstTokenTime = receivedAt;
1440
+ }
1441
+ if (!('response' in event) || !event.response) {
1442
+ return;
1443
+ }
1444
+ const response = event.response;
1445
+ this.model ||= response.model || undefined;
1446
+ this.completionId ||= response.id || undefined;
1447
+ if (response.service_tier != null) {
1448
+ this.serviceTier = response.service_tier;
1449
+ }
1450
+ const webSearchCount = calculateWebSearchCount(response);
1451
+ if (webSearchCount > (this.usage.webSearchCount ?? 0)) {
1452
+ this.usage.webSearchCount = webSearchCount;
1453
+ }
1454
+ if (response.usage) {
1455
+ this.usage = {
1456
+ ...this.usage,
1457
+ inputTokens: response.usage.input_tokens ?? 0,
1458
+ outputTokens: response.usage.output_tokens ?? 0,
1459
+ reasoningTokens: response.usage.output_tokens_details?.reasoning_tokens ?? 0,
1460
+ cacheReadInputTokens: response.usage.input_tokens_details?.cached_tokens ?? 0,
1461
+ cacheCreationInputTokens: extractCacheWriteTokens(response.usage.input_tokens_details),
1462
+ rawUsage: response.usage
1463
+ };
1464
+ }
1465
+ if (isTerminalResponse(response)) {
1466
+ this.terminalResponse = response;
1467
+ this.output = response.output ?? [];
1468
+ this.stopReason = response.status;
1469
+ }
1470
+ }
1471
+ result() {
1472
+ return {
1473
+ output: [...this.output],
1474
+ model: this.model,
1475
+ completionId: this.completionId,
1476
+ serviceTier: this.serviceTier,
1477
+ firstTokenTime: this.firstTokenTime,
1478
+ stopReason: this.stopReason,
1479
+ usage: {
1480
+ ...this.usage
1481
+ },
1482
+ terminalResponse: this.terminalResponse
1483
+ };
1484
+ }
1485
+ }
1486
+
1487
+ function captureAiGenerationInBackground(...args) {
1488
+ void captureAiGeneration(...args).catch(() => undefined);
1489
+ }
1490
+
1491
+ /** Preserve immediate delivery while isolating normal telemetry from provider latency/failures. */
1492
+ async function captureAiGenerationAfterSuccess(...args) {
1493
+ if (args[1].captureImmediate) {
1494
+ await captureAiGeneration(...args);
1495
+ } else {
1496
+ captureAiGenerationInBackground(...args);
1497
+ }
1498
+ }
1499
+ function buildChatUsage(usage, webSearchSource) {
1500
+ return {
1501
+ inputTokens: usage?.prompt_tokens ?? 0,
1502
+ outputTokens: usage?.completion_tokens ?? 0,
1503
+ reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens ?? 0,
1504
+ cacheReadInputTokens: usage?.prompt_tokens_details?.cached_tokens ?? 0,
1505
+ cacheCreationInputTokens: extractCacheWriteTokens(usage?.prompt_tokens_details),
1506
+ webSearchCount: calculateWebSearchCount(webSearchSource),
1507
+ rawUsage: usage
1508
+ };
1509
+ }
1510
+ function buildResponsesUsage(usage, webSearchSource) {
1511
+ return {
1512
+ inputTokens: usage?.input_tokens ?? 0,
1513
+ outputTokens: usage?.output_tokens ?? 0,
1514
+ reasoningTokens: usage?.output_tokens_details?.reasoning_tokens ?? 0,
1515
+ cacheReadInputTokens: usage?.input_tokens_details?.cached_tokens ?? 0,
1516
+ cacheCreationInputTokens: extractCacheWriteTokens(usage?.input_tokens_details),
1517
+ webSearchCount: calculateWebSearchCount(webSearchSource),
1518
+ rawUsage: usage
1519
+ };
1520
+ }
1521
+ function buildChatSuccessOptions(context, result) {
1522
+ return {
1523
+ ...context.monitoring,
1524
+ model: context.params.model ?? result.model,
1525
+ provider: context.provider,
1526
+ input: sanitizeOpenAI(context.params.messages, context.client),
1527
+ output: sanitizeOpenAIResponse(result.output, context.client),
1528
+ latency: result.latency,
1529
+ timeToFirstToken: result.timeToFirstToken,
1530
+ baseURL: context.baseURL,
1531
+ modelParameters: getModelParams(context.modelParametersSource, result.serviceTier),
1532
+ httpStatus: 200,
1533
+ usage: result.usage,
1534
+ stopReason: result.stopReason,
1535
+ tools: extractAvailableToolCalls('openai', context.params),
1536
+ completionId: result.completionId,
1537
+ providerMetadata: buildProviderMetadata({
1538
+ systemFingerprint: result.systemFingerprint,
1539
+ requestId: result.requestId
1540
+ })
1541
+ };
1542
+ }
1543
+ function buildChatErrorOptions(context, error, metadata = {}) {
1544
+ return {
1545
+ ...context.monitoring,
1546
+ model: context.params.model,
1547
+ provider: context.provider,
1548
+ input: sanitizeOpenAI(context.params.messages, context.client),
1549
+ output: [],
1550
+ latency: 0,
1551
+ baseURL: context.baseURL,
1552
+ modelParameters: getModelParams(context.modelParametersSource),
1553
+ usage: {
1554
+ inputTokens: 0,
1555
+ outputTokens: 0
1556
+ },
1557
+ completionId: metadata.completionId,
1558
+ providerMetadata: buildProviderMetadata({
1559
+ systemFingerprint: metadata.systemFingerprint
1560
+ }),
1561
+ error
1562
+ };
1563
+ }
1564
+ function buildSanitizedResponsesInput(context) {
1565
+ return formatOpenAIResponsesInput(sanitizeOpenAIResponse(context.params.input, context.client), sanitizeOpenAIResponse(context.params.instructions, context.client));
1566
+ }
1567
+ function buildResponsesSuccessOptions(context, result) {
1568
+ const response = result.response;
1569
+ return {
1570
+ ...context.monitoring,
1571
+ model: context.params.model ?? response.model,
1572
+ provider: context.provider,
1573
+ input: buildSanitizedResponsesInput(context),
1574
+ output: sanitizeOpenAIResponse(result.output, context.client),
1575
+ latency: result.latency,
1576
+ timeToFirstToken: result.timeToFirstToken,
1577
+ baseURL: context.baseURL,
1578
+ modelParameters: getModelParams(context.modelParametersSource, response.service_tier),
1579
+ httpStatus: 200,
1580
+ usage: result.usage ?? buildResponsesUsage(response.usage, response),
1581
+ stopReason: response.status ?? undefined,
1582
+ tools: result.includeTools ? extractAvailableToolCalls('openai', context.params) : undefined,
1583
+ completionId: response.id,
1584
+ providerMetadata: buildProviderMetadata({
1585
+ requestId: result.includeRequestId ? extractRequestId(response) : undefined,
1586
+ incompleteDetails: response.incomplete_details
1587
+ }),
1588
+ error: getResponseFailure({
1589
+ id: response.id,
1590
+ status: response.status,
1591
+ error: response.error ?? null
1592
+ })
1593
+ };
1594
+ }
1595
+ function buildBackgroundResponseOptions(context, response) {
1596
+ return buildResponsesSuccessOptions(context, {
1597
+ response,
1598
+ output: formatResponseOpenAI({
1599
+ output: response.output
1600
+ }),
1601
+ latency: getBackgroundResponseLatency(response),
1602
+ includeTools: true,
1603
+ includeRequestId: true
1604
+ });
1605
+ }
1606
+ function buildResponsesErrorOptions(context, error, completionId) {
1607
+ return {
1608
+ ...context.monitoring,
1609
+ model: context.params.model,
1610
+ provider: context.provider,
1611
+ input: buildSanitizedResponsesInput(context),
1612
+ output: [],
1613
+ latency: 0,
1614
+ baseURL: context.baseURL,
1615
+ modelParameters: getModelParams(context.modelParametersSource),
1616
+ usage: {
1617
+ inputTokens: 0,
1618
+ outputTokens: 0
1619
+ },
1620
+ completionId,
1621
+ error
1622
+ };
1623
+ }
1624
+ function buildEmbeddingSuccessOptions(context, usage, latency) {
1625
+ return {
1626
+ eventType: AIEvent.Embedding,
1627
+ ...context.monitoring,
1628
+ model: context.params.model,
1629
+ provider: context.provider,
1630
+ input: withPrivacyMode(context.client, context.monitoring.privacyMode, context.params.input),
1631
+ output: null,
1632
+ latency,
1633
+ baseURL: context.baseURL,
1634
+ modelParameters: getModelParams(context.modelParametersSource),
1635
+ httpStatus: 200,
1636
+ usage: {
1637
+ inputTokens: usage?.prompt_tokens ?? 0,
1638
+ rawUsage: usage
1639
+ }
1640
+ };
1641
+ }
1642
+ function buildEmbeddingErrorOptions(context, error) {
1643
+ return {
1644
+ eventType: AIEvent.Embedding,
1645
+ ...context.monitoring,
1646
+ model: context.params.model,
1647
+ provider: context.provider,
1648
+ input: withPrivacyMode(context.client, context.monitoring.privacyMode, context.params.input),
1649
+ output: null,
1650
+ latency: 0,
1651
+ baseURL: context.baseURL,
1652
+ modelParameters: getModelParams(context.modelParametersSource),
1653
+ usage: {
1654
+ inputTokens: 0
1655
+ },
1656
+ error
1657
+ };
1658
+ }
1659
+
1298
1660
  class PostHogAzureOpenAI extends AzureOpenAI {
1299
1661
  constructor(config) {
1300
1662
  const {
@@ -1340,169 +1702,37 @@ let WrappedCompletions$1 = class WrappedCompletions extends AzureOpenAI.Chat.Com
1340
1702
  if (Symbol.asyncIterator in value) {
1341
1703
  const [stream1, stream2] = monitoredStreamTee(value, (iterator, controller) => new Stream(iterator, controller));
1342
1704
  (async () => {
1343
- // Hoisted so the catch block can surface whatever was accumulated
1344
- // from the streamed chunks before the failure.
1345
- let completionIdFromResponse;
1346
- let systemFingerprintFromResponse;
1705
+ const accumulator = new OpenAIChatStreamAccumulator();
1347
1706
  try {
1348
- const contentBlocks = [];
1349
- let accumulatedContent = '';
1350
- let modelFromResponse;
1351
- let serviceTierFromResponse;
1352
- let firstTokenTime;
1353
- let usage = {
1354
- inputTokens: 0,
1355
- outputTokens: 0
1356
- };
1357
-
1358
- // Map to track in-progress tool calls
1359
- const toolCallsInProgress = new Map();
1360
1707
  for await (const chunk of stream1) {
1361
- // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
1362
- if (!modelFromResponse && chunk.model) {
1363
- modelFromResponse = chunk.model;
1364
- }
1365
- if (!completionIdFromResponse && chunk.id) {
1366
- completionIdFromResponse = chunk.id;
1367
- }
1368
- if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
1369
- systemFingerprintFromResponse = chunk.system_fingerprint;
1370
- }
1371
- if (chunk.service_tier != null) {
1372
- serviceTierFromResponse = chunk.service_tier;
1373
- }
1374
- const choice = chunk?.choices?.[0];
1375
-
1376
- // Handle text content
1377
- const deltaContent = choice?.delta?.content;
1378
- if (deltaContent) {
1379
- if (firstTokenTime === undefined) {
1380
- firstTokenTime = Date.now();
1381
- }
1382
- accumulatedContent += deltaContent;
1383
- }
1384
-
1385
- // Handle tool calls
1386
- const deltaToolCalls = choice?.delta?.tool_calls;
1387
- if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
1388
- if (firstTokenTime === undefined) {
1389
- firstTokenTime = Date.now();
1390
- }
1391
- for (const toolCall of deltaToolCalls) {
1392
- const index = toolCall.index;
1393
- if (index !== undefined) {
1394
- if (!toolCallsInProgress.has(index)) {
1395
- // New tool call
1396
- toolCallsInProgress.set(index, {
1397
- id: toolCall.id || '',
1398
- name: toolCall.function?.name || '',
1399
- arguments: ''
1400
- });
1401
- }
1402
- const inProgressCall = toolCallsInProgress.get(index);
1403
- if (inProgressCall) {
1404
- // Update tool call data
1405
- if (toolCall.id) {
1406
- inProgressCall.id = toolCall.id;
1407
- }
1408
- if (toolCall.function?.name) {
1409
- inProgressCall.name = toolCall.function.name;
1410
- }
1411
- if (toolCall.function?.arguments) {
1412
- inProgressCall.arguments += toolCall.function.arguments;
1413
- }
1414
- }
1415
- }
1416
- }
1417
- }
1418
-
1419
- // Handle usage information
1420
- if (chunk.usage) {
1421
- usage = {
1422
- inputTokens: chunk.usage.prompt_tokens ?? 0,
1423
- outputTokens: chunk.usage.completion_tokens ?? 0,
1424
- reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
1425
- cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0,
1426
- cacheCreationInputTokens: extractCacheWriteTokens(chunk.usage.prompt_tokens_details)
1427
- };
1428
- }
1429
- }
1430
-
1431
- // Build final content blocks
1432
- if (accumulatedContent) {
1433
- contentBlocks.push({
1434
- type: 'text',
1435
- text: accumulatedContent
1436
- });
1708
+ accumulator.consume(chunk);
1437
1709
  }
1438
-
1439
- // Add completed tool calls to content blocks
1440
- for (const toolCall of toolCallsInProgress.values()) {
1441
- if (toolCall.name) {
1442
- contentBlocks.push({
1443
- type: 'function',
1444
- id: toolCall.id,
1445
- function: {
1446
- name: toolCall.name,
1447
- arguments: toolCall.arguments
1448
- }
1449
- });
1450
- }
1451
- }
1452
-
1453
- // Format output to match non-streaming version
1454
- const formattedOutput = contentBlocks.length > 0 ? [{
1455
- role: 'assistant',
1456
- content: contentBlocks
1457
- }] : [{
1458
- role: 'assistant',
1459
- content: [{
1460
- type: 'text',
1461
- text: ''
1462
- }]
1463
- }];
1464
- const latency = (Date.now() - startTime) / 1000;
1465
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1466
- await captureAiGeneration(this.phClient, {
1467
- ...posthogParams,
1468
- model: openAIParams.model ?? modelFromResponse,
1710
+ const accumulated = accumulator.result();
1711
+ await captureAiGeneration(this.phClient, buildChatSuccessOptions({
1712
+ client: this.phClient,
1469
1713
  provider: 'azure',
1470
- input: sanitizeOpenAI(openAIParams.messages),
1471
- output: sanitizeOpenAIResponse(formattedOutput),
1472
- latency,
1473
- timeToFirstToken,
1474
1714
  baseURL: this.baseURL,
1475
- modelParameters: getModelParams(body, serviceTierFromResponse),
1476
- httpStatus: 200,
1477
- usage,
1478
- completionId: completionIdFromResponse,
1479
- providerMetadata: buildProviderMetadata({
1480
- systemFingerprint: systemFingerprintFromResponse
1481
- })
1482
- });
1715
+ params: openAIParams,
1716
+ monitoring: posthogParams,
1717
+ modelParametersSource: body
1718
+ }, {
1719
+ ...accumulated,
1720
+ latency: (Date.now() - startTime) / 1000,
1721
+ timeToFirstToken: accumulated.firstTokenTime === undefined ? undefined : (accumulated.firstTokenTime - startTime) / 1000
1722
+ }));
1483
1723
  } catch (error) {
1484
- await captureAiGeneration(this.phClient, {
1485
- ...posthogParams,
1486
- model: openAIParams.model,
1724
+ const accumulated = accumulator.result();
1725
+ await captureAiGeneration(this.phClient, buildChatErrorOptions({
1726
+ client: this.phClient,
1487
1727
  provider: 'azure',
1488
- input: sanitizeOpenAI(openAIParams.messages),
1489
- output: [],
1490
- latency: 0,
1491
1728
  baseURL: this.baseURL,
1492
- modelParameters: getModelParams(body),
1493
- usage: {
1494
- inputTokens: 0,
1495
- outputTokens: 0
1496
- },
1497
- // If the stream fails mid-flight, surface whatever completion
1498
- // metadata the consumed chunks already provided so the error
1499
- // event can still be correlated to OpenAI's Logs dashboard.
1500
- completionId: completionIdFromResponse,
1501
- providerMetadata: buildProviderMetadata({
1502
- systemFingerprint: systemFingerprintFromResponse
1503
- }),
1504
- error: error
1505
- });
1729
+ params: openAIParams,
1730
+ monitoring: posthogParams,
1731
+ modelParametersSource: body
1732
+ }, error, {
1733
+ completionId: accumulated.completionId,
1734
+ systemFingerprint: accumulated.systemFingerprint
1735
+ }));
1506
1736
  throw error;
1507
1737
  }
1508
1738
  })().catch(() => {
@@ -1519,50 +1749,35 @@ let WrappedCompletions$1 = class WrappedCompletions extends AzureOpenAI.Chat.Com
1519
1749
  } else {
1520
1750
  const wrappedPromise = parentPromise.then(async result => {
1521
1751
  if ('choices' in result) {
1522
- const latency = (Date.now() - startTime) / 1000;
1523
- await captureAiGeneration(this.phClient, {
1524
- ...posthogParams,
1525
- model: openAIParams.model ?? result.model,
1752
+ await captureAiGenerationAfterSuccess(this.phClient, buildChatSuccessOptions({
1753
+ client: this.phClient,
1526
1754
  provider: 'azure',
1527
- input: sanitizeOpenAI(openAIParams.messages),
1528
- output: sanitizeOpenAIResponse(formatResponseOpenAI(result)),
1529
- latency,
1530
1755
  baseURL: this.baseURL,
1531
- modelParameters: getModelParams(body, result.service_tier),
1532
- httpStatus: 200,
1533
- usage: {
1534
- inputTokens: result.usage?.prompt_tokens ?? 0,
1535
- outputTokens: result.usage?.completion_tokens ?? 0,
1536
- reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
1537
- cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0,
1538
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.prompt_tokens_details)
1539
- },
1756
+ params: openAIParams,
1757
+ monitoring: posthogParams,
1758
+ modelParametersSource: body
1759
+ }, {
1760
+ output: formatResponseOpenAI(result),
1761
+ model: result.model,
1762
+ serviceTier: result.service_tier ?? undefined,
1763
+ latency: (Date.now() - startTime) / 1000,
1764
+ usage: buildChatUsage(result.usage, result),
1765
+ stopReason: result.choices[0]?.finish_reason ?? undefined,
1540
1766
  completionId: result.id,
1541
- providerMetadata: buildProviderMetadata({
1542
- systemFingerprint: result.system_fingerprint,
1543
- requestId: extractRequestId(result)
1544
- })
1545
- });
1767
+ systemFingerprint: result.system_fingerprint,
1768
+ requestId: result._request_id
1769
+ }));
1546
1770
  }
1547
1771
  return result;
1548
1772
  }, async error => {
1549
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1550
- await captureAiGeneration(this.phClient, {
1551
- ...posthogParams,
1552
- model: openAIParams.model,
1773
+ await captureAiGeneration(this.phClient, buildChatErrorOptions({
1774
+ client: this.phClient,
1553
1775
  provider: 'azure',
1554
- input: sanitizeOpenAI(openAIParams.messages),
1555
- output: [],
1556
- latency: 0,
1557
1776
  baseURL: this.baseURL,
1558
- modelParameters: getModelParams(body),
1559
- httpStatus,
1560
- usage: {
1561
- inputTokens: 0,
1562
- outputTokens: 0
1563
- },
1564
- error
1565
- });
1777
+ params: openAIParams,
1778
+ monitoring: posthogParams,
1779
+ modelParametersSource: body
1780
+ }, error));
1566
1781
  throw error;
1567
1782
  });
1568
1783
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -1581,32 +1796,14 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1581
1796
  openAIParams,
1582
1797
  posthogParams
1583
1798
  } = context;
1584
- await captureAiGeneration(this.phClient, {
1585
- ...posthogParams,
1586
- model: openAIParams.model ?? result.model,
1799
+ await captureAiGenerationAfterSuccess(this.phClient, buildBackgroundResponseOptions({
1800
+ client: this.phClient,
1587
1801
  provider: 'azure',
1588
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1589
- output: result.output,
1590
- latency: getBackgroundResponseLatency(result),
1591
1802
  baseURL: this.baseURL,
1592
- modelParameters: getModelParams(openAIParams, result.service_tier),
1593
- httpStatus: 200,
1594
- usage: {
1595
- inputTokens: result.usage?.input_tokens ?? 0,
1596
- outputTokens: result.usage?.output_tokens ?? 0,
1597
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1598
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1599
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.input_tokens_details),
1600
- rawUsage: result.usage
1601
- },
1602
- stopReason: result.status ?? undefined,
1603
- completionId: result.id,
1604
- providerMetadata: buildProviderMetadata({
1605
- requestId: extractRequestId(result),
1606
- incompleteDetails: result.incomplete_details
1607
- }),
1608
- error: getResponseFailure(result)
1609
- });
1803
+ params: openAIParams,
1804
+ monitoring: posthogParams,
1805
+ modelParametersSource: openAIParams
1806
+ }, result));
1610
1807
  }
1611
1808
 
1612
1809
  // --- Overload #1: Non-streaming
@@ -1628,108 +1825,61 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1628
1825
  if (Symbol.asyncIterator in value) {
1629
1826
  const [stream1, stream2] = monitoredStreamTee(value, (iterator, controller) => new Stream(iterator, controller));
1630
1827
  (async () => {
1631
- // Hoisted so the catch block can surface the completion ID that
1632
- // was accumulated from the streamed chunks before the failure.
1633
- let completionIdFromResponse;
1828
+ const accumulator = new OpenAIResponsesStreamAccumulator();
1634
1829
  try {
1635
- let finalContent = [];
1636
- let modelFromResponse;
1637
- let serviceTierFromResponse;
1638
- let firstTokenTime;
1639
- let usage = {
1640
- inputTokens: 0,
1641
- outputTokens: 0
1642
- };
1643
- let terminalResponse;
1644
1830
  for await (const chunk of stream1) {
1645
- // Track first token time on content delta events
1646
- if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
1647
- firstTokenTime = Date.now();
1648
- }
1649
- if ('response' in chunk && chunk.response) {
1650
- // Extract model and completion ID from the response object in the chunk (for stored prompts)
1651
- if (!modelFromResponse && chunk.response.model) {
1652
- modelFromResponse = chunk.response.model;
1653
- }
1654
- if (!completionIdFromResponse && chunk.response.id) {
1655
- completionIdFromResponse = chunk.response.id;
1656
- }
1657
- if (openAIParams.background === true && !this.backgroundResponses.get(chunk.response.id)) {
1658
- this.backgroundResponses.set(chunk.response.id, {
1659
- openAIParams,
1660
- posthogParams
1661
- });
1662
- }
1663
- if (chunk.response.service_tier != null) {
1664
- serviceTierFromResponse = chunk.response.service_tier;
1665
- }
1666
- if (isTerminalResponse(chunk.response)) {
1667
- terminalResponse = chunk.response;
1668
- finalContent = chunk.response.output ?? [];
1669
- }
1670
- }
1671
- if ('response' in chunk && chunk.response?.usage) {
1672
- usage = {
1673
- inputTokens: chunk.response.usage.input_tokens ?? 0,
1674
- outputTokens: chunk.response.usage.output_tokens ?? 0,
1675
- reasoningTokens: chunk.response.usage.output_tokens_details?.reasoning_tokens ?? 0,
1676
- cacheReadInputTokens: chunk.response.usage.input_tokens_details?.cached_tokens ?? 0,
1677
- cacheCreationInputTokens: extractCacheWriteTokens(chunk.response.usage.input_tokens_details)
1678
- };
1831
+ accumulator.consume(chunk);
1832
+ if (openAIParams.background === true && 'response' in chunk && chunk.response && !this.backgroundResponses.get(chunk.response.id)) {
1833
+ this.backgroundResponses.set(chunk.response.id, {
1834
+ openAIParams,
1835
+ posthogParams
1836
+ });
1679
1837
  }
1680
1838
  }
1839
+ const accumulated = accumulator.result();
1681
1840
  if (openAIParams.background === true) {
1682
- if (terminalResponse) {
1683
- const context = this.backgroundResponses.take(terminalResponse.id);
1841
+ if (accumulated.terminalResponse) {
1842
+ const context = this.backgroundResponses.take(accumulated.terminalResponse.id);
1684
1843
  if (context) {
1685
- await this.captureBackgroundResponse(terminalResponse, context).catch(() => undefined);
1844
+ await this.captureBackgroundResponse(accumulated.terminalResponse, context).catch(() => undefined);
1686
1845
  }
1687
1846
  }
1688
1847
  return;
1689
1848
  }
1690
- const latency = (Date.now() - startTime) / 1000;
1691
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
1692
- await captureAiGeneration(this.phClient, {
1693
- ...posthogParams,
1694
- model: openAIParams.model ?? modelFromResponse,
1849
+ const response = accumulated.terminalResponse ?? {
1850
+ id: accumulated.completionId ?? '',
1851
+ model: accumulated.model ?? openAIParams.model,
1852
+ status: accumulated.stopReason,
1853
+ service_tier: accumulated.serviceTier
1854
+ };
1855
+ await captureAiGeneration(this.phClient, buildResponsesSuccessOptions({
1856
+ client: this.phClient,
1695
1857
  provider: 'azure',
1696
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1697
- output: sanitizeOpenAIResponse(finalContent),
1698
- latency,
1699
- timeToFirstToken,
1700
1858
  baseURL: this.baseURL,
1701
- modelParameters: getModelParams(body, serviceTierFromResponse),
1702
- httpStatus: 200,
1703
- usage,
1704
- stopReason: terminalResponse?.status ?? undefined,
1705
- completionId: completionIdFromResponse,
1706
- providerMetadata: buildProviderMetadata({
1707
- incompleteDetails: terminalResponse?.incomplete_details
1708
- }),
1709
- error: getResponseFailure(terminalResponse)
1710
- });
1859
+ params: openAIParams,
1860
+ monitoring: posthogParams,
1861
+ modelParametersSource: body
1862
+ }, {
1863
+ response,
1864
+ output: accumulated.output,
1865
+ latency: (Date.now() - startTime) / 1000,
1866
+ timeToFirstToken: accumulated.firstTokenTime === undefined ? undefined : (accumulated.firstTokenTime - startTime) / 1000,
1867
+ usage: accumulated.usage,
1868
+ includeTools: true
1869
+ }));
1711
1870
  } catch (error) {
1712
- if (openAIParams.background === true && completionIdFromResponse && this.backgroundResponses.get(completionIdFromResponse)) {
1871
+ const accumulated = accumulator.result();
1872
+ if (openAIParams.background === true && accumulated.completionId && this.backgroundResponses.get(accumulated.completionId)) {
1713
1873
  throw error;
1714
1874
  }
1715
- await captureAiGeneration(this.phClient, {
1716
- ...posthogParams,
1717
- model: openAIParams.model,
1875
+ await captureAiGeneration(this.phClient, buildResponsesErrorOptions({
1876
+ client: this.phClient,
1718
1877
  provider: 'azure',
1719
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1720
- output: [],
1721
- latency: 0,
1722
1878
  baseURL: this.baseURL,
1723
- modelParameters: getModelParams(body),
1724
- usage: {
1725
- inputTokens: 0,
1726
- outputTokens: 0
1727
- },
1728
- // Surface the completion ID from any chunks consumed before
1729
- // the stream failed so the error event remains correlatable.
1730
- completionId: completionIdFromResponse,
1731
- error: error
1732
- });
1879
+ params: openAIParams,
1880
+ monitoring: posthogParams,
1881
+ modelParametersSource: body
1882
+ }, error, accumulated.completionId));
1733
1883
  throw error;
1734
1884
  }
1735
1885
  })().catch(() => {
@@ -1751,53 +1901,33 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1751
1901
  });
1752
1902
  return result;
1753
1903
  }
1754
- const latency = (Date.now() - startTime) / 1000;
1755
- await captureAiGeneration(this.phClient, {
1756
- ...posthogParams,
1757
- model: openAIParams.model ?? result.model,
1904
+ await captureAiGenerationAfterSuccess(this.phClient, buildResponsesSuccessOptions({
1905
+ client: this.phClient,
1758
1906
  provider: 'azure',
1759
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1760
- output: sanitizeOpenAIResponse(result.output),
1761
- latency,
1762
1907
  baseURL: this.baseURL,
1763
- modelParameters: getModelParams(body, result.service_tier),
1764
- httpStatus: 200,
1765
- usage: {
1766
- inputTokens: result.usage?.input_tokens ?? 0,
1767
- outputTokens: result.usage?.output_tokens ?? 0,
1768
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1769
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1770
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.input_tokens_details),
1771
- rawUsage: result.usage
1772
- },
1773
- stopReason: result.status ?? undefined,
1774
- completionId: result.id,
1775
- providerMetadata: buildProviderMetadata({
1776
- requestId: extractRequestId(result),
1777
- incompleteDetails: result.incomplete_details
1908
+ params: openAIParams,
1909
+ monitoring: posthogParams,
1910
+ modelParametersSource: body
1911
+ }, {
1912
+ response: result,
1913
+ output: formatResponseOpenAI({
1914
+ output: result.output
1778
1915
  }),
1779
- error: getResponseFailure(result)
1780
- });
1916
+ latency: (Date.now() - startTime) / 1000,
1917
+ includeTools: true,
1918
+ includeRequestId: true
1919
+ }));
1781
1920
  }
1782
1921
  return result;
1783
1922
  }, async error => {
1784
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1785
- await captureAiGeneration(this.phClient, {
1786
- ...posthogParams,
1787
- model: openAIParams.model,
1923
+ await captureAiGeneration(this.phClient, buildResponsesErrorOptions({
1924
+ client: this.phClient,
1788
1925
  provider: 'azure',
1789
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1790
- output: [],
1791
- latency: 0,
1792
1926
  baseURL: this.baseURL,
1793
- modelParameters: getModelParams(body),
1794
- httpStatus,
1795
- usage: {
1796
- inputTokens: 0,
1797
- outputTokens: 0
1798
- },
1799
- error
1800
- });
1927
+ params: openAIParams,
1928
+ monitoring: posthogParams,
1929
+ modelParametersSource: body
1930
+ }, error));
1801
1931
  throw error;
1802
1932
  });
1803
1933
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -1867,51 +1997,29 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1867
1997
  });
1868
1998
  return result;
1869
1999
  }
1870
- const latency = (Date.now() - startTime) / 1000;
1871
- await captureAiGeneration(this.phClient, {
1872
- ...posthogParams,
1873
- model: openAIParams.model ?? result.model,
2000
+ await captureAiGeneration(this.phClient, buildResponsesSuccessOptions({
2001
+ client: this.phClient,
1874
2002
  provider: 'azure',
1875
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1876
- output: sanitizeOpenAIResponse(result.output),
1877
- latency,
1878
2003
  baseURL: this.baseURL,
1879
- modelParameters: getModelParams(body, result.service_tier),
1880
- httpStatus: 200,
1881
- usage: {
1882
- inputTokens: result.usage?.input_tokens ?? 0,
1883
- outputTokens: result.usage?.output_tokens ?? 0,
1884
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1885
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1886
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.input_tokens_details),
1887
- rawUsage: result.usage
1888
- },
1889
- stopReason: result.status ?? undefined,
1890
- completionId: result.id,
1891
- providerMetadata: buildProviderMetadata({
1892
- requestId: extractRequestId(result),
1893
- incompleteDetails: result.incomplete_details
1894
- }),
1895
- error: getResponseFailure(result)
1896
- });
2004
+ params: openAIParams,
2005
+ monitoring: posthogParams,
2006
+ modelParametersSource: body
2007
+ }, {
2008
+ response: result,
2009
+ output: result.output,
2010
+ latency: (Date.now() - startTime) / 1000,
2011
+ includeRequestId: true
2012
+ }));
1897
2013
  return result;
1898
2014
  }, async error => {
1899
- await captureAiGeneration(this.phClient, {
1900
- ...posthogParams,
1901
- model: openAIParams.model,
2015
+ await captureAiGeneration(this.phClient, buildResponsesErrorOptions({
2016
+ client: this.phClient,
1902
2017
  provider: 'azure',
1903
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1904
- output: [],
1905
- latency: 0,
1906
2018
  baseURL: this.baseURL,
1907
- modelParameters: getModelParams(body),
1908
- httpStatus: error?.status ? error.status : 500,
1909
- usage: {
1910
- inputTokens: 0,
1911
- outputTokens: 0
1912
- },
1913
- error
1914
- });
2019
+ params: openAIParams,
2020
+ monitoring: posthogParams,
2021
+ modelParametersSource: body
2022
+ }, error));
1915
2023
  throw error;
1916
2024
  });
1917
2025
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -1931,42 +2039,24 @@ let WrappedEmbeddings$1 = class WrappedEmbeddings extends AzureOpenAI.Embeddings
1931
2039
  const startTime = Date.now();
1932
2040
  const parentPromise = super.create(openAIParams, options);
1933
2041
  const wrappedPromise = parentPromise.then(async result => {
1934
- const latency = (Date.now() - startTime) / 1000;
1935
- await captureAiGeneration(this.phClient, {
1936
- eventType: AIEvent.Embedding,
1937
- ...posthogParams,
1938
- model: openAIParams.model,
2042
+ await captureAiGeneration(this.phClient, buildEmbeddingSuccessOptions({
2043
+ client: this.phClient,
1939
2044
  provider: 'azure',
1940
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
1941
- output: null,
1942
- // Embeddings don't have output content
1943
- latency,
1944
2045
  baseURL: this.baseURL,
1945
- modelParameters: getModelParams(body),
1946
- httpStatus: 200,
1947
- usage: {
1948
- inputTokens: result.usage?.prompt_tokens ?? 0
1949
- }
1950
- });
2046
+ params: openAIParams,
2047
+ monitoring: posthogParams,
2048
+ modelParametersSource: body
2049
+ }, result.usage, (Date.now() - startTime) / 1000));
1951
2050
  return result;
1952
2051
  }, async error => {
1953
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1954
- await captureAiGeneration(this.phClient, {
1955
- eventType: AIEvent.Embedding,
1956
- ...posthogParams,
1957
- model: openAIParams.model,
2052
+ await captureAiGeneration(this.phClient, buildEmbeddingErrorOptions({
2053
+ client: this.phClient,
1958
2054
  provider: 'azure',
1959
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
1960
- output: null,
1961
- latency: 0,
1962
2055
  baseURL: this.baseURL,
1963
- modelParameters: getModelParams(body),
1964
- httpStatus,
1965
- usage: {
1966
- inputTokens: 0
1967
- },
1968
- error
1969
- });
2056
+ params: openAIParams,
2057
+ monitoring: posthogParams,
2058
+ modelParametersSource: body
2059
+ }, error));
1970
2060
  throw error;
1971
2061
  });
1972
2062
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -1979,17 +2069,6 @@ const Responses = OpenAI.Responses;
1979
2069
  const Embeddings = OpenAI.Embeddings;
1980
2070
  const Audio = OpenAI.Audio;
1981
2071
  const Transcriptions = OpenAI.Audio.Transcriptions;
1982
- function captureAiGenerationInBackground(...args) {
1983
- void captureAiGeneration(...args).catch(() => undefined);
1984
- }
1985
- async function captureAiGenerationAfterSuccess(...args) {
1986
- const [, options] = args;
1987
- if (options.captureImmediate) {
1988
- await captureAiGeneration(...args);
1989
- } else {
1990
- captureAiGenerationInBackground(...args);
1991
- }
1992
- }
1993
2072
  class PostHogOpenAI extends OpenAI {
1994
2073
  constructor(config) {
1995
2074
  const {
@@ -2036,192 +2115,37 @@ class WrappedCompletions extends Completions {
2036
2115
  if (Symbol.asyncIterator in value) {
2037
2116
  const [stream1, stream2] = monitoredStreamTee(value, (iterator, controller) => new Stream(iterator, controller));
2038
2117
  (async () => {
2039
- // Hoisted so the catch block can surface whatever was accumulated
2040
- // from the streamed chunks before the failure.
2041
- let completionIdFromResponse;
2042
- let systemFingerprintFromResponse;
2118
+ const accumulator = new OpenAIChatStreamAccumulator();
2043
2119
  try {
2044
- const contentBlocks = [];
2045
- let accumulatedContent = '';
2046
- let modelFromResponse;
2047
- let serviceTierFromResponse;
2048
- let firstTokenTime;
2049
- let stopReason;
2050
- let usage = {
2051
- inputTokens: 0,
2052
- outputTokens: 0,
2053
- webSearchCount: 0
2054
- };
2055
-
2056
- // Map to track in-progress tool calls
2057
- const toolCallsInProgress = new Map();
2058
- let rawUsageData;
2059
2120
  for await (const chunk of stream1) {
2060
- // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
2061
- if (!modelFromResponse && chunk.model) {
2062
- modelFromResponse = chunk.model;
2063
- }
2064
- if (!completionIdFromResponse && chunk.id) {
2065
- completionIdFromResponse = chunk.id;
2066
- }
2067
- if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
2068
- systemFingerprintFromResponse = chunk.system_fingerprint;
2069
- }
2070
- if (chunk.service_tier != null) {
2071
- serviceTierFromResponse = chunk.service_tier;
2072
- }
2073
- const choice = chunk?.choices?.[0];
2074
- if (choice?.finish_reason) {
2075
- stopReason = choice.finish_reason;
2076
- }
2077
- const chunkWebSearchCount = calculateWebSearchCount(chunk);
2078
- if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
2079
- usage.webSearchCount = chunkWebSearchCount;
2080
- }
2081
-
2082
- // Handle text content
2083
- const deltaContent = choice?.delta?.content;
2084
- if (deltaContent) {
2085
- if (firstTokenTime === undefined) {
2086
- firstTokenTime = Date.now();
2087
- }
2088
- accumulatedContent += deltaContent;
2089
- }
2090
-
2091
- // Handle tool calls
2092
- const deltaToolCalls = choice?.delta?.tool_calls;
2093
- if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
2094
- if (firstTokenTime === undefined) {
2095
- firstTokenTime = Date.now();
2096
- }
2097
- for (const toolCall of deltaToolCalls) {
2098
- const index = toolCall.index;
2099
- if (index !== undefined) {
2100
- if (!toolCallsInProgress.has(index)) {
2101
- // New tool call
2102
- toolCallsInProgress.set(index, {
2103
- id: toolCall.id || '',
2104
- name: toolCall.function?.name || '',
2105
- arguments: ''
2106
- });
2107
- }
2108
- const inProgressCall = toolCallsInProgress.get(index);
2109
- if (inProgressCall) {
2110
- // Update tool call data
2111
- if (toolCall.id) {
2112
- inProgressCall.id = toolCall.id;
2113
- }
2114
- if (toolCall.function?.name) {
2115
- inProgressCall.name = toolCall.function.name;
2116
- }
2117
- if (toolCall.function?.arguments) {
2118
- inProgressCall.arguments += toolCall.function.arguments;
2119
- }
2120
- }
2121
- }
2122
- }
2123
- }
2124
-
2125
- // Handle usage information
2126
- if (chunk.usage) {
2127
- rawUsageData = chunk.usage;
2128
- usage = {
2129
- ...usage,
2130
- inputTokens: chunk.usage.prompt_tokens ?? 0,
2131
- outputTokens: chunk.usage.completion_tokens ?? 0,
2132
- reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
2133
- cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0,
2134
- cacheCreationInputTokens: extractCacheWriteTokens(chunk.usage.prompt_tokens_details)
2135
- };
2136
- }
2137
- }
2138
-
2139
- // Build final content blocks
2140
- if (accumulatedContent) {
2141
- contentBlocks.push({
2142
- type: 'text',
2143
- text: accumulatedContent
2144
- });
2121
+ accumulator.consume(chunk);
2145
2122
  }
2146
-
2147
- // Add completed tool calls to content blocks
2148
- for (const toolCall of toolCallsInProgress.values()) {
2149
- if (toolCall.name) {
2150
- contentBlocks.push({
2151
- type: 'function',
2152
- id: toolCall.id,
2153
- function: {
2154
- name: toolCall.name,
2155
- arguments: toolCall.arguments
2156
- }
2157
- });
2158
- }
2159
- }
2160
-
2161
- // Format output to match non-streaming version
2162
- const formattedOutput = contentBlocks.length > 0 ? [{
2163
- role: 'assistant',
2164
- content: contentBlocks
2165
- }] : [{
2166
- role: 'assistant',
2167
- content: [{
2168
- type: 'text',
2169
- text: ''
2170
- }]
2171
- }];
2172
- const latency = (Date.now() - startTime) / 1000;
2173
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
2174
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
2175
- await captureAiGeneration(this.phClient, {
2176
- ...posthogParams,
2177
- model: openAIParams.model ?? modelFromResponse,
2123
+ const accumulated = accumulator.result();
2124
+ await captureAiGeneration(this.phClient, buildChatSuccessOptions({
2125
+ client: this.phClient,
2178
2126
  provider: 'openai',
2179
- input: sanitizeOpenAI(openAIParams.messages),
2180
- output: sanitizeOpenAIResponse(formattedOutput),
2181
- latency,
2182
- timeToFirstToken,
2183
2127
  baseURL: this.baseURL,
2184
- modelParameters: getModelParams(body, serviceTierFromResponse),
2185
- httpStatus: 200,
2186
- usage: {
2187
- inputTokens: usage.inputTokens,
2188
- outputTokens: usage.outputTokens,
2189
- reasoningTokens: usage.reasoningTokens,
2190
- cacheReadInputTokens: usage.cacheReadInputTokens,
2191
- cacheCreationInputTokens: usage.cacheCreationInputTokens,
2192
- webSearchCount: usage.webSearchCount,
2193
- rawUsage: rawUsageData
2194
- },
2195
- stopReason,
2196
- tools: availableTools,
2197
- completionId: completionIdFromResponse,
2198
- providerMetadata: buildProviderMetadata({
2199
- systemFingerprint: systemFingerprintFromResponse
2200
- })
2201
- });
2128
+ params: openAIParams,
2129
+ monitoring: posthogParams,
2130
+ modelParametersSource: body
2131
+ }, {
2132
+ ...accumulated,
2133
+ latency: (Date.now() - startTime) / 1000,
2134
+ timeToFirstToken: accumulated.firstTokenTime === undefined ? undefined : (accumulated.firstTokenTime - startTime) / 1000
2135
+ }));
2202
2136
  } catch (error) {
2203
- await captureAiGeneration(this.phClient, {
2204
- ...posthogParams,
2205
- model: openAIParams.model,
2137
+ const accumulated = accumulator.result();
2138
+ await captureAiGeneration(this.phClient, buildChatErrorOptions({
2139
+ client: this.phClient,
2206
2140
  provider: 'openai',
2207
- input: sanitizeOpenAI(openAIParams.messages),
2208
- output: [],
2209
- latency: 0,
2210
2141
  baseURL: this.baseURL,
2211
- modelParameters: getModelParams(body),
2212
- usage: {
2213
- inputTokens: 0,
2214
- outputTokens: 0
2215
- },
2216
- // If the stream fails mid-flight, surface whatever completion
2217
- // metadata the consumed chunks already provided so the error
2218
- // event can still be correlated to OpenAI's Logs dashboard.
2219
- completionId: completionIdFromResponse,
2220
- providerMetadata: buildProviderMetadata({
2221
- systemFingerprint: systemFingerprintFromResponse
2222
- }),
2223
- error
2224
- });
2142
+ params: openAIParams,
2143
+ monitoring: posthogParams,
2144
+ modelParametersSource: body
2145
+ }, error, {
2146
+ completionId: accumulated.completionId,
2147
+ systemFingerprint: accumulated.systemFingerprint
2148
+ }));
2225
2149
  throw error;
2226
2150
  }
2227
2151
  })().catch(() => {
@@ -2238,56 +2162,35 @@ class WrappedCompletions extends Completions {
2238
2162
  } else {
2239
2163
  const wrappedPromise = parentPromise.then(async result => {
2240
2164
  if ('choices' in result) {
2241
- const latency = (Date.now() - startTime) / 1000;
2242
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
2243
- const formattedOutput = formatResponseOpenAI(result);
2244
- await captureAiGenerationAfterSuccess(this.phClient, {
2245
- ...posthogParams,
2246
- model: openAIParams.model ?? result.model,
2165
+ await captureAiGenerationAfterSuccess(this.phClient, buildChatSuccessOptions({
2166
+ client: this.phClient,
2247
2167
  provider: 'openai',
2248
- input: sanitizeOpenAI(openAIParams.messages),
2249
- output: sanitizeOpenAIResponse(formattedOutput),
2250
- latency,
2251
2168
  baseURL: this.baseURL,
2252
- modelParameters: getModelParams(body, result.service_tier),
2253
- httpStatus: 200,
2254
- usage: {
2255
- inputTokens: result.usage?.prompt_tokens ?? 0,
2256
- outputTokens: result.usage?.completion_tokens ?? 0,
2257
- reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
2258
- cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0,
2259
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.prompt_tokens_details),
2260
- webSearchCount: calculateWebSearchCount(result),
2261
- rawUsage: result.usage
2262
- },
2169
+ params: openAIParams,
2170
+ monitoring: posthogParams,
2171
+ modelParametersSource: body
2172
+ }, {
2173
+ output: formatResponseOpenAI(result),
2174
+ model: result.model,
2175
+ serviceTier: result.service_tier ?? undefined,
2176
+ latency: (Date.now() - startTime) / 1000,
2177
+ usage: buildChatUsage(result.usage, result),
2263
2178
  stopReason: result.choices[0]?.finish_reason ?? undefined,
2264
- tools: availableTools,
2265
2179
  completionId: result.id,
2266
- providerMetadata: buildProviderMetadata({
2267
- systemFingerprint: result.system_fingerprint,
2268
- requestId: extractRequestId(result)
2269
- })
2270
- });
2180
+ systemFingerprint: result.system_fingerprint,
2181
+ requestId: extractRequestId(result)
2182
+ }));
2271
2183
  }
2272
2184
  return result;
2273
2185
  }, async error => {
2274
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
2275
- await captureAiGeneration(this.phClient, {
2276
- ...posthogParams,
2277
- model: openAIParams.model,
2186
+ await captureAiGeneration(this.phClient, buildChatErrorOptions({
2187
+ client: this.phClient,
2278
2188
  provider: 'openai',
2279
- input: sanitizeOpenAI(openAIParams.messages),
2280
- output: [],
2281
- latency: 0,
2282
2189
  baseURL: this.baseURL,
2283
- modelParameters: getModelParams(body),
2284
- httpStatus,
2285
- usage: {
2286
- inputTokens: 0,
2287
- outputTokens: 0
2288
- },
2289
- error
2290
- });
2190
+ params: openAIParams,
2191
+ monitoring: posthogParams,
2192
+ modelParametersSource: body
2193
+ }, error));
2291
2194
  throw error;
2292
2195
  });
2293
2196
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -2306,36 +2209,14 @@ class WrappedResponses extends Responses {
2306
2209
  openAIParams,
2307
2210
  posthogParams
2308
2211
  } = context;
2309
- await captureAiGenerationAfterSuccess(this.phClient, {
2310
- ...posthogParams,
2311
- model: openAIParams.model ?? result.model,
2212
+ await captureAiGenerationAfterSuccess(this.phClient, buildBackgroundResponseOptions({
2213
+ client: this.phClient,
2312
2214
  provider: 'openai',
2313
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2314
- output: formatResponseOpenAI({
2315
- output: result.output
2316
- }),
2317
- latency: getBackgroundResponseLatency(result),
2318
2215
  baseURL: this.baseURL,
2319
- modelParameters: getModelParams(openAIParams, result.service_tier),
2320
- httpStatus: 200,
2321
- usage: {
2322
- inputTokens: result.usage?.input_tokens ?? 0,
2323
- outputTokens: result.usage?.output_tokens ?? 0,
2324
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
2325
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
2326
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.input_tokens_details),
2327
- webSearchCount: calculateWebSearchCount(result),
2328
- rawUsage: result.usage
2329
- },
2330
- stopReason: result.status ?? undefined,
2331
- tools: extractAvailableToolCalls('openai', openAIParams),
2332
- completionId: result.id,
2333
- providerMetadata: buildProviderMetadata({
2334
- requestId: extractRequestId(result),
2335
- incompleteDetails: result.incomplete_details
2336
- }),
2337
- error: getResponseFailure(result)
2338
- });
2216
+ params: openAIParams,
2217
+ monitoring: posthogParams,
2218
+ modelParametersSource: openAIParams
2219
+ }, result));
2339
2220
  }
2340
2221
 
2341
2222
  // --- Overload #1: Non-streaming
@@ -2357,128 +2238,61 @@ class WrappedResponses extends Responses {
2357
2238
  if (Symbol.asyncIterator in value) {
2358
2239
  const [stream1, stream2] = monitoredStreamTee(value, (iterator, controller) => new Stream(iterator, controller));
2359
2240
  (async () => {
2360
- // Hoisted so the catch block can surface the completion ID that
2361
- // was accumulated from the streamed chunks before the failure.
2362
- let completionIdFromResponse;
2241
+ const accumulator = new OpenAIResponsesStreamAccumulator();
2363
2242
  try {
2364
- let finalContent = [];
2365
- let modelFromResponse;
2366
- let serviceTierFromResponse;
2367
- let firstTokenTime;
2368
- let stopReason;
2369
- let usage = {
2370
- inputTokens: 0,
2371
- outputTokens: 0,
2372
- webSearchCount: 0
2373
- };
2374
- let rawUsageData;
2375
- let terminalResponse;
2376
2243
  for await (const chunk of stream1) {
2377
- // Track first token time on content delta events
2378
- if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
2379
- firstTokenTime = Date.now();
2380
- }
2381
- if ('response' in chunk && chunk.response) {
2382
- // Extract model and completion ID from the response object in the chunk (for stored prompts)
2383
- if (!modelFromResponse && chunk.response.model) {
2384
- modelFromResponse = chunk.response.model;
2385
- }
2386
- if (!completionIdFromResponse && chunk.response.id) {
2387
- completionIdFromResponse = chunk.response.id;
2388
- }
2389
- if (openAIParams.background === true && !this.backgroundResponses.get(chunk.response.id)) {
2390
- this.backgroundResponses.set(chunk.response.id, {
2391
- openAIParams,
2392
- posthogParams
2393
- });
2394
- }
2395
- if (chunk.response.service_tier != null) {
2396
- serviceTierFromResponse = chunk.response.service_tier;
2397
- }
2398
- const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
2399
- if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
2400
- usage.webSearchCount = chunkWebSearchCount;
2401
- }
2402
- if (isTerminalResponse(chunk.response)) {
2403
- terminalResponse = chunk.response;
2404
- finalContent = chunk.response.output ?? [];
2405
- stopReason = chunk.response.status;
2406
- }
2407
- }
2408
- if ('response' in chunk && chunk.response?.usage) {
2409
- rawUsageData = chunk.response.usage;
2410
- usage = {
2411
- ...usage,
2412
- inputTokens: chunk.response.usage.input_tokens ?? 0,
2413
- outputTokens: chunk.response.usage.output_tokens ?? 0,
2414
- reasoningTokens: chunk.response.usage.output_tokens_details?.reasoning_tokens ?? 0,
2415
- cacheReadInputTokens: chunk.response.usage.input_tokens_details?.cached_tokens ?? 0,
2416
- cacheCreationInputTokens: extractCacheWriteTokens(chunk.response.usage.input_tokens_details)
2417
- };
2244
+ accumulator.consume(chunk);
2245
+ if (openAIParams.background === true && 'response' in chunk && chunk.response && !this.backgroundResponses.get(chunk.response.id)) {
2246
+ this.backgroundResponses.set(chunk.response.id, {
2247
+ openAIParams,
2248
+ posthogParams
2249
+ });
2418
2250
  }
2419
2251
  }
2252
+ const accumulated = accumulator.result();
2420
2253
  if (openAIParams.background === true) {
2421
- if (terminalResponse) {
2422
- const context = this.backgroundResponses.take(terminalResponse.id);
2254
+ if (accumulated.terminalResponse) {
2255
+ const context = this.backgroundResponses.take(accumulated.terminalResponse.id);
2423
2256
  if (context) {
2424
- await this.captureBackgroundResponse(terminalResponse, context).catch(() => undefined);
2257
+ await this.captureBackgroundResponse(accumulated.terminalResponse, context).catch(() => undefined);
2425
2258
  }
2426
2259
  }
2427
2260
  return;
2428
2261
  }
2429
- const latency = (Date.now() - startTime) / 1000;
2430
- const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
2431
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
2432
- await captureAiGeneration(this.phClient, {
2433
- ...posthogParams,
2434
- model: openAIParams.model ?? modelFromResponse,
2262
+ const response = accumulated.terminalResponse ?? {
2263
+ id: accumulated.completionId ?? '',
2264
+ model: accumulated.model ?? openAIParams.model,
2265
+ status: accumulated.stopReason,
2266
+ service_tier: accumulated.serviceTier
2267
+ };
2268
+ await captureAiGeneration(this.phClient, buildResponsesSuccessOptions({
2269
+ client: this.phClient,
2435
2270
  provider: 'openai',
2436
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2437
- output: sanitizeOpenAIResponse(finalContent),
2438
- latency,
2439
- timeToFirstToken,
2440
2271
  baseURL: this.baseURL,
2441
- modelParameters: getModelParams(body, serviceTierFromResponse),
2442
- httpStatus: 200,
2443
- usage: {
2444
- inputTokens: usage.inputTokens,
2445
- outputTokens: usage.outputTokens,
2446
- reasoningTokens: usage.reasoningTokens,
2447
- cacheReadInputTokens: usage.cacheReadInputTokens,
2448
- cacheCreationInputTokens: usage.cacheCreationInputTokens,
2449
- webSearchCount: usage.webSearchCount,
2450
- rawUsage: rawUsageData
2451
- },
2452
- stopReason,
2453
- tools: availableTools,
2454
- completionId: completionIdFromResponse,
2455
- providerMetadata: buildProviderMetadata({
2456
- incompleteDetails: terminalResponse?.incomplete_details
2457
- }),
2458
- error: getResponseFailure(terminalResponse)
2459
- });
2272
+ params: openAIParams,
2273
+ monitoring: posthogParams,
2274
+ modelParametersSource: body
2275
+ }, {
2276
+ response,
2277
+ output: accumulated.output,
2278
+ latency: (Date.now() - startTime) / 1000,
2279
+ timeToFirstToken: accumulated.firstTokenTime === undefined ? undefined : (accumulated.firstTokenTime - startTime) / 1000,
2280
+ usage: accumulated.usage,
2281
+ includeTools: true
2282
+ }));
2460
2283
  } catch (error) {
2461
- if (openAIParams.background === true && completionIdFromResponse && this.backgroundResponses.get(completionIdFromResponse)) {
2284
+ const accumulated = accumulator.result();
2285
+ if (openAIParams.background === true && accumulated.completionId && this.backgroundResponses.get(accumulated.completionId)) {
2462
2286
  throw error;
2463
2287
  }
2464
- await captureAiGeneration(this.phClient, {
2465
- ...posthogParams,
2466
- model: openAIParams.model,
2288
+ await captureAiGeneration(this.phClient, buildResponsesErrorOptions({
2289
+ client: this.phClient,
2467
2290
  provider: 'openai',
2468
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2469
- output: [],
2470
- latency: 0,
2471
2291
  baseURL: this.baseURL,
2472
- modelParameters: getModelParams(body),
2473
- usage: {
2474
- inputTokens: 0,
2475
- outputTokens: 0
2476
- },
2477
- // Surface the completion ID from any chunks consumed before
2478
- // the stream failed so the error event remains correlatable.
2479
- completionId: completionIdFromResponse,
2480
- error
2481
- });
2292
+ params: openAIParams,
2293
+ monitoring: posthogParams,
2294
+ modelParametersSource: body
2295
+ }, error, accumulated.completionId));
2482
2296
  throw error;
2483
2297
  }
2484
2298
  })().catch(() => {
@@ -2500,59 +2314,33 @@ class WrappedResponses extends Responses {
2500
2314
  });
2501
2315
  return result;
2502
2316
  }
2503
- const latency = (Date.now() - startTime) / 1000;
2504
- const availableTools = extractAvailableToolCalls('openai', openAIParams);
2505
- const formattedOutput = formatResponseOpenAI({
2506
- output: result.output
2507
- });
2508
- await captureAiGenerationAfterSuccess(this.phClient, {
2509
- ...posthogParams,
2510
- model: openAIParams.model ?? result.model,
2317
+ await captureAiGenerationAfterSuccess(this.phClient, buildResponsesSuccessOptions({
2318
+ client: this.phClient,
2511
2319
  provider: 'openai',
2512
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2513
- output: sanitizeOpenAIResponse(formattedOutput),
2514
- latency,
2515
2320
  baseURL: this.baseURL,
2516
- modelParameters: getModelParams(body, result.service_tier),
2517
- httpStatus: 200,
2518
- usage: {
2519
- inputTokens: result.usage?.input_tokens ?? 0,
2520
- outputTokens: result.usage?.output_tokens ?? 0,
2521
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
2522
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
2523
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.input_tokens_details),
2524
- webSearchCount: calculateWebSearchCount(result),
2525
- rawUsage: result.usage
2526
- },
2527
- stopReason: result.status ?? undefined,
2528
- tools: availableTools,
2529
- completionId: result.id,
2530
- providerMetadata: buildProviderMetadata({
2531
- requestId: extractRequestId(result),
2532
- incompleteDetails: result.incomplete_details
2321
+ params: openAIParams,
2322
+ monitoring: posthogParams,
2323
+ modelParametersSource: body
2324
+ }, {
2325
+ response: result,
2326
+ output: formatResponseOpenAI({
2327
+ output: result.output
2533
2328
  }),
2534
- error: getResponseFailure(result)
2535
- });
2329
+ latency: (Date.now() - startTime) / 1000,
2330
+ includeTools: true,
2331
+ includeRequestId: true
2332
+ }));
2536
2333
  }
2537
2334
  return result;
2538
2335
  }, async error => {
2539
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
2540
- await captureAiGeneration(this.phClient, {
2541
- ...posthogParams,
2542
- model: openAIParams.model,
2336
+ await captureAiGeneration(this.phClient, buildResponsesErrorOptions({
2337
+ client: this.phClient,
2543
2338
  provider: 'openai',
2544
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2545
- output: [],
2546
- latency: 0,
2547
2339
  baseURL: this.baseURL,
2548
- modelParameters: getModelParams(body),
2549
- httpStatus,
2550
- usage: {
2551
- inputTokens: 0,
2552
- outputTokens: 0
2553
- },
2554
- error
2555
- });
2340
+ params: openAIParams,
2341
+ monitoring: posthogParams,
2342
+ modelParametersSource: body
2343
+ }, error));
2556
2344
  throw error;
2557
2345
  });
2558
2346
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -2622,50 +2410,29 @@ class WrappedResponses extends Responses {
2622
2410
  });
2623
2411
  return result;
2624
2412
  }
2625
- const latency = (Date.now() - startTime) / 1000;
2626
- await captureAiGeneration(this.phClient, {
2627
- ...posthogParams,
2628
- model: openAIParams.model ?? result.model,
2413
+ await captureAiGeneration(this.phClient, buildResponsesSuccessOptions({
2414
+ client: this.phClient,
2629
2415
  provider: 'openai',
2630
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2631
- output: sanitizeOpenAIResponse(result.output),
2632
- latency,
2633
2416
  baseURL: this.baseURL,
2634
- modelParameters: getModelParams(body, result.service_tier),
2635
- httpStatus: 200,
2636
- usage: {
2637
- inputTokens: result.usage?.input_tokens ?? 0,
2638
- outputTokens: result.usage?.output_tokens ?? 0,
2639
- reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
2640
- cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
2641
- cacheCreationInputTokens: extractCacheWriteTokens(result.usage?.input_tokens_details),
2642
- rawUsage: result.usage
2643
- },
2644
- stopReason: result.status ?? undefined,
2645
- completionId: result.id,
2646
- providerMetadata: buildProviderMetadata({
2647
- requestId: extractRequestId(result),
2648
- incompleteDetails: result.incomplete_details
2649
- }),
2650
- error: getResponseFailure(result)
2651
- });
2417
+ params: openAIParams,
2418
+ monitoring: posthogParams,
2419
+ modelParametersSource: body
2420
+ }, {
2421
+ response: result,
2422
+ output: result.output,
2423
+ latency: (Date.now() - startTime) / 1000,
2424
+ includeRequestId: true
2425
+ }));
2652
2426
  return result;
2653
2427
  }, async error => {
2654
- await captureAiGeneration(this.phClient, {
2655
- ...posthogParams,
2656
- model: openAIParams.model,
2428
+ await captureAiGeneration(this.phClient, buildResponsesErrorOptions({
2429
+ client: this.phClient,
2657
2430
  provider: 'openai',
2658
- input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2659
- output: [],
2660
- latency: 0,
2661
2431
  baseURL: this.baseURL,
2662
- modelParameters: getModelParams(body),
2663
- usage: {
2664
- inputTokens: 0,
2665
- outputTokens: 0
2666
- },
2667
- error
2668
- });
2432
+ params: openAIParams,
2433
+ monitoring: posthogParams,
2434
+ modelParametersSource: body
2435
+ }, error));
2669
2436
  throw error;
2670
2437
  });
2671
2438
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -2685,44 +2452,24 @@ class WrappedEmbeddings extends Embeddings {
2685
2452
  const startTime = Date.now();
2686
2453
  const parentPromise = super.create(openAIParams, options);
2687
2454
  const wrappedPromise = parentPromise.then(async result => {
2688
- const latency = (Date.now() - startTime) / 1000;
2689
- await captureAiGeneration(this.phClient, {
2690
- ...posthogParams,
2691
- eventType: AIEvent.Embedding,
2692
- model: openAIParams.model,
2455
+ await captureAiGeneration(this.phClient, buildEmbeddingSuccessOptions({
2456
+ client: this.phClient,
2693
2457
  provider: 'openai',
2694
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
2695
- output: null,
2696
- // Embeddings don't have output content
2697
- latency,
2698
2458
  baseURL: this.baseURL,
2699
- modelParameters: getModelParams(body),
2700
- httpStatus: 200,
2701
- usage: {
2702
- inputTokens: result.usage?.prompt_tokens ?? 0,
2703
- rawUsage: result.usage
2704
- }
2705
- });
2459
+ params: openAIParams,
2460
+ monitoring: posthogParams,
2461
+ modelParametersSource: body
2462
+ }, result.usage, (Date.now() - startTime) / 1000));
2706
2463
  return result;
2707
2464
  }, async error => {
2708
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
2709
- await captureAiGeneration(this.phClient, {
2710
- eventType: AIEvent.Embedding,
2711
- ...posthogParams,
2712
- model: openAIParams.model,
2465
+ await captureAiGeneration(this.phClient, buildEmbeddingErrorOptions({
2466
+ client: this.phClient,
2713
2467
  provider: 'openai',
2714
- input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
2715
- output: null,
2716
- // Embeddings don't have output content
2717
- latency: 0,
2718
2468
  baseURL: this.baseURL,
2719
- modelParameters: getModelParams(body),
2720
- httpStatus,
2721
- usage: {
2722
- inputTokens: 0
2723
- },
2724
- error
2725
- });
2469
+ params: openAIParams,
2470
+ monitoring: posthogParams,
2471
+ modelParametersSource: body
2472
+ }, error));
2726
2473
  throw error;
2727
2474
  });
2728
2475
  return preserveProviderPromise(parentPromise, wrappedPromise);
@@ -2800,7 +2547,7 @@ class WrappedTranscriptions extends Transcriptions {
2800
2547
  model: openAIParams.model,
2801
2548
  provider: 'openai',
2802
2549
  input: openAIParams.prompt,
2803
- output: sanitizeOpenAIResponse(finalContent),
2550
+ output: sanitizeOpenAIResponse(finalContent, this.phClient),
2804
2551
  latency,
2805
2552
  timeToFirstToken,
2806
2553
  baseURL: this.baseURL,
@@ -2845,7 +2592,7 @@ class WrappedTranscriptions extends Transcriptions {
2845
2592
  model: openAIParams.model,
2846
2593
  provider: 'openai',
2847
2594
  input: openAIParams.prompt,
2848
- output: sanitizeOpenAIResponse(result.text),
2595
+ output: sanitizeOpenAIResponse(result.text, this.phClient),
2849
2596
  latency,
2850
2597
  baseURL: this.baseURL,
2851
2598
  modelParameters: getModelParams(body),