@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.
@@ -45,14 +45,16 @@ const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'f
45
45
  const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
46
46
  class MediaTypeContext {
47
47
  static EMPTY = new MediaTypeContext(undefined, undefined);
48
- constructor(parent, key) {
48
+ constructor(parent, key, explicitMediaType) {
49
49
  this.parent = parent;
50
50
  this.key = key;
51
+ this.explicitMediaType = explicitMediaType;
51
52
  }
52
53
  inferMediaType() {
53
54
  return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
54
55
  }
55
56
  inferFromSiblingMime() {
57
+ if (this.explicitMediaType) return this.explicitMediaType;
56
58
  if (!this.parent) return undefined;
57
59
  for (const hint of MIME_HINT_KEYS) {
58
60
  const v = this.parent[hint];
@@ -87,7 +89,13 @@ class MediaTypeContext {
87
89
  if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
88
90
  return undefined;
89
91
  }
92
+ hasExplicitBinaryMediaType() {
93
+ if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
94
+ const mediaType = this.inferFromSiblingMime();
95
+ return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/');
96
+ }
90
97
  signalsBinary() {
98
+ if (this.explicitMediaType) return true;
91
99
  if (this.parent) {
92
100
  for (const hint of MIME_HINT_KEYS) {
93
101
  if (typeof this.parent[hint] === 'string') return true;
@@ -109,10 +117,10 @@ class BinaryContentRedactor {
109
117
  constructor(recognizer = new Base64Recognizer()) {
110
118
  this.recognizer = recognizer;
111
119
  }
112
- redact(value) {
120
+ redact(value, mediaType) {
113
121
  if (this.isMultimodalEnabled()) return value;
114
122
  this.visited = new WeakSet();
115
- return this.walk(value, MediaTypeContext.EMPTY);
123
+ return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
116
124
  }
117
125
  walk(value, ctx) {
118
126
  if (value === null || value === undefined) return value;
@@ -136,8 +144,10 @@ class BinaryContentRedactor {
136
144
  return out;
137
145
  }
138
146
  redactString(value, ctx) {
139
- const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
140
- const recognition = this.recognizer.recognize(value, minLength);
147
+ const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
148
+ const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, '') : value;
149
+ const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
150
+ const recognition = this.recognizer.recognize(recognitionValue, minLength);
141
151
  switch (recognition.kind) {
142
152
  case 'data-url':
143
153
  return this.placeholderFor(recognition.mediaType);
@@ -538,7 +548,7 @@ function formatOpenAIResponsesInput(input, instructions) {
538
548
  return messages;
539
549
  }
540
550
 
541
- var version = "8.6.4";
551
+ var version = "8.6.5";
542
552
 
543
553
  const DEFAULT_MAX_DEPTH = 3;
544
554
  const MAX_STACK_LINES = 20;
@@ -838,7 +848,7 @@ function extractRequestId(result) {
838
848
  * Assembles the `$ai_provider_metadata` blob for OpenAI / Azure OpenAI events.
839
849
  * Provider-specific fields (system fingerprint, request id) live here rather
840
850
  * than in the shared, provider-agnostic `$ai_*` namespace. Only keys with a
841
- * truthy value are included, and `undefined` is returned when there is nothing
851
+ * meaningful value are included, and `undefined` is returned when there is nothing
842
852
  * to report so the property can be omitted from the event entirely.
843
853
  */
844
854
  function buildProviderMetadata(fields) {
@@ -849,8 +859,33 @@ function buildProviderMetadata(fields) {
849
859
  if (fields.requestId) {
850
860
  metadata.request_id = fields.requestId;
851
861
  }
862
+ if (fields.incompleteDetails != null) {
863
+ metadata.incomplete_details = fields.incompleteDetails;
864
+ }
852
865
  return Object.keys(metadata).length > 0 ? metadata : undefined;
853
866
  }
867
+ const TERMINAL_RESPONSE_STATUSES = new Set(['completed', 'failed', 'cancelled', 'incomplete']);
868
+
869
+ /**
870
+ * Checks whether a Responses API response has reached a status that should
871
+ * produce a final `$ai_generation` event.
872
+ */
873
+ function isTerminalResponse(response) {
874
+ return !!response?.status && TERMINAL_RESPONSE_STATUSES.has(response.status);
875
+ }
876
+
877
+ /**
878
+ * Returns an isolated copy of a failed Responses API error for `$ai_error`, or
879
+ * creates a fallback error when the provider omitted failure details.
880
+ */
881
+ function getResponseFailure(response) {
882
+ if (response?.status !== 'failed') {
883
+ return undefined;
884
+ }
885
+ return response.error ? {
886
+ ...response.error
887
+ } : new Error(`OpenAI response ${response.id} failed without error details`);
888
+ }
854
889
 
855
890
  function addRequestId(result, response, requestIdHeader) {
856
891
  if (!result || typeof result !== 'object' || Array.isArray(result)) {
@@ -1330,7 +1365,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends openai.AzureOpenAI.C
1330
1365
  model: openAIParams.model ?? modelFromResponse,
1331
1366
  provider: 'azure',
1332
1367
  input: sanitizeOpenAI(openAIParams.messages),
1333
- output: formattedOutput,
1368
+ output: sanitizeOpenAIResponse(formattedOutput),
1334
1369
  latency,
1335
1370
  timeToFirstToken,
1336
1371
  baseURL: this.baseURL,
@@ -1386,8 +1421,8 @@ let WrappedCompletions$1 = class WrappedCompletions extends openai.AzureOpenAI.C
1386
1421
  ...posthogParams,
1387
1422
  model: openAIParams.model ?? result.model,
1388
1423
  provider: 'azure',
1389
- input: openAIParams.messages,
1390
- output: formatResponseOpenAI(result),
1424
+ input: sanitizeOpenAI(openAIParams.messages),
1425
+ output: sanitizeOpenAIResponse(formatResponseOpenAI(result)),
1391
1426
  latency,
1392
1427
  baseURL: this.baseURL,
1393
1428
  modelParameters: getModelParams(body, result.service_tier),
@@ -1412,7 +1447,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends openai.AzureOpenAI.C
1412
1447
  ...posthogParams,
1413
1448
  model: openAIParams.model,
1414
1449
  provider: 'azure',
1415
- input: openAIParams.messages,
1450
+ input: sanitizeOpenAI(openAIParams.messages),
1416
1451
  output: [],
1417
1452
  latency: 0,
1418
1453
  baseURL: this.baseURL,
@@ -1468,6 +1503,7 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1468
1503
  inputTokens: 0,
1469
1504
  outputTokens: 0
1470
1505
  };
1506
+ let terminalResponse;
1471
1507
  for await (const chunk of stream1) {
1472
1508
  // Track first token time on content delta events
1473
1509
  if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
@@ -1484,9 +1520,10 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1484
1520
  if (chunk.response.service_tier != null) {
1485
1521
  serviceTierFromResponse = chunk.response.service_tier;
1486
1522
  }
1487
- }
1488
- if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1489
- finalContent = chunk.response.output;
1523
+ if (isTerminalResponse(chunk.response)) {
1524
+ terminalResponse = chunk.response;
1525
+ finalContent = chunk.response.output ?? [];
1526
+ }
1490
1527
  }
1491
1528
  if ('response' in chunk && chunk.response?.usage) {
1492
1529
  usage = {
@@ -1503,22 +1540,27 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1503
1540
  ...posthogParams,
1504
1541
  model: openAIParams.model ?? modelFromResponse,
1505
1542
  provider: 'azure',
1506
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1507
- output: finalContent,
1543
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1544
+ output: sanitizeOpenAIResponse(finalContent),
1508
1545
  latency,
1509
1546
  timeToFirstToken,
1510
1547
  baseURL: this.baseURL,
1511
1548
  modelParameters: getModelParams(body, serviceTierFromResponse),
1512
1549
  httpStatus: 200,
1513
1550
  usage,
1514
- completionId: completionIdFromResponse
1551
+ stopReason: terminalResponse?.status ?? undefined,
1552
+ completionId: completionIdFromResponse,
1553
+ providerMetadata: buildProviderMetadata({
1554
+ incompleteDetails: terminalResponse?.incomplete_details
1555
+ }),
1556
+ error: getResponseFailure(terminalResponse)
1515
1557
  });
1516
1558
  } catch (error) {
1517
1559
  await captureAiGeneration(this.phClient, {
1518
1560
  ...posthogParams,
1519
1561
  model: openAIParams.model,
1520
1562
  provider: 'azure',
1521
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1563
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1522
1564
  output: [],
1523
1565
  latency: 0,
1524
1566
  baseURL: this.baseURL,
@@ -1551,8 +1593,8 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1551
1593
  ...posthogParams,
1552
1594
  model: openAIParams.model ?? result.model,
1553
1595
  provider: 'azure',
1554
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1555
- output: result.output,
1596
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1597
+ output: sanitizeOpenAIResponse(result.output),
1556
1598
  latency,
1557
1599
  baseURL: this.baseURL,
1558
1600
  modelParameters: getModelParams(body, result.service_tier),
@@ -1563,10 +1605,13 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1563
1605
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1564
1606
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1565
1607
  },
1608
+ stopReason: result.status ?? undefined,
1566
1609
  completionId: result.id,
1567
1610
  providerMetadata: buildProviderMetadata({
1568
- requestId: extractRequestId(result)
1569
- })
1611
+ requestId: extractRequestId(result),
1612
+ incompleteDetails: result.incomplete_details
1613
+ }),
1614
+ error: getResponseFailure(result)
1570
1615
  });
1571
1616
  }
1572
1617
  return result;
@@ -1576,7 +1621,7 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1576
1621
  ...posthogParams,
1577
1622
  model: openAIParams.model,
1578
1623
  provider: 'azure',
1579
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1624
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1580
1625
  output: [],
1581
1626
  latency: 0,
1582
1627
  baseURL: this.baseURL,
@@ -1606,8 +1651,8 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1606
1651
  ...posthogParams,
1607
1652
  model: openAIParams.model ?? result.model,
1608
1653
  provider: 'azure',
1609
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1610
- output: result.output,
1654
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1655
+ output: sanitizeOpenAIResponse(result.output),
1611
1656
  latency,
1612
1657
  baseURL: this.baseURL,
1613
1658
  modelParameters: getModelParams(body, result.service_tier),
@@ -1618,10 +1663,13 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1618
1663
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1619
1664
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1620
1665
  },
1666
+ stopReason: result.status ?? undefined,
1621
1667
  completionId: result.id,
1622
1668
  providerMetadata: buildProviderMetadata({
1623
- requestId: extractRequestId(result)
1624
- })
1669
+ requestId: extractRequestId(result),
1670
+ incompleteDetails: result.incomplete_details
1671
+ }),
1672
+ error: getResponseFailure(result)
1625
1673
  });
1626
1674
  return result;
1627
1675
  }, async error => {
@@ -1629,7 +1677,7 @@ let WrappedResponses$1 = class WrappedResponses extends openai.AzureOpenAI.Respo
1629
1677
  ...posthogParams,
1630
1678
  model: openAIParams.model,
1631
1679
  provider: 'azure',
1632
- input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1680
+ input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
1633
1681
  output: [],
1634
1682
  latency: 0,
1635
1683
  baseURL: this.baseURL,
@@ -1719,9 +1767,8 @@ async function captureAiGenerationAfterSuccess(...args) {
1719
1767
  captureAiGenerationInBackground(...args);
1720
1768
  }
1721
1769
  }
1722
- const TERMINAL_RESPONSE_STATUSES = new Set(['completed', 'failed', 'cancelled', 'incomplete']);
1723
1770
  function isPendingBackgroundResponse(params, response) {
1724
- return params.background === true && !response.usage && !!response.status && !TERMINAL_RESPONSE_STATUSES.has(response.status);
1771
+ return params.background === true && !response.usage && !!response.status && !isTerminalResponse(response);
1725
1772
  }
1726
1773
  class PostHogOpenAI extends openai.OpenAI {
1727
1774
  constructor(config) {
@@ -1909,7 +1956,7 @@ class WrappedCompletions extends Completions {
1909
1956
  model: openAIParams.model ?? modelFromResponse,
1910
1957
  provider: 'openai',
1911
1958
  input: sanitizeOpenAI(openAIParams.messages),
1912
- output: formattedOutput,
1959
+ output: sanitizeOpenAIResponse(formattedOutput),
1913
1960
  latency,
1914
1961
  timeToFirstToken,
1915
1962
  baseURL: this.baseURL,
@@ -1977,7 +2024,7 @@ class WrappedCompletions extends Completions {
1977
2024
  model: openAIParams.model ?? result.model,
1978
2025
  provider: 'openai',
1979
2026
  input: sanitizeOpenAI(openAIParams.messages),
1980
- output: formattedOutput,
2027
+ output: sanitizeOpenAIResponse(formattedOutput),
1981
2028
  latency,
1982
2029
  baseURL: this.baseURL,
1983
2030
  modelParameters: getModelParams(body, result.service_tier),
@@ -2065,6 +2112,7 @@ class WrappedResponses extends Responses {
2065
2112
  webSearchCount: 0
2066
2113
  };
2067
2114
  let rawUsageData;
2115
+ let terminalResponse;
2068
2116
  for await (const chunk of stream1) {
2069
2117
  // Track first token time on content delta events
2070
2118
  if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
@@ -2085,10 +2133,9 @@ class WrappedResponses extends Responses {
2085
2133
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
2086
2134
  usage.webSearchCount = chunkWebSearchCount;
2087
2135
  }
2088
- }
2089
- if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
2090
- finalContent = chunk.response.output;
2091
- if (chunk.response.status) {
2136
+ if (isTerminalResponse(chunk.response)) {
2137
+ terminalResponse = chunk.response;
2138
+ finalContent = chunk.response.output ?? [];
2092
2139
  stopReason = chunk.response.status;
2093
2140
  }
2094
2141
  }
@@ -2111,7 +2158,7 @@ class WrappedResponses extends Responses {
2111
2158
  model: openAIParams.model ?? modelFromResponse,
2112
2159
  provider: 'openai',
2113
2160
  input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2114
- output: finalContent,
2161
+ output: sanitizeOpenAIResponse(finalContent),
2115
2162
  latency,
2116
2163
  timeToFirstToken,
2117
2164
  baseURL: this.baseURL,
@@ -2127,7 +2174,11 @@ class WrappedResponses extends Responses {
2127
2174
  },
2128
2175
  stopReason,
2129
2176
  tools: availableTools,
2130
- completionId: completionIdFromResponse
2177
+ completionId: completionIdFromResponse,
2178
+ providerMetadata: buildProviderMetadata({
2179
+ incompleteDetails: terminalResponse?.incomplete_details
2180
+ }),
2181
+ error: getResponseFailure(terminalResponse)
2131
2182
  });
2132
2183
  } catch (error) {
2133
2184
  await captureAiGeneration(this.phClient, {
@@ -2175,7 +2226,7 @@ class WrappedResponses extends Responses {
2175
2226
  model: openAIParams.model ?? result.model,
2176
2227
  provider: 'openai',
2177
2228
  input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2178
- output: formattedOutput,
2229
+ output: sanitizeOpenAIResponse(formattedOutput),
2179
2230
  latency,
2180
2231
  baseURL: this.baseURL,
2181
2232
  modelParameters: getModelParams(body, result.service_tier),
@@ -2192,8 +2243,10 @@ class WrappedResponses extends Responses {
2192
2243
  tools: availableTools,
2193
2244
  completionId: result.id,
2194
2245
  providerMetadata: buildProviderMetadata({
2195
- requestId: extractRequestId(result)
2196
- })
2246
+ requestId: extractRequestId(result),
2247
+ incompleteDetails: result.incomplete_details
2248
+ }),
2249
+ error: getResponseFailure(result)
2197
2250
  });
2198
2251
  }
2199
2252
  return result;
@@ -2237,7 +2290,7 @@ class WrappedResponses extends Responses {
2237
2290
  model: openAIParams.model ?? result.model,
2238
2291
  provider: 'openai',
2239
2292
  input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
2240
- output: result.output,
2293
+ output: sanitizeOpenAIResponse(result.output),
2241
2294
  latency,
2242
2295
  baseURL: this.baseURL,
2243
2296
  modelParameters: getModelParams(body, result.service_tier),
@@ -2252,8 +2305,10 @@ class WrappedResponses extends Responses {
2252
2305
  stopReason: result.status ?? undefined,
2253
2306
  completionId: result.id,
2254
2307
  providerMetadata: buildProviderMetadata({
2255
- requestId: extractRequestId(result)
2256
- })
2308
+ requestId: extractRequestId(result),
2309
+ incompleteDetails: result.incomplete_details
2310
+ }),
2311
+ error: getResponseFailure(result)
2257
2312
  });
2258
2313
  return result;
2259
2314
  }, async error => {
@@ -2406,7 +2461,7 @@ class WrappedTranscriptions extends Transcriptions {
2406
2461
  model: openAIParams.model,
2407
2462
  provider: 'openai',
2408
2463
  input: openAIParams.prompt,
2409
- output: finalContent,
2464
+ output: sanitizeOpenAIResponse(finalContent),
2410
2465
  latency,
2411
2466
  timeToFirstToken,
2412
2467
  baseURL: this.baseURL,
@@ -2451,7 +2506,7 @@ class WrappedTranscriptions extends Transcriptions {
2451
2506
  model: openAIParams.model,
2452
2507
  provider: 'openai',
2453
2508
  input: openAIParams.prompt,
2454
- output: result.text,
2509
+ output: sanitizeOpenAIResponse(result.text),
2455
2510
  latency,
2456
2511
  baseURL: this.baseURL,
2457
2512
  modelParameters: getModelParams(body),