@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.d.ts CHANGED
@@ -479,6 +479,19 @@ interface CaptureAiGenerationOptions {
479
479
  costOverride?: CostOverride;
480
480
  tools?: ChatCompletionTool[] | AnthropicTool[] | Tool[] | null;
481
481
  stopReason?: string;
482
+ /**
483
+ * Provider-assigned ID for the generation (e.g. OpenAI's `chatcmpl-…` /
484
+ * `resp_…`). Maps to `$ai_completion_id`. Response IDs generalize across
485
+ * providers, so this lives in the shared schema rather than under
486
+ * `providerMetadata`.
487
+ */
488
+ completionId?: string;
489
+ /**
490
+ * Provider-specific response metadata that has no place in the shared,
491
+ * provider-agnostic `$ai_*` schema (e.g. OpenAI's `system_fingerprint` and
492
+ * `request_id`). Maps to `$ai_provider_metadata`; omitted when empty.
493
+ */
494
+ providerMetadata?: Record<string, unknown>;
482
495
  /** When set, the event is captured as an error. */
483
496
  error?: unknown;
484
497
  /** Awaits delivery instead of batching. Useful in serverless environments. */
package/dist/index.mjs CHANGED
@@ -709,7 +709,7 @@ function formatOpenAIResponsesInput(input, instructions) {
709
709
  return messages;
710
710
  }
711
711
 
712
- var version = "7.19.7";
712
+ var version = "7.20.0";
713
713
 
714
714
  const DEFAULT_MAX_DEPTH = 3;
715
715
  const MAX_STACK_LINES = 20;
@@ -870,6 +870,12 @@ const captureAiGeneration = async (client, options) => {
870
870
  ...(options.tools ? {
871
871
  $ai_tools: options.tools
872
872
  } : {}),
873
+ ...(options.completionId ? {
874
+ $ai_completion_id: options.completionId
875
+ } : {}),
876
+ ...(options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? {
877
+ $ai_provider_metadata: options.providerMetadata
878
+ } : {}),
873
879
  ...errorData,
874
880
  ...costOverrideData
875
881
  };
@@ -893,6 +899,32 @@ const captureAiGeneration = async (client, options) => {
893
899
  function isResponseTokenChunk(chunk) {
894
900
  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';
895
901
  }
902
+ /**
903
+ * Reads the OpenAI SDK's `_request_id` field from a response object. The SDK
904
+ * attaches the `x-request-id` response header here, but it is not part of the
905
+ * public response types, so it has to be read through a cast. Used to populate
906
+ * `$ai_provider_metadata.request_id`.
907
+ */
908
+ function extractRequestId(result) {
909
+ return result?._request_id ?? undefined;
910
+ }
911
+ /**
912
+ * Assembles the `$ai_provider_metadata` blob for OpenAI / Azure OpenAI events.
913
+ * Provider-specific fields (system fingerprint, request id) live here rather
914
+ * than in the shared, provider-agnostic `$ai_*` namespace. Only keys with a
915
+ * truthy value are included, and `undefined` is returned when there is nothing
916
+ * to report so the property can be omitted from the event entirely.
917
+ */
918
+ function buildProviderMetadata(fields) {
919
+ const metadata = {};
920
+ if (fields.systemFingerprint) {
921
+ metadata.system_fingerprint = fields.systemFingerprint;
922
+ }
923
+ if (fields.requestId) {
924
+ metadata.request_id = fields.requestId;
925
+ }
926
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
927
+ }
896
928
 
897
929
  const Chat = OpenAI.Chat;
898
930
  const Completions = Chat.Completions;
@@ -939,6 +971,10 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
939
971
  if ('tee' in value) {
940
972
  const [stream1, stream2] = value.tee();
941
973
  (async () => {
974
+ // Hoisted so the catch block can surface whatever was accumulated
975
+ // from the streamed chunks before the failure.
976
+ let completionIdFromResponse;
977
+ let systemFingerprintFromResponse;
942
978
  try {
943
979
  const contentBlocks = [];
944
980
  let accumulatedContent = '';
@@ -954,10 +990,16 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
954
990
  const toolCallsInProgress = new Map();
955
991
  let rawUsageData;
956
992
  for await (const chunk of stream1) {
957
- // Extract model from chunk (Chat Completions chunks have model field)
993
+ // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
958
994
  if (!modelFromResponse && chunk.model) {
959
995
  modelFromResponse = chunk.model;
960
996
  }
997
+ if (!completionIdFromResponse && chunk.id) {
998
+ completionIdFromResponse = chunk.id;
999
+ }
1000
+ if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
1001
+ systemFingerprintFromResponse = chunk.system_fingerprint;
1002
+ }
961
1003
  const choice = chunk?.choices?.[0];
962
1004
  if (choice?.finish_reason) {
963
1005
  stopReason = choice.finish_reason;
@@ -1073,7 +1115,11 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1073
1115
  rawUsage: rawUsageData
1074
1116
  },
1075
1117
  stopReason,
1076
- tools: availableTools
1118
+ tools: availableTools,
1119
+ completionId: completionIdFromResponse,
1120
+ providerMetadata: buildProviderMetadata({
1121
+ systemFingerprint: systemFingerprintFromResponse
1122
+ })
1077
1123
  });
1078
1124
  } catch (error) {
1079
1125
  await captureAiGeneration(this.phClient, {
@@ -1089,6 +1135,13 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1089
1135
  inputTokens: 0,
1090
1136
  outputTokens: 0
1091
1137
  },
1138
+ // If the stream fails mid-flight, surface whatever completion
1139
+ // metadata the consumed chunks already provided so the error
1140
+ // event can still be correlated to OpenAI's Logs dashboard.
1141
+ completionId: completionIdFromResponse,
1142
+ providerMetadata: buildProviderMetadata({
1143
+ systemFingerprint: systemFingerprintFromResponse
1144
+ }),
1092
1145
  error
1093
1146
  });
1094
1147
  throw error;
@@ -1124,7 +1177,12 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1124
1177
  rawUsage: result.usage
1125
1178
  },
1126
1179
  stopReason: result.choices[0]?.finish_reason ?? undefined,
1127
- tools: availableTools
1180
+ tools: availableTools,
1181
+ completionId: result.id,
1182
+ providerMetadata: buildProviderMetadata({
1183
+ systemFingerprint: result.system_fingerprint,
1184
+ requestId: extractRequestId(result)
1185
+ })
1128
1186
  });
