@posthog/ai 8.6.4 → 8.6.5

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.
@@ -41,14 +41,16 @@ const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'f
41
41
  const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
42
42
  class MediaTypeContext {
43
43
  static EMPTY = new MediaTypeContext(undefined, undefined);
44
- constructor(parent, key) {
44
+ constructor(parent, key, explicitMediaType) {
45
45
  this.parent = parent;
46
46
  this.key = key;
47
+ this.explicitMediaType = explicitMediaType;
47
48
  }
48
49
  inferMediaType() {
49
50
  return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
50
51
  }
51
52
  inferFromSiblingMime() {
53
+ if (this.explicitMediaType) return this.explicitMediaType;
52
54
  if (!this.parent) return undefined;
53
55
  for (const hint of MIME_HINT_KEYS) {
54
56
  const v = this.parent[hint];
@@ -83,7 +85,13 @@ class MediaTypeContext {
83
85
  if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
84
86
  return undefined;
85
87
  }
88
+ hasExplicitBinaryMediaType() {
89
+ if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
90
+ const mediaType = this.inferFromSiblingMime();
91
+ return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/');
92
+ }
86
93
  signalsBinary() {
94
+ if (this.explicitMediaType) return true;
87
95
  if (this.parent) {
88
96
  for (const hint of MIME_HINT_KEYS) {
89
97
  if (typeof this.parent[hint] === 'string') return true;
@@ -105,10 +113,10 @@ class BinaryContentRedactor {
105
113
  constructor(recognizer = new Base64Recognizer()) {
106
114
  this.recognizer = recognizer;
107
115
  }
108
- redact(value) {
116
+ redact(value, mediaType) {
109
117
  if (this.isMultimodalEnabled()) return value;
110
118
  this.visited = new WeakSet();
111
- return this.walk(value, MediaTypeContext.EMPTY);
119
+ return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
112
120
  }
113
121
  walk(value, ctx) {
114
122
  if (value === null || value === undefined) return value;
@@ -132,8 +140,10 @@ class BinaryContentRedactor {
132
140
  return out;
133
141
  }
134
142
  redactString(value, ctx) {
135
- const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
136
- const recognition = this.recognizer.recognize(value, minLength);
143
+ const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
144
+ const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, '') : value;
145
+ const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
146
+ const recognition = this.recognizer.recognize(recognitionValue, minLength);
137
147
  switch (recognition.kind) {
138
148
  case 'data-url':
139
149
  return this.placeholderFor(recognition.mediaType);
@@ -534,7 +544,7 @@ function formatOpenAIResponsesInput(input, instructions) {
534
544
  return messages;
535
545
  }
536
546
 
537
- var version = "8.6.4";
547
+ var version = "8.6.5";
538
548
 
539
549
  const DEFAULT_MAX_DEPTH = 3;
540
550
  const MAX_STACK_LINES = 20;
@@ -834,7 +844,7 @@ function extractRequestId(result) {
834
844
  * Assembles the `$ai_provider_metadata` blob for OpenAI / Azure OpenAI events.
835
845
  * Provider-specific fields (system fingerprint, request id) live here rather
836
846
  * than in the shared, provider-agnostic `$ai_*` namespace. Only keys with a
837
- * truthy value are included, and `undefined` is returned when there is nothing
847
+ * meaningful value are included, and `undefined` is returned when there is nothing
838
848
  * to report so the property can be omitted from the event entirely.
839
849
  */
840
850
  function buildProviderMetadata(fields) {
@@ -845,8 +855,33 @@ function buildProviderMetadata(fields) {
845
855
  if (fields.requestId) {
846
856
  metadata.request_id = fields.requestId;
847
857
  }
858
+ if (fields.incompleteDetails != null) {
859
+ metadata.incomplete_details = fields.incompleteDetails;
860
+ }
848
861
  return Object.keys(metadata).length > 0 ? metadata : undefined;
849
862
  }
863
+ const TERMINAL_RESPONSE_STATUSES = new Set(['completed', 'failed', 'cancelled', 'incomplete']);
864
+
865
+ /**
866
+ * Checks whether a Responses API response has reached a status that should
867
+ * produce a final `$ai_generation` event.
868
+ */
869
+ function isTerminalResponse(response) {
870
+ return !!response?.status && TERMINAL_RESPONSE_STATUSES.has(response.status);
871
+ }
872
+
873
+ /**
874
+ * Returns an isolated copy of a failed Responses API error for `$ai_error`, or
875
+ * creates a fallback error when the provider omitted failure details.
876
+ */
877
+ function getResponseFailure(response) {
878
+ if (response?.status !== 'failed') {
879
+ return undefined;
880
+ }
881
+ return response.error ? {
882
+ ...response.error
883
+ } : new Error(`OpenAI response ${response.id} failed without error details`);
884
+ }
850
885
 
851
886
  function addRequestId(result, response, requestIdHeader) {
852
887
  if (!result || typeof result !== 'object' || Array.isArray(result)) {
@@ -1326,7 +1361,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends AzureOpenAI.Chat.Com
1326
1361
  model: openAIParams.model ?? modelFromResponse,
1327
1362
  provider: 'azure',
1328
1363
  input: sanitizeOpenAI(openAIParams.messages),
1329
- output: formattedOutput,
1364
+ output: sanitizeOpenAIResponse(formattedOutput),
1330
1365
  latency,
1331
1366
  timeToFirstToken,
1332
1367
  baseURL: this.baseURL,
@@ -1382,8 +1417,8 @@ let WrappedCompletions$1 = class WrappedCompletions extends AzureOpenAI.Chat.Com
1382
1417
  ...posthogParams,
1383
1418
  model: openAIParams.model ?? result.model,
1384
1419
  provider: 'azure',
1385
- input: openAIParams.messages,
1386
- output: formatResponseOpenAI(result),
1420
+ input: sanitizeOpenAI(openAIParams.messages),
1421
+ output: sanitizeOpenAIResponse(formatResponseOpenAI(result)),
1387
1422
  latency,
1388
1423
  baseURL: this.baseURL,
1389
1424
  modelParameters: getModelParams(body, result.service_tier),
@@ -1408,7 +1443,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends AzureOpenAI.Chat.Com
1408
1443
  ...posthogParams,
1409
1444
  model: openAIParams.model,
1410
1445
  provider: 'azure',
1411
- input: openAIParams.messages,
1446
+ input: sanitizeOpenAI(openAIParams.messages),
1412
1447
  output: [],
1413
1448
  latency: 0,
1414
1449
  baseURL: this.baseURL,
@@ -1464,6 +1499,7 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1464
1499
  inputTokens: 0,
1465
1500
  outputTokens: 0
1466
1501
  };
1502
+ let terminalResponse;
1467
1503
  for await (const chunk of stream1) {
1468
1504
  // Track first token time on content delta events
1469
1505
  if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
@@ -1480,9 +1516,10 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1480
1516
  if (chunk.response.service_tier != null) {
1481
1517
  serviceTierFromResponse = chunk.response.service_tier;
1482
1518
  }
1483
- }
1484
- if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1485
- finalContent = chunk.response.output;
1519
+ if (isTerminalResponse(chunk.response)) {
1520
+ terminalResponse = chunk.response;
1521
+ finalContent = chunk.response.output ?? [];
1522
+ }
1486
1523
  }
1487
1524
  if ('response' in chunk && chunk.response?.usage) {
1488
1525
  usage = {
@@ -1499,22 +1536,27 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1499
1536
  ...posthogParams,
1500
1537
  model: openAIParams.model ?? modelFromResponse,
1501
1538
  provider: 'azure',
1502
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1503
- output: finalContent,
1539
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1540
+ output: sanitizeOpenAIResponse(finalContent),
1504
1541
  latency,
1505
1542
  timeToFirstToken,
1506
1543
  baseURL: this.baseURL,
1507
1544
  modelParameters: getModelParams(body, serviceTierFromResponse),
1508
1545
  httpStatus: 200,
1509
1546
  usage,
1510
- completionId: completionIdFromResponse
1547
+ stopReason: terminalResponse?.status ?? undefined,
1548
+ completionId: completionIdFromResponse,
1549
+ providerMetadata: buildProviderMetadata({
1550
+ incompleteDetails: terminalResponse?.incomplete_details
1551
+ }),
1552
+ error: getResponseFailure(terminalResponse)
1511
1553
  });
1512
1554
  } catch (error) {
1513
1555
  await captureAiGeneration(this.phClient, {
1514
1556
  ...posthogParams,
1515
1557
  model: openAIParams.model,
1516
1558
  provider: 'azure',
1517
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1559
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1518
1560
  output: [],
1519
1561
  latency: 0,
1520
1562
  baseURL: this.baseURL,
@@ -1547,8 +1589,8 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1547
1589
  ...posthogParams,
1548
1590
  model: openAIParams.model ?? result.model,
1549
1591
  provider: 'azure',
1550
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1551
- output: result.output,
1592
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1593
+ output: sanitizeOpenAIResponse(result.output),
1552
1594
  latency,
1553
1595
  baseURL: this.baseURL,
1554
1596
  modelParameters: getModelParams(body, result.service_tier),
@@ -1559,10 +1601,13 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1559
1601
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1560
1602
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1561
1603
  },
1604
+ stopReason: result.status ?? undefined,
1562
1605
  completionId: result.id,
1563
1606
  providerMetadata: buildProviderMetadata({
1564
- requestId: extractRequestId(result)
1565
- })
1607
+ requestId: extractRequestId(result),
1608
+ incompleteDetails: result.incomplete_details
1609
+ }),
1610
+ error: getResponseFailure(result)
1566
1611
  });
1567
1612
  }
1568
1613
  return result;
@@ -1572,7 +1617,7 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1572
1617
  ...posthogParams,
1573
1618
  model: openAIParams.model,
1574
1619
  provider: 'azure',
1575
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1620
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1576
1621
  output: [],
1577
1622
  latency: 0,
1578
1623
  baseURL: this.baseURL,
@@ -1602,8 +1647,8 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1602
1647
  ...posthogParams,
1603
1648
  model: openAIParams.model ?? result.model,
1604
1649
  provider: 'azure',
1605
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1606
- output: result.output,
1650
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1651
+ output: sanitizeOpenAIResponse(result.output),
1607
1652
  latency,
1608
1653
  baseURL: this.baseURL,
1609
1654
  modelParameters: getModelParams(body, result.service_tier),
@@ -1614,10 +1659,13 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1614
1659
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1615
1660
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1616
1661
  },
1662
+ stopReason: result.status ?? undefined,
1617
1663
  completionId: result.id,
1618
1664
  providerMetadata: buildProviderMetadata({
1619
- requestId: extractRequestId(result)
1620
- })
1665
+ requestId: extractRequestId(result),
1666
+ incompleteDetails: result.incomplete_details
1667
+ }),
1668
+ error: getResponseFailure(result)
1621
1669
  });
1622
1670
  return result;
1623
1671
  }, async error => {
@@ -1625,7 +1673,7 @@ let WrappedResponses$1 = class WrappedResponses extends AzureOpenAI.Responses {
1625
1673
  ...posthogParams,
1626
1674
  model: openAIParams.model,
1627
1675
  provider: 'azure',
1628
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1676
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1629
1677
  output: [],
1630
1678
  latency: 0,
1631
1679
  baseURL: this.baseURL,
@@ -1715,9 +1763,8 @@ async function captureAiGenerationAfterSuccess(...args) {
1715
1763
  captureAiGenerationInBackground(...args);
1716
1764
  }
1717
1765
  }
1718
- const TERMINAL_RESPONSE_STATUSES = new Set(['completed', 'failed', 'cancelled', 'incomplete']);
1719
1766
  function isPendingBackgroundResponse(params, response) {
1720
- return params.background === true && !response.usage && !!response.status && !TERMINAL_RESPONSE_STATUSES.has(response.status);
1767
+ return params.background === true && !response.usage && !!response.status && !isTerminalResponse(response);
1721
1768
  }
1722
1769
  class PostHogOpenAI extends OpenAI {
1723
1770
  constructor(config) {
@@ -1905,7 +1952,7 @@ class WrappedCompletions extends Completions {
1905
1952
  model: openAIParams.model ?? modelFromResponse,
1906
1953
  provider: 'openai',
1907
1954
  input: sanitizeOpenAI(openAIParams.messages),
1908
- output: formattedOutput,
1955
+ output: sanitizeOpenAIResponse(formattedOutput),
1909
1956
  latency,
1910
1957
  timeToFirstToken,
1911
1958
  baseURL: this.baseURL,
@@ -1973,7 +2020,7 @@ class WrappedCompletions extends Completions {
1973
2020
  model: openAIParams.model ?? result.model,
1974
2021
  provider: 'openai',
1975
2022
  input: sanitizeOpenAI(openAIParams.messages),
1976
- output: formattedOutput,
2023
+ output: sanitizeOpenAIResponse(formattedOutput),
1977
2024
  latency,
1978
2025
  baseURL: this.baseURL,
1979
2026
  modelParameters: getModelParams(body, result.service_tier),
@@ -2061,6 +2108,7 @@ class WrappedResponses extends Responses {
2061
2108
  webSearchCount: 0
2062
2109
  };
2063
2110
  let rawUsageData;
2111
+ let terminalResponse;
2064
2112
  for await (const chunk of stream1) {
2065
2113
  // Track first token time on content delta events
2066
2114
  if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
@@ -2081,10 +2129,9 @@ class WrappedResponses extends Responses {
2081
2129
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
2082
2130
  usage.webSearchCount = chunkWebSearchCount;
2083
2131
  }
2084
- }
2085
- if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
2086
- finalContent = chunk.response.output;
2087
- if (chunk.response.status) {
2132
+ if (isTerminalResponse(chunk.response)) {
2133
+ terminalResponse = chunk.response;
2134
+ finalContent = chunk.response.output ?? [];
2088
2135
  stopReason = chunk.response.status;
2089
2136
  }
2090
2137
  }
@@ -2107,7 +2154,7 @@ class WrappedResponses extends Responses {
2107
2154
  model: openAIParams.model ?? modelFromResponse,
2108
2155
  provider: 'openai',
2109
2156
  input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2110
- output: finalContent,
2157
+ output: sanitizeOpenAIResponse(finalContent),
2111
2158
  latency,
2112
2159
  timeToFirstToken,
2113
2160
  baseURL: this.baseURL,
@@ -2123,7 +2170,11 @@ class WrappedResponses extends Responses {
2123
2170
  },
2124
2171
  stopReason,
2125
2172
  tools: availableTools,
2126
- completionId: completionIdFromResponse
2173
+ completionId: completionIdFromResponse,
2174
+ providerMetadata: buildProviderMetadata({
2175
+ incompleteDetails: terminalResponse?.incomplete_details
2176
+ }),
2177
+ error: getResponseFailure(terminalResponse)
2127
2178
  });
2128
2179
  } catch (error) {
2129
2180
  await captureAiGeneration(this.phClient, {
@@ -2171,7 +2222,7 @@ class WrappedResponses extends Responses {
2171
2222
  model: openAIParams.model ?? result.model,
2172
2223
  provider: 'openai',
2173
2224
  input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2174
- output: formattedOutput,
2225
+ output: sanitizeOpenAIResponse(formattedOutput),
2175
2226
  latency,
2176
2227
  baseURL: this.baseURL,
2177
2228
  modelParameters: getModelParams(body, result.service_tier),
@@ -2188,8 +2239,10 @@ class WrappedResponses extends Responses {
2188
2239
  tools: availableTools,
2189
2240
  completionId: result.id,
2190
2241
  providerMetadata: buildProviderMetadata({
2191
- requestId: extractRequestId(result)
2192
- })
2242
+ requestId: extractRequestId(result),
2243
+ incompleteDetails: result.incomplete_details
2244
+ }),
2245
+ error: getResponseFailure(result)
2193
2246
  });
2194
2247
  }
2195
2248
  return result;
@@ -2233,7 +2286,7 @@ class WrappedResponses extends Responses {
2233
2286
  model: openAIParams.model ?? result.model,
2234
2287
  provider: 'openai',
2235
2288
  input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2236
- output: result.output,
2289
+ output: sanitizeOpenAIResponse(result.output),
2237
2290
  latency,
2238
2291
  baseURL: this.baseURL,
2239
2292
  modelParameters: getModelParams(body, result.service_tier),
@@ -2248,8 +2301,10 @@ class WrappedResponses extends Responses {
2248
2301
  stopReason: result.status ?? undefined,
2249
2302
  completionId: result.id,
2250
2303
  providerMetadata: buildProviderMetadata({
2251
- requestId: extractRequestId(result)
2252
- })
2304
+ requestId: extractRequestId(result),
2305
+ incompleteDetails: result.incomplete_details
2306
+ }),
2307
+ error: getResponseFailure(result)
2253
2308
  });
2254
2309
  return result;
2255
2310
  }, async error => {
@@ -2402,7 +2457,7 @@ class WrappedTranscriptions extends Transcriptions {
2402
2457
  model: openAIParams.model,
2403
2458
  provider: 'openai',
2404
2459
  input: openAIParams.prompt,
2405
- output: finalContent,
2460
+ output: sanitizeOpenAIResponse(finalContent),
2406
2461
  latency,
2407
2462
  timeToFirstToken,
2408
2463
  baseURL: this.baseURL,
@@ -2447,7 +2502,7 @@ class WrappedTranscriptions extends Transcriptions {
2447
2502
  model: openAIParams.model,
2448
2503
  provider: 'openai',
2449
2504
  input: openAIParams.prompt,
2450
- output: result.text,
2505
+ output: sanitizeOpenAIResponse(result.text),
2451
2506
  latency,
2452
2507
  baseURL: this.baseURL,
2453
2508
  modelParameters: getModelParams(body),