@posthog/ai 8.14.5 → 8.15.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.
Files changed (47) hide show
  1. package/README.md +14 -0
  2. package/dist/adk/index.cjs +12 -5
  3. package/dist/adk/index.cjs.map +1 -1
  4. package/dist/adk/index.mjs +12 -5
  5. package/dist/adk/index.mjs.map +1 -1
  6. package/dist/anthropic/index.cjs +12 -5
  7. package/dist/anthropic/index.cjs.map +1 -1
  8. package/dist/anthropic/index.mjs +12 -5
  9. package/dist/anthropic/index.mjs.map +1 -1
  10. package/dist/claude-agent-sdk/index.cjs +12 -5
  11. package/dist/claude-agent-sdk/index.cjs.map +1 -1
  12. package/dist/claude-agent-sdk/index.mjs +12 -5
  13. package/dist/claude-agent-sdk/index.mjs.map +1 -1
  14. package/dist/gemini/index.cjs +377 -5
  15. package/dist/gemini/index.cjs.map +1 -1
  16. package/dist/gemini/index.d.ts +28 -0
  17. package/dist/gemini/index.mjs +377 -5
  18. package/dist/gemini/index.mjs.map +1 -1
  19. package/dist/index.cjs +12 -5
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.mjs +12 -5
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/langchain/index.cjs +12 -5
  24. package/dist/langchain/index.cjs.map +1 -1
  25. package/dist/langchain/index.mjs +12 -5
  26. package/dist/langchain/index.mjs.map +1 -1
  27. package/dist/langchain/middleware/index.cjs +12 -5
  28. package/dist/langchain/middleware/index.cjs.map +1 -1
  29. package/dist/langchain/middleware/index.mjs +12 -5
  30. package/dist/langchain/middleware/index.mjs.map +1 -1
  31. package/dist/openai/index.cjs +12 -5
  32. package/dist/openai/index.cjs.map +1 -1
  33. package/dist/openai/index.mjs +12 -5
  34. package/dist/openai/index.mjs.map +1 -1
  35. package/dist/openai-agents/index.cjs +12 -5
  36. package/dist/openai-agents/index.cjs.map +1 -1
  37. package/dist/openai-agents/index.mjs +12 -5
  38. package/dist/openai-agents/index.mjs.map +1 -1
  39. package/dist/otel/index.cjs +11 -4
  40. package/dist/otel/index.cjs.map +1 -1
  41. package/dist/otel/index.mjs +11 -4
  42. package/dist/otel/index.mjs.map +1 -1
  43. package/dist/vercel/index.cjs +12 -5
  44. package/dist/vercel/index.cjs.map +1 -1
  45. package/dist/vercel/index.mjs +12 -5
  46. package/dist/vercel/index.mjs.map +1 -1
  47. package/package.json +4 -3
@@ -22,6 +22,33 @@ interface CostOverride {
22
22
  outputCost: number;
23
23
  }
24
24
  //#endregion
25
+ //#region src/gemini/interactions.d.ts
26
+ type Create = GoogleGenAI['interactions']['create'];
27
+ type CreateParams = Parameters<Create>[0];
28
+ type ForegroundParams = CreateParams & {
29
+ model: string;
30
+ agent?: never;
31
+ background?: false;
32
+ };
33
+ type CreateOptions = Parameters<Create>[1];
34
+ type CreateResult = Awaited<ReturnType<Create>>;
35
+ type Interaction = Exclude<CreateResult, AsyncIterable<unknown>>;
36
+ type InteractionStream = Extract<CreateResult, AsyncIterable<unknown>>;
37
+ type InteractionEvent = InteractionStream extends AsyncIterable<infer Event> ? Event : never;
38
+ declare class WrappedInteractions {
39
+ private readonly client;
40
+ private readonly phClient;
41
+ constructor(client: GoogleGenAI, phClient: PostHog);
42
+ create(params: ForegroundParams & MonitoringParams & {
43
+ stream: true;
44
+ }, options?: CreateOptions): Promise<ReadableStream<InteractionEvent> & AsyncIterable<InteractionEvent>>;
45
+ create(params: ForegroundParams & MonitoringParams & {
46
+ stream?: false;
47
+ }, options?: CreateOptions): Promise<Interaction>;
48
+ create(params: ForegroundParams & MonitoringParams, options?: CreateOptions): Promise<Interaction | (ReadableStream<InteractionEvent> & AsyncIterable<InteractionEvent>)>;
49
+ private wrapStream;
50
+ }
51
+ //#endregion
25
52
  //#region src/gemini/index.d.ts
