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