1129
1187
  }
1130
1188
  return result;
@@ -1171,6 +1229,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1171
1229
  if ('tee' in value && typeof value.tee === 'function') {
1172
1230
  const [stream1, stream2] = value.tee();
1173
1231
  (async () => {
1232
+ // Hoisted so the catch block can surface the completion ID that
1233
+ // was accumulated from the streamed chunks before the failure.
1234
+ let completionIdFromResponse;
1174
1235
  try {
1175
1236
  let finalContent = [];
1176
1237
  let modelFromResponse;
@@ -1188,10 +1249,13 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1188
1249
  firstTokenTime = Date.now();
1189
1250
  }
1190
1251
  if ('response' in chunk && chunk.response) {
1191
- // Extract model from response object in chunk (for stored prompts)
1252
+ // Extract model and completion ID from the response object in the chunk (for stored prompts)
1192
1253
  if (!modelFromResponse && chunk.response.model) {
1193
1254
  modelFromResponse = chunk.response.model;
1194
1255
  }
1256
+ if (!completionIdFromResponse && chunk.response.id) {
1257
+ completionIdFromResponse = chunk.response.id;
1258
+ }
1195
1259
  const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
1196
1260
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
1197
1261
  usage.webSearchCount = chunkWebSearchCount;
@@ -1237,7 +1301,8 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1237
1301
  rawUsage: rawUsageData
1238
1302
  },
1239
1303
  stopReason,
1240
- tools: availableTools
1304
+ tools: availableTools,
1305
+ completionId: completionIdFromResponse
1241
1306
  });
1242
1307
  } catch (error) {
1243
1308
  await captureAiGeneration(this.phClient, {
@@ -1253,6 +1318,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1253
1318
  inputTokens: 0,
1254
1319
  outputTokens: 0
1255
1320
  },
1321
+ // Surface the completion ID from any chunks consumed before
1322
+ // the stream failed so the error event remains correlatable.
1323
+ completionId: completionIdFromResponse,
1256
1324
  error
1257
1325
  });