26
53
  interface MonitoringGeminiConfig extends GoogleGenAIOptions {
27
54
  posthog: PostHog;
@@ -30,6 +57,7 @@ declare class PostHogGoogleGenAI {
30
57
  private readonly phClient;
31
58
  private readonly client;
32
59
  models: WrappedModels;
60
+ interactions: WrappedInteractions;
33
61
  constructor(config: MonitoringGeminiConfig);
34
62
  }
35
63
  declare class WrappedModels {
@@ -92,6 +92,12 @@ const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
92
92
  "media",
93
93
  "file-data"
94
94
  ]);
95
+ const TYPED_BINARY_DATA_TYPES = /* @__PURE__ */ new Set([
96
+ "image",
97
+ "audio",
98
+ "video",
99
+ "document"
100
+ ]);
95
101
  const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
96
102
  "wav",
97
103
  "mp3",
@@ -143,7 +149,8 @@ var MediaTypeContext = class MediaTypeContext {
143
149
  if (key.includes("image")) return "image";
144
150
  if (key.includes("file") || key.includes("document")) return "application/octet-stream";
145
151
  }
146
- hasExplicitBinaryMediaType() {
152
+ hasDefiniteBinaryData() {
153
+ if (this.parent && this.key === "data" && typeof this.parent.type === "string" && TYPED_BINARY_DATA_TYPES.has(this.parent.type)) return true;
147
154
  if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
148
155
  const mediaType = this.inferFromSiblingMime();
149
156
  return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
@@ -197,9 +204,9 @@ var BinaryContentRedactor = class {
197
204
  return out;
198
205
  }
199
206
  redactString(value, ctx) {
200
- const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
201
- const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
202
- const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
207
+ const hasDefiniteBinaryData = ctx.hasDefiniteBinaryData();
208
+ const recognitionValue = hasDefiniteBinaryData ? value.replace(/[\r\n]/g, "") : value;
209
+ const minLength = hasDefiniteBinaryData ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
203
210
  const recognition = this.recognizer.recognize(recognitionValue, minLength);
204
211
  switch (recognition.kind) {
205
212
  case "data-url": return this.placeholderFor(recognition.mediaType);
@@ -452,7 +459,7 @@ function addDefaults(params) {
452
459
  }
453
460
  //#endregion
454
461
  //#region package.json
455
- var version = "8.14.5";
462
+ var version = "8.15.0";
456
463
  //#endregion
457
464
  //#region src/serializeError.ts
458
465
  const DEFAULT_MAX_DEPTH = 3;
@@ -718,6 +725,370 @@ function mapGeminiUsage(metadata, additionalUsage = {}) {
718
725
  };
719
726
  }
720
727
  //#endregion
728
+ //#region src/gemini/interactions.ts
729
+ function record(value) {
730
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
731
+ }
732
+ function string(value) {
733
+ return typeof value === "string" ? value : void 0;
734
+ }
735
+ function number(value) {
736
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
737
+ }
738
+ function isStream(value) {
739
+ return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
740
+ }
741
+ function usageFromInteraction(value, model) {
742
+ const usage = record(value);
743
+ if (!usage) return {};
744
+ const cached = number(usage.total_cached_tokens);
745
+ const searches = Array.isArray(usage.grounding_tool_count) ? usage.grounding_tool_count.reduce((total, entry) => {
746
+ const grounding = record(entry);
747
+ return total + (grounding?.type === "google_search" ? number(grounding.count) ?? 0 : 0);
748
+ }, 0) : void 0;
749
+ const modelMajor = /^(?:models\/)?gemini-(\d+)/.exec(model ?? "")?.[1];
750
+ const webSearchCount = modelMajor === void 0 ? void 0 : Number(modelMajor) >= 3 ? searches : searches ? 1 : 0;
751
+ return {
752
+ inputTokens: number(usage.total_input_tokens),
753
+ outputTokens: number(usage.total_output_tokens),
754
+ reasoningTokens: number(usage.total_thought_tokens),
755
+ cacheReadInputTokens: cached,
756
+ ...cached !== void 0 ? { cacheReportingExclusive: false } : {},
757
+ webSearchCount,
758
+ rawUsage: usage
759
+ };
760
+ }
761
+ function functionArguments(value) {
762
+ if (typeof value !== "string") return record(value) ?? {};
763
+ try {
764
+ return record(JSON.parse(value)) ?? value;
765
+ } catch {
766
+ return value;
767
+ }
768
+ }
769
+ function outputFromSteps(steps, fallbackText) {
770
+ const content = [];
771
+ if (Array.isArray(steps)) for (const rawStep of steps) {
772
+ const step = record(rawStep);
773
+ if (step?.type === "model_output" && Array.isArray(step.content)) content.push(...step.content);
774
+ else if (step?.type === "function_call" && string(step.name)) content.push({
775
+ type: "function",
776
+ id: string(step.id),
777
+ function: {
778
+ name: step.name,
779
+ arguments: functionArguments(step.arguments)
780
+ }
781
+ });
782
+ }
783
+ if (content.length === 0 && fallbackText) content.push({
784
+ type: "text",
785
+ text: fallbackText
786
+ });
787
+ return content.length ? [{
788
+ role: "assistant",
789
+ content
790
+ }] : [];
791
+ }
792
+ function inputFromRequest(input, systemInstruction) {
793
+ const messages = [];
794
+ if (typeof systemInstruction === "string") messages.push({
795
+ role: "system",
796
+ content: systemInstruction
797
+ });
798
+ let userContent = [];
799
+ const flushUserContent = () => {
800
+ if (userContent.length) messages.push({
801
+ role: "user",
802
+ content: userContent
803
+ });
804
+ userContent = [];
805
+ };
806
+ for (const part of Array.isArray(input) ? input : [input]) {
807
+ const step = record(part);
808
+ if (step?.type === "text" || step?.type === "image" || step?.type === "audio" || step?.type === "video" || step?.type === "document") {
809
+ userContent.push(part);
810
+ continue;
811
+ }
812
+ flushUserContent();
813
+ if (step?.type === "function_result") messages.push({
814
+ role: "tool",
815
+ content: [{
816
+ type: "tool_result",
817
+ tool_use_id: step.call_id,
818
+ content: step.result
819
+ }]
820
+ });
821
+ else if (step?.type === "user_input") messages.push({
822
+ role: "user",
823
+ content: step.content ?? [part]
824
+ });
825
+ else if (step?.type === "model_output") messages.push({
826
+ role: "assistant",
827
+ content: step.content ?? [part]
828
+ });
829
+ else if (step?.type === "function_call") {
830
+ const output = outputFromSteps([part]);
831
+ messages.push(...output.length ? output : [{
832
+ role: "assistant",
833
+ content: [part]
834
+ }]);
835
+ } else messages.push({
836
+ role: "user",
837
+ content: part
838
+ });
839
+ }
840
+ flushUserContent();
841
+ return messages;
842
+ }
843
+ function safeTools(tools) {
844
+ if (!Array.isArray(tools)) return void 0;
845
+ return tools.map((tool) => ({
846
+ type: string(record(tool)?.type),
847
+ name: string(record(tool)?.name)
848
+ }));
849
+ }
850
+ function isFailure(status) {
851
+ return status === "failed" || status === "cancelled" || status === "incomplete" || status === "budget_exceeded";
852
+ }
853
+ var WrappedInteractions = class {
854
+ constructor(client, phClient) {
855
+ this.client = client;
856
+ this.phClient = phClient;
857
+ }
858
+ async create(params, options) {
859
+ const { providerParams, posthogParams } = extractPosthogParams(params);
860
+ const request = providerParams;
861
+ const requestData = record(request) ?? {};
862
+ if (requestData.background === true || typeof requestData.model !== "string" || requestData.agent !== void 0) throw new Error("Gemini Interactions tracing supports foreground model calls only");
863
+ const startTime = Date.now();
864
+ const capture = async (interaction, error, output, firstTokenTime) => {
865
+ try {
866
+ const privateCapture = withPrivacyMode(this.phClient, posthogParams.privacyMode, false) === null;
867
+ const model = string(interaction?.model) ?? string(requestData.model);
868
+ const generationConfig = record(requestData.generation_config);
869
+ const modelParameters = privateCapture ? {} : {
870
+ ...number(generationConfig?.max_output_tokens) !== void 0 ? { max_tokens: generationConfig?.max_output_tokens } : {},
871
+ ...string(generationConfig?.thinking_level) !== void 0 ? { thinking_level: generationConfig?.thinking_level } : {}
872
+ };
873
+ await captureAiGeneration(this.phClient, {
874
+ ...posthogParams,
875
+ model,
876
+ provider: "gemini",
877
+ input: privateCapture ? null : inputFromRequest(sanitizeGemini(requestData.input, this.phClient), sanitizeGemini(requestData.system_instruction, this.phClient)),
878
+ output: privateCapture ? null : sanitizeGemini(output ?? outputFromSteps(interaction?.steps, string(interaction?.output_text)), this.phClient),
879
+ latency: (Date.now() - startTime) / 1e3,
880
+ timeToFirstToken: firstTokenTime === void 0 ? void 0 : (firstTokenTime - startTime) / 1e3,
881
+ baseURL: "https://generativelanguage.googleapis.com",
882
+ modelParameters,
883
+ completionId: string(interaction?.id),
884
+ stopReason: string(interaction?.status),
885
+ servedServiceTier: string(interaction?.service_tier),
886
+ usage: usageFromInteraction(interaction?.usage, model),
887
+ tools: privateCapture ? void 0 : safeTools(requestData.tools),
888
+ error: privateCapture && error ? /* @__PURE__ */ new Error("Gemini interaction failed") : error
889
+ });
890
+ } catch {}
891
+ };
892
+ try {
893
+ if (!this.client.interactions?.create) throw new Error("Gemini Interactions requires @google/genai 2.18.0 or newer");
894
+ const result = await (options === void 0 ? this.client.interactions.create(request) : this.client.interactions.create(request, options));
895
+ if (requestData.stream === true) {
896
+ if (!isStream(result)) throw new Error("Gemini Interactions did not return a stream");
897
+ return this.wrapStream(result, capture);
898
+ }
899
+ if (isStream(result)) throw new Error("Gemini Interactions returned an unexpected stream");
900
+ const interaction = record(result);
901
+ let unsupportedSchema = false;
902
+ try {
903
+ unsupportedSchema = Array.isArray(interaction?.outputs) && !Array.isArray(interaction?.steps);
904
+ } catch {
905
+ return result;
906
+ }
907
+ if (unsupportedSchema) throw new Error("Gemini Interactions GA schema requires @google/genai 2.18.0 or newer");
908
+ try {
909
+ const status = interaction?.status;
910
+ if (status !== "queued" && status !== "in_progress") await capture(interaction, isFailure(status) ? /* @__PURE__ */ new Error(`Gemini interaction ${status}`) : void 0);
911
+ } catch {}
912
+ return result;
913
+ } catch (error) {
914
+ await capture(void 0, error);
915
+ throw error;
916
+ }
917
+ }
918
+ wrapStream(stream, capture) {
919
+ const reader = "getReader" in stream && typeof stream.getReader === "function" ? stream.getReader() : void 0;
920
+ const iterator = reader ? void 0 : stream[Symbol.asyncIterator]();
921
+ let readerReleased = false;
922
+ let errorIterator;
923
+ let readError;
924
+ const next = async () => {
925
+ if (!reader) return iterator.next();
926
+ try {
927
+ return await reader.read();
928
+ } catch (providerError) {
929
+ if (closed) throw providerError;
930
+ readError = providerError;
931
+ release();
932
+ try {
933
+ errorIterator = stream[Symbol.asyncIterator]();
934
+ } catch {
935
+ throw providerError;
936
+ }
937
+ try {
938
+ await errorIterator.next();
939
+ } finally {
940
+ try {
941
+ await errorIterator.return?.();
942
+ } catch {}
943
+ }
944
+ throw providerError;
945
+ }
946
+ };
947
+ const cancelSource = async (reason) => {
948
+ if (reader && !readerReleased) await reader.cancel(reason);
949
+ else if (reader) {
950
+ try {
951
+ await errorIterator?.return?.();
952
+ } catch {}
953
+ throw readError;
954
+ } else await iterator?.return?.();
955
+ };
956
+ const release = () => {
957
+ if (readerReleased) return;
958
+ try {
959
+ reader?.releaseLock();
960
+ readerReleased = true;
961
+ } catch {}
962
+ };
963
+ let interaction;
964
+ let latestUsage;
965
+ let finalUsage;
966
+ let error;
967
+ let firstTokenTime;
968
+ const steps = /* @__PURE__ */ new Map();
969
+ let completed = false;
970
+ let closed = false;
971
+ let cancelled = false;
972
+ let captured = false;
973
+ const observe = (event) => {
974
+ let schemaError;
975
+ try {
976
+ const item = record(event);
977
+ const eventType = item?.event_type;
978
+ if (eventType === "interaction.start" || eventType === "interaction.complete" || eventType === "interaction.delta" || typeof eventType === "string" && eventType.startsWith("content.")) schemaError = /* @__PURE__ */ new Error("Gemini Interactions GA schema requires @google/genai 2.18.0 or newer");
979
+ else {
980
+ const usage = record(record(item?.metadata)?.total_usage) ?? (eventType === "step.stop" ? record(item?.usage) : void 0);
981
+ if (usage) latestUsage = { ...usage };
982
+ if (eventType === "interaction.created") {
983
+ interaction = { ...record(item?.interaction) };
984
+ const initialUsage = record(interaction.usage);
985
+ if (initialUsage) latestUsage = { ...initialUsage };
986
+ } else if (eventType === "step.start") {
987
+ const index = number(item?.index);
988
+ const step = record(item?.step);
989
+ if (index !== void 0) steps.set(index, {
990
+ ...step,
991
+ content: Array.isArray(step?.content) ? step.content.map((item) => ({ ...record(item) })) : []
992
+ });
993
+ } else if (eventType === "step.delta") {
994
+ const index = number(item?.index);
995
+ const delta = record(item?.delta);
996
+ const step = index === void 0 ? void 0 : steps.get(index);
997
+ if (step && delta?.type === "text" && typeof delta.text === "string") {
998
+ const last = step.content?.at(-1);
999
+ const text = record(last);
1000
+ if (text?.type === "text") text.text = `${text.text ?? ""}${delta.text}`;
1001
+ else step.content?.push({
1002
+ type: "text",
1003
+ text: delta.text
1004
+ });
1005
+ firstTokenTime ??= Date.now();
1006
+ } else if (step && (delta?.type === "image" || delta?.type === "audio" || delta?.type === "video" || delta?.type === "document")) {
1007
+ step.content?.push({ ...delta });
1008
+ firstTokenTime ??= Date.now();
1009
+ } else if (step && delta?.type === "arguments_delta" && typeof delta.arguments === "string") {
1010
+ step.argumentChunks = `${step.argumentChunks ?? ""}${delta.arguments}`;
1011
+ firstTokenTime ??= Date.now();
1012
+ }
1013
+ } else if (eventType === "interaction.completed") {
1014
+ const result = record(item?.interaction);
1015
+ interaction = {
1016
+ ...interaction,
1017
+ ...result
1018
+ };
1019
+ completed = interaction.status !== "queued" && interaction.status !== "in_progress";
1020
+ const reportedUsage = record(result?.usage);
1021
+ if (reportedUsage) {
1022
+ latestUsage = { ...reportedUsage };
1023
+ if (completed) finalUsage = latestUsage;
1024
+ }
1025
+ if (isFailure(interaction.status)) error = /* @__PURE__ */ new Error(`Gemini interaction ${interaction.status}`);
1026
+ if (!completed) error = /* @__PURE__ */ new Error("Gemini interaction stream ended without a terminal result");
1027
+ } else if (eventType === "error") error = /* @__PURE__ */ new Error("Gemini interaction stream failed");
1028
+ }
1029
+ } catch {}
1030
+ if (schemaError) throw schemaError;
1031
+ };
1032
+ const finish = async (status, providerError) => {
1033
+ if (captured) return;
1034
+ captured = true;
1035
+ try {
1036
+ const streamedSteps = [...steps.entries()].sort(([left], [right]) => left - right).map(([, step]) => ({
1037
+ ...step,
1038
+ arguments: step.argumentChunks === void 0 ? step.arguments : functionArguments(step.argumentChunks)
1039
+ }));
1040
+ const output = outputFromSteps(Array.isArray(interaction?.steps) && interaction.steps.length > 0 ? interaction.steps : streamedSteps);
1041
+ await capture({
1042
+ ...interaction,
1043
+ usage: finalUsage ?? latestUsage,
1044
+ ...completed ? {} : { status }
1045
+ }, providerError ?? error ?? (!completed && status === "incomplete" ? /* @__PURE__ */ new Error("Gemini interaction stream ended without completion") : void 0), output, firstTokenTime);
1046
+ } catch {}
1047
+ };
1048
+ return new ReadableStream({
1049
+ async pull(controller) {
1050
+ try {
1051
+ const result = await next();
1052
+ if (closed) return;
1053
+ if (result.done) {
1054
+ closed = true;
1055
+ await finish("incomplete");
1056
+ release();
1057
+ if (!cancelled) controller.close();
1058
+ } else {
1059
+ observe(result.value);
1060
+ controller.enqueue(result.value);
1061
+ }
1062
+ } catch (providerError) {
1063
+ if (closed) return;
1064
+ closed = true;
1065
+ try {
1066
+ await cancelSource(providerError);
1067
+ } catch {}
1068
+ await finish("failed", providerError);
1069
+ release();
1070
+ if (!cancelled) controller.error(providerError);
1071
+ }
1072
+ },
1073
+ async cancel(reason) {
1074
+ cancelled = true;
1075
+ if (closed) return;
1076
+ closed = true;
1077
+ let cancellationError;
1078
+ try {
1079
+ await cancelSource(reason);
1080
+ } catch (providerError) {
1081
+ cancellationError = providerError;
1082
+ throw providerError;
1083
+ } finally {
1084
+ await finish("cancelled", cancellationError);
1085
+ release();
1086
+ }
1087
+ }
1088
+ });
1089
+ }
1090
+ };
1091
+ //#endregion
721
1092
  //#region src/gemini/index.ts
722
1093
  /** Append streamed text to the last block of the same type, or start a new one. */
723
1094
  function appendStreamedText(content, type, text) {
@@ -746,6 +1117,7 @@ var PostHogGoogleGenAI = class {
746
1117
  this.phClient = posthog;
747
1118
  this.client = new GoogleGenAI(geminiConfig);
748
1119
  this.models = new WrappedModels(this.client, this.phClient);
1120
+ this.interactions = new WrappedInteractions(this.client, this.phClient);
749
1121
  }
750
1122
  };
751
1123
  var WrappedModels = class {