@posthog/ai 7.19.7 → 7.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -733,7 +733,7 @@ function formatOpenAIResponsesInput(input, instructions) {
733
733
  return messages;
734
734
  }
735
735
 
736
- var version = "7.19.7";
736
+ var version = "7.20.0";
737
737
 
738
738
  const DEFAULT_MAX_DEPTH = 3;
739
739
  const MAX_STACK_LINES = 20;
@@ -894,6 +894,12 @@ const captureAiGeneration = async (client, options) => {
894
894
  ...(options.tools ? {
895
895
  $ai_tools: options.tools
896
896
  } : {}),
897
+ ...(options.completionId ? {
898
+ $ai_completion_id: options.completionId
899
+ } : {}),
900
+ ...(options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? {
901
+ $ai_provider_metadata: options.providerMetadata
902
+ } : {}),
897
903
  ...errorData,
898
904
  ...costOverrideData
899
905
  };
@@ -917,6 +923,32 @@ const captureAiGeneration = async (client, options) => {
917
923
  function isResponseTokenChunk(chunk) {
918
924
  return chunk.type === 'response.output_item.added' || chunk.type === 'response.content_part.added' || chunk.type === 'response.output_text.delta' || chunk.type === 'response.reasoning_text.delta' || chunk.type === 'response.reasoning_summary_text.delta' || chunk.type === 'response.audio.delta' || chunk.type === 'response.audio.transcript.delta' || chunk.type === 'response.refusal.delta';
919
925
  }
926
+ /**
927
+ * Reads the OpenAI SDK's `_request_id` field from a response object. The SDK
928
+ * attaches the `x-request-id` response header here, but it is not part of the
929
+ * public response types, so it has to be read through a cast. Used to populate
930
+ * `$ai_provider_metadata.request_id`.
931
+ */
932
+ function extractRequestId(result) {
933
+ return result?._request_id ?? undefined;
934
+ }
935
+ /**
936
+ * Assembles the `$ai_provider_metadata` blob for OpenAI / Azure OpenAI events.
937
+ * Provider-specific fields (system fingerprint, request id) live here rather
938
+ * than in the shared, provider-agnostic `$ai_*` namespace. Only keys with a
939
+ * truthy value are included, and `undefined` is returned when there is nothing
940
+ * to report so the property can be omitted from the event entirely.
941
+ */
942
+ function buildProviderMetadata(fields) {
943
+ const metadata = {};
944
+ if (fields.systemFingerprint) {
945
+ metadata.system_fingerprint = fields.systemFingerprint;
946
+ }
947
+ if (fields.requestId) {
948
+ metadata.request_id = fields.requestId;
949
+ }
950
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
951
+ }
920
952
 
921
953
  const Chat = openai.OpenAI.Chat;
922
954
  const Completions = Chat.Completions;
@@ -963,6 +995,10 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
963
995
  if ('tee' in value) {
964
996
  const [stream1, stream2] = value.tee();
965
997
  (async () => {
998
+ // Hoisted so the catch block can surface whatever was accumulated
999
+ // from the streamed chunks before the failure.
1000
+ let completionIdFromResponse;
1001
+ let systemFingerprintFromResponse;
966
1002
  try {
967
1003
  const contentBlocks = [];
968
1004
  let accumulatedContent = '';
@@ -978,10 +1014,16 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
978
1014
  const toolCallsInProgress = new Map();
979
1015
  let rawUsageData;
980
1016
  for await (const chunk of stream1) {
981
- // Extract model from chunk (Chat Completions chunks have model field)
1017
+ // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
982
1018
  if (!modelFromResponse && chunk.model) {
983
1019
  modelFromResponse = chunk.model;
984
1020
  }
1021
+ if (!completionIdFromResponse && chunk.id) {
1022
+ completionIdFromResponse = chunk.id;
1023
+ }
1024
+ if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
1025
+ systemFingerprintFromResponse = chunk.system_fingerprint;
1026
+ }
985
1027
  const choice = chunk?.choices?.[0];
986
1028
  if (choice?.finish_reason) {
987
1029
  stopReason = choice.finish_reason;
@@ -1097,7 +1139,11 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1097
1139
  rawUsage: rawUsageData
1098
1140
  },
1099
1141
  stopReason,
1100
- tools: availableTools
1142
+ tools: availableTools,
1143
+ completionId: completionIdFromResponse,
1144
+ providerMetadata: buildProviderMetadata({
1145
+ systemFingerprint: systemFingerprintFromResponse
1146
+ })
1101
1147
  });
1102
1148
  } catch (error) {
1103
1149
  await captureAiGeneration(this.phClient, {
@@ -1113,6 +1159,13 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1113
1159
  inputTokens: 0,
1114
1160
  outputTokens: 0
1115
1161
  },
1162
+ // If the stream fails mid-flight, surface whatever completion
1163
+ // metadata the consumed chunks already provided so the error
1164
+ // event can still be correlated to OpenAI's Logs dashboard.
1165
+ completionId: completionIdFromResponse,
1166
+ providerMetadata: buildProviderMetadata({
1167
+ systemFingerprint: systemFingerprintFromResponse
1168
+ }),
1116
1169
  error
1117
1170
  });
1118
1171
  throw error;
@@ -1148,7 +1201,12 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1148
1201
  rawUsage: result.usage
1149
1202
  },
1150
1203
  stopReason: result.choices[0]?.finish_reason ?? undefined,
1151
- tools: availableTools
1204
+ tools: availableTools,
1205
+ completionId: result.id,
1206
+ providerMetadata: buildProviderMetadata({
1207
+ systemFingerprint: result.system_fingerprint,
1208
+ requestId: extractRequestId(result)
1209
+ })
1152
1210
  });