1258
1326
  throw error;
@@ -1289,7 +1357,11 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1289
1357
  rawUsage: result.usage
1290
1358
  },
1291
1359
  stopReason: result.status ?? undefined,
1292
- tools: availableTools
1360
+ tools: availableTools,
1361
+ completionId: result.id,
1362
+ providerMetadata: buildProviderMetadata({
1363
+ requestId: extractRequestId(result)
1364
+ })
1293
1365
  });
1294
1366
  }
1295
1367
  return result;
@@ -1347,7 +1419,11 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1347
1419
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1348
1420
  rawUsage: result.usage
1349
1421
  },
1350
- stopReason: result.status ?? undefined
1422
+ stopReason: result.status ?? undefined,
1423
+ completionId: result.id,
1424
+ providerMetadata: buildProviderMetadata({
1425
+ requestId: extractRequestId(result)
1426
+ })
1351
1427
  });
1352
1428
  return result;
1353
1429
  }, async error => {
@@ -1603,6 +1679,10 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1603
1679
  if ('tee' in value) {
1604
1680
  const [stream1, stream2] = value.tee();
1605
1681
  (async () => {
1682
+ // Hoisted so the catch block can surface whatever was accumulated
1683
+ // from the streamed chunks before the failure.
1684
+ let completionIdFromResponse;
1685
+ let systemFingerprintFromResponse;
1606
1686
  try {
1607
1687
  const contentBlocks = [];
1608
1688
  let accumulatedContent = '';
@@ -1615,10 +1695,16 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1615
1695
  // Map to track in-progress tool calls
1616
1696
  const toolCallsInProgress = new Map();
1617
1697
  for await (const chunk of stream1) {
1618
- // Extract model from response if not in params
1698
+ // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
1619
1699
  if (!modelFromResponse && chunk.model) {
1620
1700
  modelFromResponse = chunk.model;
1621
1701
  }
1702
+ if (!completionIdFromResponse && chunk.id) {
1703
+ completionIdFromResponse = chunk.id;
1704
+ }
1705
+ if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
1706
+ systemFingerprintFromResponse = chunk.system_fingerprint;
1707
+ }
1622
1708
  const choice = chunk?.choices?.[0];
1623
1709
  // Handle text content
1624
1710
  const deltaContent = choice?.delta?.content;
@@ -1715,7 +1801,11 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1715
1801
  baseURL: this.baseURL,
1716
1802
  modelParameters: getModelParams(body),
1717
1803
  httpStatus: 200,
1718
- usage
1804
+ usage,
1805
+ completionId: completionIdFromResponse,
1806
+ providerMetadata: buildProviderMetadata({
1807
+ systemFingerprint: systemFingerprintFromResponse
1808
+ })
1719
1809
  });
1720
1810
  } catch (error) {
1721
1811
  await captureAiGeneration(this.phClient, {
@@ -1731,6 +1821,13 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1731
1821
  inputTokens: 0,
1732
1822
  outputTokens: 0
1733
1823
  },
1824
+ // If the stream fails mid-flight, surface whatever completion
1825
+ // metadata the consumed chunks already provided so the error
1826
+ // event can still be correlated to OpenAI's Logs dashboard.
1827
+ completionId: completionIdFromResponse,
1828
+ providerMetadata: buildProviderMetadata({
1829
+ systemFingerprint: systemFingerprintFromResponse
1830
+ }),
1734
1831
  error: error
1735
1832
  });
1736
1833
  throw error;
@@ -1760,7 +1857,12 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1760
1857
  outputTokens: result.usage?.completion_tokens ?? 0,
1761
1858
  reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
1762
1859
  cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0
1763
- }
1860
+ },
1861
+ completionId: result.id,
1862
+ providerMetadata: buildProviderMetadata({
1863
+ systemFingerprint: result.system_fingerprint,
1864
+ requestId: extractRequestId(result)
1865
+ })
1764
1866
  });
