@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.
@@ -530,7 +530,7 @@ function formatOpenAIResponsesInput(input, instructions) {
530
530
  return messages;
531
531
  }
532
532
 
533
- var version = "7.19.7";
533
+ var version = "7.20.0";
534
534
 
535
535
  const DEFAULT_MAX_DEPTH = 3;
536
536
  const MAX_STACK_LINES = 20;
@@ -697,6 +697,12 @@ const captureAiGeneration = async (client, options) => {
697
697
  ...(options.tools ? {
698
698
  $ai_tools: options.tools
699
699
  } : {}),
700
+ ...(options.completionId ? {
701
+ $ai_completion_id: options.completionId
702
+ } : {}),
703
+ ...(options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? {
704
+ $ai_provider_metadata: options.providerMetadata
705
+ } : {}),
700
706
  ...errorData,
701
707
  ...costOverrideData
702
708
  };
@@ -721,6 +727,34 @@ function isResponseTokenChunk(chunk) {
721
727
  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';
722
728
  }
723
729
 
730
+ /**
731
+ * Reads the OpenAI SDK's `_request_id` field from a response object. The SDK
732
+ * attaches the `x-request-id` response header here, but it is not part of the
733
+ * public response types, so it has to be read through a cast. Used to populate
734
+ * `$ai_provider_metadata.request_id`.
735
+ */
736
+ function extractRequestId(result) {
737
+ return result?._request_id ?? undefined;
738
+ }
739
+
740
+ /**
741
+ * Assembles the `$ai_provider_metadata` blob for OpenAI / Azure OpenAI events.
742
+ * Provider-specific fields (system fingerprint, request id) live here rather
743
+ * than in the shared, provider-agnostic `$ai_*` namespace. Only keys with a
744
+ * truthy value are included, and `undefined` is returned when there is nothing
745
+ * to report so the property can be omitted from the event entirely.
746
+ */
747
+ function buildProviderMetadata(fields) {
748
+ const metadata = {};
749
+ if (fields.systemFingerprint) {
750
+ metadata.system_fingerprint = fields.systemFingerprint;
751
+ }
752
+ if (fields.requestId) {
753
+ metadata.request_id = fields.requestId;
754
+ }
755
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
756
+ }
757
+
724
758
  const Chat = OpenAI.Chat;
725
759
  const Completions = Chat.Completions;
726
760
  const Responses = OpenAI.Responses;