1153
1211
  }
1154
1212
  return result;
@@ -1195,6 +1253,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1195
1253
  if ('tee' in value && typeof value.tee === 'function') {
1196
1254
  const [stream1, stream2] = value.tee();
1197
1255
  (async () => {
1256
+ // Hoisted so the catch block can surface the completion ID that
1257
+ // was accumulated from the streamed chunks before the failure.
1258
+ let completionIdFromResponse;
1198
1259
  try {
1199
1260
  let finalContent = [];
1200
1261
  let modelFromResponse;
@@ -1212,10 +1273,13 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1212
1273
  firstTokenTime = Date.now();
1213
1274
  }
1214
1275
  if ('response' in chunk && chunk.response) {
1215
- // Extract model from response object in chunk (for stored prompts)
1276
+ // Extract model and completion ID from the response object in the chunk (for stored prompts)
1216
1277
  if (!modelFromResponse && chunk.response.model) {
1217
1278
  modelFromResponse = chunk.response.model;
1218
1279
  }
1280
+ if (!completionIdFromResponse && chunk.response.id) {
1281
+ completionIdFromResponse = chunk.response.id;
1282
+ }
1219
1283
  const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
1220
1284
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
1221
1285
  usage.webSearchCount = chunkWebSearchCount;
@@ -1261,7 +1325,8 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1261
1325
  rawUsage: rawUsageData
1262
1326
  },
1263
1327
  stopReason,
1264
- tools: availableTools
1328
+ tools: availableTools,
1329
+ completionId: completionIdFromResponse
1265
1330
  });
1266
1331
  } catch (error) {
1267
1332
  await captureAiGeneration(this.phClient, {
@@ -1277,6 +1342,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1277
1342
  inputTokens: 0,
1278
1343
  outputTokens: 0
1279
1344
  },
1345
+ // Surface the completion ID from any chunks consumed before
1346
+ // the stream failed so the error event remains correlatable.
1347
+ completionId: completionIdFromResponse,
1280
1348
  error
1281
1349
  });
1282
1350
  throw error;
@@ -1313,7 +1381,11 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1313
1381
  rawUsage: result.usage
1314
1382
  },
1315
1383
  stopReason: result.status ?? undefined,
1316
- tools: availableTools
1384
+ tools: availableTools,
1385
+ completionId: result.id,
1386
+ providerMetadata: buildProviderMetadata({
1387
+ requestId: extractRequestId(result)
1388
+ })
1317
1389
  });
1318
1390
  }
1319
1391
  return result;
@@ -1371,7 +1443,11 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1371
1443
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1372
1444
  rawUsage: result.usage
1373
1445
  },
1374
- stopReason: result.status ?? undefined
1446
+ stopReason: result.status ?? undefined,
1447
+ completionId: result.id,
1448
+ providerMetadata: buildProviderMetadata({
1449
+ requestId: extractRequestId(result)
1450
+ })
1375
1451
  });
1376
1452
  return result;