1765
1867
  }
1766
1868
  return result;
@@ -1807,6 +1909,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1807
1909
  if ('tee' in value && typeof value.tee === 'function') {
1808
1910
  const [stream1, stream2] = value.tee();
1809
1911
  (async () => {
1912
+ // Hoisted so the catch block can surface the completion ID that
1913
+ // was accumulated from the streamed chunks before the failure.
1914
+ let completionIdFromResponse;
1810
1915
  try {
1811
1916
  let finalContent = [];
1812
1917
  let modelFromResponse;
@@ -1821,10 +1926,13 @@ class WrappedResponses extends AzureOpenAI.Responses {
1821
1926
  firstTokenTime = Date.now();
1822
1927
  }
1823
1928
  if ('response' in chunk && chunk.response) {
1824
- // Extract model from response if not in params (for stored prompts)
1929
+ // Extract model and completion ID from the response object in the chunk (for stored prompts)
1825
1930
  if (!modelFromResponse && chunk.response.model) {
1826
1931
  modelFromResponse = chunk.response.model;
1827
1932
  }
1933
+ if (!completionIdFromResponse && chunk.response.id) {
1934
+ completionIdFromResponse = chunk.response.id;
1935
+ }
1828
1936
  }
1829
1937
  if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1830
1938
  finalContent = chunk.response.output;
@@ -1851,7 +1959,8 @@ class WrappedResponses extends AzureOpenAI.Responses {
1851
1959
  baseURL: this.baseURL,
1852
1960
  modelParameters: getModelParams(body),
1853
1961
  httpStatus: 200,
1854
- usage
1962
+ usage,
1963
+ completionId: completionIdFromResponse
1855
1964
  });
1856
1965
  } catch (error) {
1857
1966
  await captureAiGeneration(this.phClient, {
@@ -1867,6 +1976,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1867
1976
  inputTokens: 0,
1868
1977
  outputTokens: 0
1869
1978
  },
1979
+ // Surface the completion ID from any chunks consumed before
1980
+ // the stream failed so the error event remains correlatable.
1981
+ completionId: completionIdFromResponse,
1870
1982
  error: error
1871
1983
  });
1872
1984
  throw error;
@@ -1895,7 +2007,11 @@ class WrappedResponses extends AzureOpenAI.Responses {
1895
2007
  outputTokens: result.usage?.output_tokens ?? 0,
1896
2008
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1897
2009
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1898
- }
2010
+ },
2011
+ completionId: result.id,
2012
+ providerMetadata: buildProviderMetadata({
2013
+ requestId: extractRequestId(result)
2014
+ })
1899
2015
  });
1900
2016
  }
1901
2017
  return result;
@@ -1946,7 +2062,11 @@ class WrappedResponses extends AzureOpenAI.Responses {
1946
2062
  outputTokens: result.usage?.output_tokens ?? 0,
1947
2063
  reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
1948
2064
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
1949
- }
2065
+ },
2066
+ completionId: result.id,
2067
+ providerMetadata: buildProviderMetadata({
2068
+ requestId: extractRequestId(result)
2069
+ })
1950
2070
  });
1951
2071
  return result;
1952
2072
  }, async error => {