@@ -773,6 +807,10 @@ class WrappedCompletions extends Completions {
773
807
  if ('tee' in value) {
774
808
  const [stream1, stream2] = value.tee();
775
809
  (async () => {
810
+ // Hoisted so the catch block can surface whatever was accumulated
811
+ // from the streamed chunks before the failure.
812
+ let completionIdFromResponse;
813
+ let systemFingerprintFromResponse;
776
814
  try {
777
815
  const contentBlocks = [];
778
816
  let accumulatedContent = '';
@@ -789,10 +827,16 @@ class WrappedCompletions extends Completions {
789
827
  const toolCallsInProgress = new Map();
790
828
  let rawUsageData;
791
829
  for await (const chunk of stream1) {
792
- // Extract model from chunk (Chat Completions chunks have model field)
830
+ // Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
793
831
  if (!modelFromResponse && chunk.model) {
794
832
  modelFromResponse = chunk.model;
795
833
  }
834
+ if (!completionIdFromResponse && chunk.id) {
835
+ completionIdFromResponse = chunk.id;
836
+ }
837
+ if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
838
+ systemFingerprintFromResponse = chunk.system_fingerprint;
839
+ }
796
840
  const choice = chunk?.choices?.[0];
797
841
  if (choice?.finish_reason) {
798
842
  stopReason = choice.finish_reason;
@@ -914,7 +958,11 @@ class WrappedCompletions extends Completions {
914
958
  rawUsage: rawUsageData
915
959
  },
916
960
  stopReason,
917
- tools: availableTools
961
+ tools: availableTools,
962
+ completionId: completionIdFromResponse,
963
+ providerMetadata: buildProviderMetadata({
964
+ systemFingerprint: systemFingerprintFromResponse
965
+ })
918
966
  });
919
967
  } catch (error) {
920
968
  await captureAiGeneration(this.phClient, {
@@ -930,6 +978,13 @@ class WrappedCompletions extends Completions {
930
978
  inputTokens: 0,
931
979
  outputTokens: 0
932
980
  },
981
+ // If the stream fails mid-flight, surface whatever completion
982
+ // metadata the consumed chunks already provided so the error
983
+ // event can still be correlated to OpenAI's Logs dashboard.
984
+ completionId: completionIdFromResponse,
985
+ providerMetadata: buildProviderMetadata({
986
+ systemFingerprint: systemFingerprintFromResponse
987
+ }),
933
988
  error
934
989
  });
935
990
  throw error;
@@ -966,7 +1021,12 @@ class WrappedCompletions extends Completions {
966
1021
  rawUsage: result.usage
967
1022
  },
968
1023
  stopReason: result.choices[0]?.finish_reason ?? undefined,
969
- tools: availableTools
1024
+ tools: availableTools,
1025
+ completionId: result.id,
1026
+ providerMetadata: buildProviderMetadata({
1027
+ systemFingerprint: result.system_fingerprint,
1028
+ requestId: extractRequestId(result)
1029
+ })
970
1030
  });
971
1031
  }
972
1032
  return result;
@@ -1020,6 +1080,9 @@ class WrappedResponses extends Responses {
1020
1080
  if ('tee' in value && typeof value.tee === 'function') {
1021
1081
  const [stream1, stream2] = value.tee();
1022
1082
  (async () => {
1083
+ // Hoisted so the catch block can surface the completion ID that
1084
+ // was accumulated from the streamed chunks before the failure.
1085
+ let completionIdFromResponse;
1023
1086
  try {
1024
1087
  let finalContent = [];
1025
1088
  let modelFromResponse;
@@ -1037,10 +1100,13 @@ class WrappedResponses extends Responses {
1037
1100
  firstTokenTime = Date.now();
1038
1101
  }
1039
1102
  if ('response' in chunk && chunk.response) {
1040
- // Extract model from response object in chunk (for stored prompts)
1103
+ // Extract model and completion ID from the response object in the chunk (for stored prompts)
1041
1104
  if (!modelFromResponse && chunk.response.model) {
1042
1105
  modelFromResponse = chunk.response.model;
1043
1106
  }
1107
+ if (!completionIdFromResponse && chunk.response.id) {
1108
+ completionIdFromResponse = chunk.response.id;
1109
+ }
1044
1110
  const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
1045
1111
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
1046
1112
  usage.webSearchCount = chunkWebSearchCount;
@@ -1086,7 +1152,8 @@ class WrappedResponses extends Responses {
1086
1152
  rawUsage: rawUsageData
1087
1153
  },
1088
1154
  stopReason,
1089
- tools: availableTools
1155
+ tools: availableTools,
1156
+ completionId: completionIdFromResponse
1090
1157
  });
1091
1158
  } catch (error) {
1092
1159
  await captureAiGeneration(this.phClient, {
@@ -1102,6 +1169,9 @@ class WrappedResponses extends Responses {
1102
1169
  inputTokens: 0,
1103
1170
  outputTokens: 0
1104
1171
  },
1172
+ // Surface the completion ID from any chunks consumed before
1173
+ // the stream failed so the error event remains correlatable.
1174
+ completionId: completionIdFromResponse,
1105
1175
  error
1106
1176
  });
1107
1177
  throw error;
@@ -1138,7 +1208,11 @@ class WrappedResponses extends Responses {
1138
1208
  rawUsage: result.usage
1139
1209
  },
1140
1210
  stopReason: result.status ?? undefined,
1141
- tools: availableTools
1211
+ tools: availableTools,
1212
+ completionId: result.id,
1213
+ providerMetadata: buildProviderMetadata({
1214
+ requestId: extractRequestId(result)
1215
+ })
1142
1216
  });
1143
1217
  }
1144
1218
  return result;
@@ -1196,7 +1270,11 @@ class WrappedResponses extends Responses {
1196
1270
  cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
1197
1271
  rawUsage: result.usage
1198
1272
  },
1199
- stopReason: result.status ?? undefined
1273
+ stopReason: result.status ?? undefined,
1274
+ completionId: result.id,
1275
+ providerMetadata: buildProviderMetadata({
1276
+ requestId: extractRequestId(result)
1277
+ })
1200
1278
  });
1201
1279
  return result;
1202
1280
  }, async error => {