1377
1453
  }, async error => {
@@ -1627,6 +1703,10 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1627
1703
  if ('tee' in value) {
1628
1704
  const [stream1, stream2] = value.tee();
1629
1705
  (async () => {
1706
+ // Hoisted so the catch block can surface whatever was accumulated
1707
+ // from the streamed chunks before the failure.
1708
+ let completionIdFromResponse;
1709
+ let systemFingerprintFromResponse;
1630
1710
  try {
1631
1711
  const contentBlocks = [];
1632
1712
  let accumulatedContent = '';
@@ -1639,10 +1719,16 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1639
1719
  // Map to track in-progress tool calls
1640
1720
  const toolCallsInProgress = new Map();
1641
1721
  for await (const chunk of stream1) {
1642
- // Extract model from response if not in params
1722
+ // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
1643
1723
  if (!modelFromResponse && chunk.model) {
1644
1724
  modelFromResponse = chunk.model;
1645
1725
  }
1726
+ if (!completionIdFromResponse && chunk.id) {
1727
+ completionIdFromResponse = chunk.id;
1728
+ }
1729
+ if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
1730
+ systemFingerprintFromResponse = chunk.system_fingerprint;
1731
+ }
1646
1732
  const choice = chunk?.choices?.[0];
1647
1733
  // Handle text content
1648
1734
  const deltaContent = choice?.delta?.content;
@@ -1739,7 +1825,11 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1739
1825
  baseURL: this.baseURL,
1740
1826
  modelParameters: getModelParams(body),
1741
1827
  httpStatus: 200,
1742
- usage
1828
+ usage,
1829
+ completionId: completionIdFromResponse,
1830
+ providerMetadata: buildProviderMetadata({
1831
+ systemFingerprint: systemFingerprintFromResponse
1832
+ })
1743
1833
  });
1744
1834
  } catch (error) {
1745
1835
  await captureAiGeneration(this.phClient, {
@@ -1755,6 +1845,13 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1755
1845
  inputTokens: 0,
1756
1846
  outputTokens: 0
1757
1847
  },
1848
+ // If the stream fails mid-flight, surface whatever completion
1849
+ // metadata the consumed chunks already provided so the error
1850
+ // event can still be correlated to OpenAI's Logs dashboard.
1851
+ completionId: completionIdFromResponse,
1852
+ providerMetadata: buildProviderMetadata({
1853
+ systemFingerprint: systemFingerprintFromResponse
1854
+ }),
1758
1855
  error: error
1759
1856
  });
1760
1857
  throw error;
@@ -1784,7 +1881,12 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1784
1881
  outputTokens: result.usage?.completion_tokens ?? 0,
1785
1882
  reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
1786
1883
  cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0
1787
- }
1884
+ },
1885
+ completionId: result.id,
1886
+ providerMetadata: buildProviderMetadata({
1887
+ systemFingerprint: result.system_fingerprint,
1888
+ requestId: extractRequestId(result)
1889
+ })
1788
1890
  });
1789
1891
  }
1790
1892
  return result;
@@ -1831,6 +1933,9 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1831
1933
  if ('tee' in value && typeof value.tee === 'function') {
1832
1934
  const [stream1, stream2] = value.tee();
1833
1935
  (async () => {
1936
+ // Hoisted so the catch block can surface the completion ID that
1937
+ // was accumulated from the streamed chunks before the failure.
1938
+ let completionIdFromResponse;
1834
1939
  try {
1835
1940
  let finalContent = [];
1836
1941
  let modelFromResponse;
@@ -1845,10 +1950,13 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1845
1950
  firstTokenTime = Date.now();
1846
1951
  }
1847
1952
  if ('response' in chunk && chunk.response) {
1848
- // Extract model from response if not in params (for stored prompts)
1953
+ // Extract model and completion ID from the response object in the chunk (for stored prompts)
1849
1954
  if (!modelFromResponse && chunk.response.model) {
1850
1955
  modelFromResponse = chunk.response.model;
1851
1956
  }
1957
+ if (!completionIdFromResponse && chunk.response.id) {
1958
+ completionIdFromResponse = chunk.response.id;
1959
+ }
1852
1960
  }
1853
1961
  if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1854
1962
  finalContent = chunk.response.output;
@@ -1875,7 +1983,8 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1875
1983
  baseURL: this.baseURL,
1876
1984
  modelParameters: getModelParams(body),
1877
1985
  httpStatus: 200,
1878
- usage
1986
+ usage,
1987
+ completionId: completionIdFromResponse
1879
1988
  });
1880
1989
  } catch (error) {
1881
1990
  await captureAiGeneration(this.phClient, {
@@ -1891,6 +2000,9 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1891
2000
  inputTokens: 0,
1892
2001
  outputTokens: 0
1893
2002
  },
2003
+ // Surface the completion ID from any chunks consumed before
2004
+ // the stream failed so the error event remains correlatable.
2005
+ completionId: completionIdFromResponse,
1894
2006
  error: error
1895
2007
  });
1896
2008
  throw error;
@@ -1919,7 +2031,11 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1919
2031
  outputTokens: result.usage?.output_tokens ?? 0,
1920
2032
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1921
2033
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1922
- }
2034
+ },
2035
+ completionId: result.id,
2036
+ providerMetadata: buildProviderMetadata({
2037
+ requestId: extractRequestId(result)
2038
+ })
1923
2039
  });
1924
2040
  }
1925
2041
  return result;
@@ -1970,7 +2086,11 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1970
2086
  outputTokens: result.usage?.output_tokens ?? 0,
1971
2087
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1972
2088
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1973
- }
2089
+ },
2090
+ completionId: result.id,
2091
+ providerMetadata: buildProviderMetadata({
2092
+ requestId: extractRequestId(result)
2093
+ })
1974
2094
  });
1975
2095
  return result;
1976
2096
  }, async error => {