@posthog/ai 8.10.0 → 8.10.2
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/adk/index.cjs +908 -1198
- package/dist/adk/index.cjs.map +1 -1
- package/dist/adk/index.d.ts +109 -109
- package/dist/adk/index.mjs +907 -1196
- package/dist/adk/index.mjs.map +1 -1
- package/dist/anthropic/index.cjs +927 -1111
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.ts +34 -33
- package/dist/anthropic/index.mjs +899 -1102
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +863 -1108
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.d.ts +38 -35
- package/dist/gemini/index.mjs +858 -1103
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +1218 -1546
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +170 -163
- package/dist/index.mjs +1216 -1544
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +851 -1029
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.d.ts +75 -75
- package/dist/langchain/index.mjs +850 -1027
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/langchain/middleware/index.cjs +1016 -1225
- package/dist/langchain/middleware/index.cjs.map +1 -1
- package/dist/langchain/middleware/index.d.ts +29 -25
- package/dist/langchain/middleware/index.mjs +1015 -1223
- package/dist/langchain/middleware/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1990 -2523
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.ts +106 -104
- package/dist/openai/index.mjs +1985 -2518
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +745 -827
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.d.ts +48 -47
- package/dist/openai-agents/index.mjs +744 -825
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/otel/index.cjs +427 -486
- package/dist/otel/index.cjs.map +1 -1
- package/dist/otel/index.d.ts +36 -35
- package/dist/otel/index.mjs +426 -484
- package/dist/otel/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +992 -1343
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.d.ts +21 -16
- package/dist/vercel/index.mjs +991 -1341
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +15 -14
package/dist/gemini/index.cjs
CHANGED
|
@@ -1,1184 +1,939 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const isString = value => {
|
|
12
|
-
return typeof value === 'string';
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let _google_genai = require("@google/genai");
|
|
6
|
+
let uuid = require("uuid");
|
|
7
|
+
let _posthog_core = require("@posthog/core");
|
|
8
|
+
//#region src/typeGuards.ts
|
|
9
|
+
const isString = (value) => {
|
|
10
|
+
return typeof value === "string";
|
|
13
11
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/** @internal */
|
|
18
|
-
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/captureAiEvent.ts
|
|
19
14
|
/** @internal */
|
|
20
15
|
function isFullAiCaptureEnabled(client) {
|
|
21
|
-
|
|
16
|
+
return client?.enableFullAiCapture === true;
|
|
22
17
|
}
|
|
23
|
-
|
|
24
18
|
/** @internal */
|
|
25
19
|
function captureAiEvent(client, event) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
if (isFullAiCaptureEnabled(client) && typeof client.captureAi === "function") {
|
|
21
|
+
client.captureAi(event);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
client.capture(event);
|
|
31
25
|
}
|
|
32
|
-
|
|
33
26
|
/** @internal */
|
|
34
27
|
async function captureAiEventImmediate(client, event) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
if (isFullAiCaptureEnabled(client) && typeof client.captureAiImmediate === "function") {
|
|
29
|
+
await client.captureAiImmediate(event);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await client.captureImmediate(event);
|
|
40
33
|
}
|
|
41
|
-
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/sanitization/base64_recognizer.ts
|
|
42
36
|
const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
|
|
43
37
|
const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
38
|
+
var Base64Recognizer = class {
|
|
39
|
+
recognize(value, minLength) {
|
|
40
|
+
const dataUrl = DATA_URL_PREFIX_RE.exec(value);
|
|
41
|
+
if (dataUrl) return {
|
|
42
|
+
kind: "data-url",
|
|
43
|
+
mediaType: dataUrl[1]
|
|
44
|
+
};
|
|
45
|
+
if (value.length < minLength) return { kind: "none" };
|
|
46
|
+
const confidencePrefix = value.slice(0, minLength);
|
|
47
|
+
if (BASE64_ALPHABET_RE.test(confidencePrefix)) return { kind: "raw" };
|
|
48
|
+
else return { kind: "none" };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/sanitization/media_type_context.ts
|
|
53
|
+
const MIME_HINT_KEYS = [
|
|
54
|
+
"mediaType",
|
|
55
|
+
"media_type",
|
|
56
|
+
"mimeType",
|
|
57
|
+
"mime_type"
|
|
58
|
+
];
|
|
59
|
+
const STRONG_CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
60
|
+
"data",
|
|
61
|
+
"file_data",
|
|
62
|
+
"fileData",
|
|
63
|
+
"image_url",
|
|
64
|
+
"imageUrl",
|
|
65
|
+
"video_url",
|
|
66
|
+
"videoUrl",
|
|
67
|
+
"audio",
|
|
68
|
+
"audio_data",
|
|
69
|
+
"audioData",
|
|
70
|
+
"inline_data",
|
|
71
|
+
"inlineData",
|
|
72
|
+
"source",
|
|
73
|
+
"result"
|
|
74
|
+
]);
|
|
75
|
+
const STRONG_CONTEXT_TYPES = /* @__PURE__ */ new Set([
|
|
76
|
+
"image",
|
|
77
|
+
"image_url",
|
|
78
|
+
"input_image",
|
|
79
|
+
"audio",
|
|
80
|
+
"input_audio",
|
|
81
|
+
"video",
|
|
82
|
+
"video_url",
|
|
83
|
+
"file",
|
|
84
|
+
"input_file",
|
|
85
|
+
"document",
|
|
86
|
+
"media",
|
|
87
|
+
"file-data"
|
|
88
|
+
]);
|
|
89
|
+
const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
|
|
90
|
+
"file",
|
|
91
|
+
"input_file",
|
|
92
|
+
"document",
|
|
93
|
+
"media",
|
|
94
|
+
"file-data"
|
|
95
|
+
]);
|
|
96
|
+
const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
|
|
97
|
+
"wav",
|
|
98
|
+
"mp3",
|
|
99
|
+
"ogg",
|
|
100
|
+
"flac",
|
|
101
|
+
"m4a",
|
|
102
|
+
"aac",
|
|
103
|
+
"webm"
|
|
104
|
+
]);
|
|
105
|
+
var MediaTypeContext = class MediaTypeContext {
|
|
106
|
+
static {
|
|
107
|
+
this.EMPTY = new MediaTypeContext(void 0, void 0);
|
|
108
|
+
}
|
|
109
|
+
constructor(parent, key, explicitMediaType) {
|
|
110
|
+
this.parent = parent;
|
|
111
|
+
this.key = key;
|
|
112
|
+
this.explicitMediaType = explicitMediaType;
|
|
113
|
+
}
|
|
114
|
+
inferMediaType() {
|
|
115
|
+
return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
|
|
116
|
+
}
|
|
117
|
+
inferFromSiblingMime() {
|
|
118
|
+
if (this.explicitMediaType) return this.explicitMediaType;
|
|
119
|
+
if (!this.parent) return void 0;
|
|
120
|
+
for (const hint of MIME_HINT_KEYS) {
|
|
121
|
+
const v = this.parent[hint];
|
|
122
|
+
if (typeof v === "string") return v;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
inferFromSiblingFormat() {
|
|
126
|
+
if (!this.parent) return void 0;
|
|
127
|
+
const fmt = this.parent.format;
|
|
128
|
+
if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return `audio/${fmt.toLowerCase()}`;
|
|
129
|
+
}
|
|
130
|
+
inferFromParentType() {
|
|
131
|
+
if (!this.parent) return void 0;
|
|
132
|
+
const t = this.parent.type;
|
|
133
|
+
if (typeof t !== "string") return void 0;
|
|
134
|
+
if (t === "image" || t === "image_url" || t === "input_image") return "image";
|
|
135
|
+
if (t === "audio" || t === "input_audio") return "audio";
|
|
136
|
+
if (t === "video" || t === "video_url") return "video";
|
|
137
|
+
if (FILE_FAMILY_TYPES.has(t)) return "application/octet-stream";
|
|
138
|
+
}
|
|
139
|
+
inferFromKey() {
|
|
140
|
+
if (!this.key) return void 0;
|
|
141
|
+
const key = this.key.toLowerCase();
|
|
142
|
+
if (key.includes("audio")) return "audio";
|
|
143
|
+
if (key.includes("video")) return "video";
|
|
144
|
+
if (key.includes("image")) return "image";
|
|
145
|
+
if (key.includes("file") || key.includes("document")) return "application/octet-stream";
|
|
146
|
+
}
|
|
147
|
+
hasExplicitBinaryMediaType() {
|
|
148
|
+
if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
|
|
149
|
+
const mediaType = this.inferFromSiblingMime();
|
|
150
|
+
return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
|
|
151
|
+
}
|
|
152
|
+
signalsBinary() {
|
|
153
|
+
if (this.explicitMediaType) return true;
|
|
154
|
+
if (this.parent) {
|
|
155
|
+
for (const hint of MIME_HINT_KEYS) if (typeof this.parent[hint] === "string") return true;
|
|
156
|
+
const fmt = this.parent.format;
|
|
157
|
+
if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
|
|
158
|
+
const t = this.parent.type;
|
|
159
|
+
if (typeof t === "string" && STRONG_CONTEXT_TYPES.has(t)) return true;
|
|
160
|
+
}
|
|
161
|
+
if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/sanitization/binary_content_redactor.ts
|
|
139
167
|
const STRONG_CONTEXT_MIN_LENGTH = 64;
|
|
140
168
|
const WEAK_CONTEXT_MIN_LENGTH = 1024;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return value;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
placeholderFor(mediaType) {
|
|
186
|
-
if (!mediaType) return '[base64 redacted]';
|
|
187
|
-
if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
|
|
188
|
-
return `[base64 ${mediaType} redacted]`;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
169
|
+
var BinaryContentRedactor = class {
|
|
170
|
+
constructor(recognizer = new Base64Recognizer()) {
|
|
171
|
+
this.recognizer = recognizer;
|
|
172
|
+
this.visited = /* @__PURE__ */ new WeakSet();
|
|
173
|
+
}
|
|
174
|
+
redact(value, mediaType) {
|
|
175
|
+
this.visited = /* @__PURE__ */ new WeakSet();
|
|
176
|
+
return this.walk(value, mediaType ? new MediaTypeContext(void 0, void 0, mediaType) : MediaTypeContext.EMPTY);
|
|
177
|
+
}
|
|
178
|
+
walk(value, ctx) {
|
|
179
|
+
if (value === null || value === void 0) return value;
|
|
180
|
+
if (typeof value === "string") return this.redactString(value, ctx);
|
|
181
|
+
if (typeof value !== "object") return value;
|
|
182
|
+
if (typeof Uint8Array !== "undefined" && value instanceof Uint8Array) return this.placeholderFor(ctx.inferMediaType());
|
|
183
|
+
if (this.visited.has(value)) return null;
|
|
184
|
+
this.visited.add(value);
|
|
185
|
+
if (Array.isArray(value)) return value.map((item) => this.walk(item, ctx));
|
|
186
|
+
const obj = value;
|
|
187
|
+
const out = {};
|
|
188
|
+
for (const k of Object.keys(obj)) out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
redactString(value, ctx) {
|
|
192
|
+
const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
|
|
193
|
+
const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
|
|
194
|
+
const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
|
|
195
|
+
const recognition = this.recognizer.recognize(recognitionValue, minLength);
|
|
196
|
+
switch (recognition.kind) {
|
|
197
|
+
case "data-url": return this.placeholderFor(recognition.mediaType);
|
|
198
|
+
case "raw": return this.placeholderFor(ctx.inferMediaType());
|
|
199
|
+
case "none": return value;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
placeholderFor(mediaType) {
|
|
203
|
+
if (!mediaType) return "[base64 redacted]";
|
|
204
|
+
if (mediaType === "application/octet-stream") return "[base64 file redacted]";
|
|
205
|
+
return `[base64 ${mediaType} redacted]`;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/sanitization.ts
|
|
192
210
|
const redactor = new BinaryContentRedactor();
|
|
193
211
|
function redactBase64DataUrl(str, mediaType) {
|
|
194
|
-
|
|
212
|
+
return redactor.redact(str, mediaType);
|
|
195
213
|
}
|
|
196
214
|
const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
|
|
197
215
|
const sanitizeGemini = (data, client) => sanitize(data, client);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/utils.ts
|
|
218
|
+
const TOKEN_PROPERTY_KEYS = /* @__PURE__ */ new Set([
|
|
219
|
+
"$ai_input_tokens",
|
|
220
|
+
"$ai_output_tokens",
|
|
221
|
+
"$ai_cache_read_input_tokens",
|
|
222
|
+
"$ai_cache_creation_input_tokens",
|
|
223
|
+
"$ai_total_tokens",
|
|
224
|
+
"$ai_reasoning_tokens"
|
|
225
|
+
]);
|
|
201
226
|
/**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
227
|
+
* Whether the caller supplied their own token counts, which override the ones the SDK
|
|
228
|
+
* derived from the provider response.
|
|
229
|
+
*/
|
|
205
230
|
function hasTokenOverrides(posthogProperties) {
|
|
206
|
-
|
|
231
|
+
return !!posthogProperties && Object.keys(posthogProperties).some((key) => TOKEN_PROPERTY_KEYS.has(key));
|
|
207
232
|
}
|
|
208
233
|
function getTokensSource(posthogProperties) {
|
|
209
|
-
|
|
234
|
+
return hasTokenOverrides(posthogProperties) ? "passthrough" : "sdk";
|
|
210
235
|
}
|
|
211
|
-
const STRING_FORMAT =
|
|
212
|
-
|
|
213
|
-
// Reused across calls to avoid per-invocation allocation; truncate() runs
|
|
214
|
-
// hundreds of times for prompts with many parts.
|
|
236
|
+
const STRING_FORMAT = "utf8";
|
|
215
237
|
new TextEncoder();
|
|
216
|
-
new TextDecoder(STRING_FORMAT, {
|
|
217
|
-
fatal: false
|
|
218
|
-
});
|
|
219
|
-
|
|
238
|
+
new TextDecoder(STRING_FORMAT, { fatal: false });
|
|
220
239
|
/**
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
* Safely converts content to a string, preserving structure for objects/arrays.
|
|
241
|
+
* - If content is already a string, returns it as-is
|
|
242
|
+
* - If content is an object or array, stringifies it with JSON.stringify to preserve structure
|
|
243
|
+
* - Otherwise, converts to string with String()
|
|
244
|
+
*
|
|
245
|
+
* This prevents the "[object Object]" bug when objects are naively converted to strings.
|
|
246
|
+
*
|
|
247
|
+
* @param content - The content to convert to a string
|
|
248
|
+
* @returns A string representation that preserves structure for complex types
|
|
249
|
+
*/
|
|
231
250
|
function toContentString(content) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// Fallback for circular refs, BigInt, or objects with throwing toJSON
|
|
240
|
-
return String(content);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return String(content);
|
|
251
|
+
if (typeof content === "string") return content;
|
|
252
|
+
if (content !== void 0 && content !== null && typeof content === "object") try {
|
|
253
|
+
return JSON.stringify(content);
|
|
254
|
+
} catch {
|
|
255
|
+
return String(content);
|
|
256
|
+
}
|
|
257
|
+
return String(content);
|
|
244
258
|
}
|
|
245
259
|
const getModelParams = (params, responseServiceTier) => {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
260
|
+
if (!params) return {};
|
|
261
|
+
const modelParams = {};
|
|
262
|
+
for (const key of [
|
|
263
|
+
"temperature",
|
|
264
|
+
"max_tokens",
|
|
265
|
+
"max_completion_tokens",
|
|
266
|
+
"top_p",
|
|
267
|
+
"frequency_penalty",
|
|
268
|
+
"presence_penalty",
|
|
269
|
+
"n",
|
|
270
|
+
"stop",
|
|
271
|
+
"stream",
|
|
272
|
+
"streaming",
|
|
273
|
+
"language",
|
|
274
|
+
"response_format",
|
|
275
|
+
"timestamp_granularities",
|
|
276
|
+
"service_tier"
|
|
277
|
+
]) if (key in params && params[key] !== void 0) modelParams[key] = params[key];
|
|
278
|
+
if (responseServiceTier != null) modelParams.service_tier = responseServiceTier;
|
|
279
|
+
return modelParams;
|
|
257
280
|
};
|
|
258
281
|
const buildInlineDataBlock = (mimeType, data) => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
mime_type: mimeType,
|
|
279
|
-
data
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
+
if (mimeType.startsWith("audio/")) return {
|
|
283
|
+
type: "audio",
|
|
284
|
+
mime_type: mimeType,
|
|
285
|
+
data
|
|
286
|
+
};
|
|
287
|
+
if (mimeType.startsWith("image/")) return {
|
|
288
|
+
type: "image",
|
|
289
|
+
inline_data: {
|
|
290
|
+
mime_type: mimeType,
|
|
291
|
+
data
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
return {
|
|
295
|
+
type: "document",
|
|
296
|
+
inline_data: {
|
|
297
|
+
mime_type: mimeType,
|
|
298
|
+
data
|
|
299
|
+
}
|
|
300
|
+
};
|
|
282
301
|
};
|
|
283
302
|
const formatInlineDataBlock = (inlineData, client) => {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
data = isFullAiCaptureEnabled(client) ? data : redactBase64DataUrl(data, mimeType);
|
|
298
|
-
return buildInlineDataBlock(mimeType, String(data ?? ''));
|
|
303
|
+
const mimeType = inlineData.mimeType || inlineData.mime_type || "application/octet-stream";
|
|
304
|
+
let data = inlineData.data;
|
|
305
|
+
if (data instanceof Uint8Array) {
|
|
306
|
+
if (typeof Buffer !== "undefined") data = Buffer.from(data).toString("base64");
|
|
307
|
+
else {
|
|
308
|
+
let binary = "";
|
|
309
|
+
for (let i = 0; i < data.length; i++) binary += String.fromCharCode(data[i]);
|
|
310
|
+
data = btoa(binary);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
data = isFullAiCaptureEnabled(client) ? data : redactBase64DataUrl(data, mimeType);
|
|
314
|
+
return buildInlineDataBlock(mimeType, String(data ?? ""));
|
|
299
315
|
};
|
|
300
316
|
const formatResponseGemini = (response, client) => {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}]
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
} else if (response.text) {
|
|
341
|
-
output.push({
|
|
342
|
-
role: 'assistant',
|
|
343
|
-
content: [{
|
|
344
|
-
type: 'text',
|
|
345
|
-
text: response.text
|
|
346
|
-
}]
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
return output;
|
|
317
|
+
const output = [];
|
|
318
|
+
if (response.candidates && Array.isArray(response.candidates)) {
|
|
319
|
+
for (const candidate of response.candidates) if (candidate.content && candidate.content.parts) {
|
|
320
|
+
const content = [];
|
|
321
|
+
for (const part of candidate.content.parts) if (part.text) content.push({
|
|
322
|
+
type: "text",
|
|
323
|
+
text: part.text
|
|
324
|
+
});
|
|
325
|
+
else if (part.functionCall) content.push({
|
|
326
|
+
type: "function",
|
|
327
|
+
function: {
|
|
328
|
+
name: part.functionCall.name,
|
|
329
|
+
arguments: part.functionCall.args
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
else if (part.inlineData) content.push(formatInlineDataBlock(part.inlineData, client));
|
|
333
|
+
if (content.length > 0) output.push({
|
|
334
|
+
role: "assistant",
|
|
335
|
+
content
|
|
336
|
+
});
|
|
337
|
+
} else if (candidate.text) output.push({
|
|
338
|
+
role: "assistant",
|
|
339
|
+
content: [{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: candidate.text
|
|
342
|
+
}]
|
|
343
|
+
});
|
|
344
|
+
} else if (response.text) output.push({
|
|
345
|
+
role: "assistant",
|
|
346
|
+
content: [{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: response.text
|
|
349
|
+
}]
|
|
350
|
+
});
|
|
351
|
+
return output;
|
|
350
352
|
};
|
|
351
353
|
const withPrivacyMode = (client, privacyMode, input) => {
|
|
352
|
-
|
|
354
|
+
return client.privacy_mode || privacyMode ? null : input;
|
|
353
355
|
};
|
|
354
|
-
|
|
355
356
|
/**
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
* Extract available tool calls from the request parameters.
|
|
358
|
+
* These are the tools provided to the LLM, not the tool calls in the response.
|
|
359
|
+
*/
|
|
359
360
|
const extractAvailableToolCalls = (provider, params) => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
361
|
+
if (provider === "anthropic") {
|
|
362
|
+
if (params.tools) return params.tools;
|
|
363
|
+
return null;
|
|
364
|
+
} else if (provider === "gemini") {
|
|
365
|
+
if (params.config && params.config.tools) return params.config.tools;
|
|
366
|
+
return null;
|
|
367
|
+
} else if (provider === "openai") {
|
|
368
|
+
if (params.tools) return params.tools;
|
|
369
|
+
return null;
|
|
370
|
+
} else if (provider === "vercel") {
|
|
371
|
+
if (params.tools) return params.tools;
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
366
375
|
};
|
|
367
|
-
let AIEvent = /*#__PURE__*/function (AIEvent) {
|
|
368
|
-
AIEvent["Generation"] = "$ai_generation";
|
|
369
|
-
AIEvent["Embedding"] = "$ai_embedding";
|
|
370
|
-
return AIEvent;
|
|
371
|
-
}({});
|
|
372
376
|
function sanitizeValues(obj) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
|
|
380
|
-
} else if (Array.isArray(jsonSafe)) {
|
|
381
|
-
return jsonSafe.map(sanitizeValues);
|
|
382
|
-
} else if (jsonSafe && typeof jsonSafe === 'object') {
|
|
383
|
-
return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
|
|
384
|
-
}
|
|
385
|
-
return jsonSafe;
|
|
377
|
+
if (obj === void 0 || obj === null) return obj;
|
|
378
|
+
const jsonSafe = JSON.parse(JSON.stringify(obj));
|
|
379
|
+
if (typeof jsonSafe === "string") return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
|
|
380
|
+
else if (Array.isArray(jsonSafe)) return jsonSafe.map(sanitizeValues);
|
|
381
|
+
else if (jsonSafe && typeof jsonSafe === "object") return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
|
|
382
|
+
return jsonSafe;
|
|
386
383
|
}
|
|
387
384
|
const POSTHOG_PARAMS_MAP = {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
385
|
+
posthogDistinctId: "distinctId",
|
|
386
|
+
posthogTraceId: "traceId",
|
|
387
|
+
posthogProperties: "properties",
|
|
388
|
+
posthogPrivacyMode: "privacyMode",
|
|
389
|
+
posthogGroups: "groups",
|
|
390
|
+
posthogModelOverride: "modelOverride",
|
|
391
|
+
posthogProviderOverride: "providerOverride",
|
|
392
|
+
posthogCostOverride: "costOverride",
|
|
393
|
+
posthogCaptureImmediate: "captureImmediate"
|
|
397
394
|
};
|
|
398
395
|
function extractPosthogParams(body) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
providerParams: providerParams,
|
|
412
|
-
posthogParams: addDefaults(posthogParams)
|
|
413
|
-
};
|
|
396
|
+
const providerParams = {};
|
|
397
|
+
const posthogParams = {};
|
|
398
|
+
for (const [key, value] of Object.entries(body)) if (POSTHOG_PARAMS_MAP[key]) posthogParams[POSTHOG_PARAMS_MAP[key]] = value;
|
|
399
|
+
else if (key.startsWith("posthog")) console.warn(`Unknown Posthog parameter ${key}`);
|
|
400
|
+
else providerParams[key] = value;
|
|
401
|
+
return {
|
|
402
|
+
providerParams,
|
|
403
|
+
posthogParams: addDefaults(posthogParams)
|
|
404
|
+
};
|
|
414
405
|
}
|
|
415
406
|
function addDefaults(params) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
407
|
+
return {
|
|
408
|
+
...params,
|
|
409
|
+
privacyMode: params.privacyMode ?? false,
|
|
410
|
+
traceId: params.traceId ?? (0, uuid.v4)()
|
|
411
|
+
};
|
|
421
412
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region package.json
|
|
415
|
+
var version = "8.10.2";
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/serializeError.ts
|
|
425
418
|
const DEFAULT_MAX_DEPTH = 3;
|
|
426
419
|
const MAX_STACK_LINES = 20;
|
|
427
420
|
function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
out.cause = serializeError(value.cause, depth - 1);
|
|
442
|
-
}
|
|
443
|
-
return out;
|
|
444
|
-
}
|
|
445
|
-
if (Array.isArray(value)) {
|
|
446
|
-
return value.map(item => serializeError(item, depth - 1));
|
|
447
|
-
}
|
|
448
|
-
return value;
|
|
421
|
+
if (depth < 0 || value === null || typeof value !== "object") return value;
|
|
422
|
+
if (value instanceof Error) {
|
|
423
|
+
const out = {
|
|
424
|
+
name: value.name,
|
|
425
|
+
message: value.message,
|
|
426
|
+
stack: truncateStack(value.stack)
|
|
427
|
+
};
|
|
428
|
+
for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
|
|
429
|
+
if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
|
|
433
|
+
return value;
|
|
449
434
|
}
|
|
450
435
|
function stringifyError(error) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
return JSON.stringify({
|
|
461
|
-
message: String(error)
|
|
462
|
-
});
|
|
463
|
-
}
|
|
436
|
+
try {
|
|
437
|
+
return JSON.stringify(sanitizeValues(serializeError(error)));
|
|
438
|
+
} catch {
|
|
439
|
+
if (error instanceof Error) return JSON.stringify({
|
|
440
|
+
name: error.name,
|
|
441
|
+
message: error.message
|
|
442
|
+
});
|
|
443
|
+
return JSON.stringify({ message: String(error) });
|
|
444
|
+
}
|
|
464
445
|
}
|
|
465
446
|
function truncateStack(stack) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (lines.length <= MAX_STACK_LINES) {
|
|
471
|
-
return stack;
|
|
472
|
-
}
|
|
473
|
-
return [...lines.slice(0, MAX_STACK_LINES), '... (truncated)'].join('\n');
|
|
447
|
+
if (!stack) return stack;
|
|
448
|
+
const lines = stack.split("\n");
|
|
449
|
+
if (lines.length <= MAX_STACK_LINES) return stack;
|
|
450
|
+
return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
|
|
474
451
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
|
|
493
|
-
} catch {
|
|
494
|
-
return undefined;
|
|
495
|
-
}
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region src/gatewayWarning.ts
|
|
454
|
+
const POSTHOG_AI_GATEWAY_HOSTS = [
|
|
455
|
+
"gateway.posthog.com",
|
|
456
|
+
"gateway.us.posthog.com",
|
|
457
|
+
"gateway.eu.posthog.com",
|
|
458
|
+
"ai-gateway.us.posthog.com",
|
|
459
|
+
"ai-gateway.eu.posthog.com"
|
|
460
|
+
];
|
|
461
|
+
const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
|
|
462
|
+
const extractHost = (baseURL) => {
|
|
463
|
+
try {
|
|
464
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
|
|
465
|
+
return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
|
|
466
|
+
} catch {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
496
469
|
};
|
|
497
|
-
const isPostHogAiGatewayUrl = baseURL => {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const host = extractHost(baseURL);
|
|
502
|
-
return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
|
|
470
|
+
const isPostHogAiGatewayUrl = (baseURL) => {
|
|
471
|
+
if (!baseURL) return false;
|
|
472
|
+
const host = extractHost(baseURL);
|
|
473
|
+
return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
|
|
503
474
|
};
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const warnIfPostHogAiGateway = baseURL => {
|
|
508
|
-
if (!isPostHogAiGatewayUrl(baseURL)) {
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
console.warn('[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' + 'Both capture $ai_generation, so every call is double-counted and double-billed. ' + `Use one or the other — see ${GATEWAY_DOCS_URL}.`);
|
|
475
|
+
const warnIfPostHogAiGateway = (baseURL) => {
|
|
476
|
+
if (!isPostHogAiGatewayUrl(baseURL)) return;
|
|
477
|
+
console.warn(`[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. Both capture \$ai_generation, so every call is double-counted and double-billed. Use one or the other — see ${GATEWAY_DOCS_URL}.`);
|
|
512
478
|
};
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
* Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
|
|
516
|
-
* directly so that any caller — first-party SDK wrappers and external code
|
|
517
|
-
* alike — produces an identical event.
|
|
518
|
-
*/
|
|
519
|
-
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/captureAiGeneration.ts
|
|
520
481
|
/**
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
482
|
+
* Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
|
|
483
|
+
*
|
|
484
|
+
* This is the canonical primitive that every `@posthog/ai` wrapper
|
|
485
|
+
* (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
|
|
486
|
+
* external code can use it directly to instrument LLM calls made through
|
|
487
|
+
* arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
|
|
488
|
+
* same events the SDK wrappers produce.
|
|
489
|
+
*
|
|
490
|
+
* When `error` is set, the event is captured as an error. If the error is an
|
|
491
|
+
* object, it is mutated in place to set `__posthog_previously_captured_error`
|
|
492
|
+
* so callers can re-throw the original error reference safely.
|
|
493
|
+
*/
|
|
533
494
|
const captureAiGeneration = async (client, options) => {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
...(usage.cacheReportingExclusive !== undefined && !tokensOverridden ? {
|
|
621
|
-
$ai_cache_reporting_exclusive: usage.cacheReportingExclusive
|
|
622
|
-
} : {}),
|
|
623
|
-
...(usage.webSearchCount ? {
|
|
624
|
-
$ai_web_search_count: usage.webSearchCount
|
|
625
|
-
} : {}),
|
|
626
|
-
...(usage.rawUsage ? {
|
|
627
|
-
$ai_usage: usage.rawUsage
|
|
628
|
-
} : {})
|
|
629
|
-
};
|
|
630
|
-
const properties = {
|
|
631
|
-
$ai_lib: 'posthog-ai',
|
|
632
|
-
$ai_lib_version: version,
|
|
633
|
-
$ai_provider: options.providerOverride ?? options.provider,
|
|
634
|
-
$ai_model: options.modelOverride ?? options.model,
|
|
635
|
-
$ai_model_parameters: options.modelParameters ?? {},
|
|
636
|
-
$ai_input: safeInput,
|
|
637
|
-
$ai_output_choices: safeOutput,
|
|
638
|
-
$ai_http_status: httpStatus,
|
|
639
|
-
...(usage.inputTokens !== undefined ? {
|
|
640
|
-
$ai_input_tokens: usage.inputTokens
|
|
641
|
-
} : {}),
|
|
642
|
-
...(usage.outputTokens !== undefined ? {
|
|
643
|
-
$ai_output_tokens: usage.outputTokens
|
|
644
|
-
} : {}),
|
|
645
|
-
...additionalTokenValues,
|
|
646
|
-
...(options.latency !== undefined ? {
|
|
647
|
-
$ai_latency: options.latency
|
|
648
|
-
} : {}),
|
|
649
|
-
...(options.timeToFirstToken !== undefined ? {
|
|
650
|
-
$ai_time_to_first_token: options.timeToFirstToken
|
|
651
|
-
} : {}),
|
|
652
|
-
$ai_trace_id: traceId,
|
|
653
|
-
...(options.baseURL === null ? {} : {
|
|
654
|
-
$ai_base_url: options.baseURL ?? ''
|
|
655
|
-
}),
|
|
656
|
-
...options.properties,
|
|
657
|
-
$ai_tokens_source: getTokensSource(options.properties),
|
|
658
|
-
...(options.distinctId ? {} : {
|
|
659
|
-
$process_person_profile: false
|
|
660
|
-
}),
|
|
661
|
-
...(options.stopReason ? {
|
|
662
|
-
$ai_stop_reason: options.stopReason
|
|
663
|
-
} : {}),
|
|
664
|
-
...(options.tools ? {
|
|
665
|
-
$ai_tools: options.tools
|
|
666
|
-
} : {}),
|
|
667
|
-
...(options.completionId ? {
|
|
668
|
-
$ai_completion_id: options.completionId
|
|
669
|
-
} : {}),
|
|
670
|
-
...(options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? {
|
|
671
|
-
$ai_provider_metadata: options.providerMetadata
|
|
672
|
-
} : {}),
|
|
673
|
-
...errorData,
|
|
674
|
-
...costOverrideData
|
|
675
|
-
};
|
|
676
|
-
const event = {
|
|
677
|
-
distinctId: options.distinctId ?? traceId,
|
|
678
|
-
event: eventType,
|
|
679
|
-
properties,
|
|
680
|
-
groups: options.groups
|
|
681
|
-
};
|
|
682
|
-
if (options.captureImmediate) {
|
|
683
|
-
await captureAiEventImmediate(client, event);
|
|
684
|
-
} else {
|
|
685
|
-
captureAiEvent(client, event);
|
|
686
|
-
}
|
|
687
|
-
} catch (error) {
|
|
688
|
-
// Telemetry failures must never affect the instrumented provider call.
|
|
689
|
-
try {
|
|
690
|
-
options.onError?.(error);
|
|
691
|
-
} catch {
|
|
692
|
-
// Error reporting must not affect the instrumented provider call either.
|
|
693
|
-
}
|
|
694
|
-
console.warn('[PostHog AI] Failed to capture generation telemetry:', error);
|
|
695
|
-
}
|
|
495
|
+
try {
|
|
496
|
+
if (!client.capture) return;
|
|
497
|
+
warnIfPostHogAiGateway(options.baseURL);
|
|
498
|
+
const traceId = options.traceId ?? (0, uuid.v4)();
|
|
499
|
+
const eventType = options.eventType ?? "$ai_generation";
|
|
500
|
+
const privacyMode = options.privacyMode ?? false;
|
|
501
|
+
const usage = options.usage ?? {};
|
|
502
|
+
const shouldRedact = withPrivacyMode(client, privacyMode, false) === null;
|
|
503
|
+
const safeInput = shouldRedact ? null : (0, _posthog_core.toJsonSafeValue)(options.input);
|
|
504
|
+
const safeOutput = shouldRedact ? null : (0, _posthog_core.toJsonSafeValue)(options.output);
|
|
505
|
+
let httpStatus = options.httpStatus;
|
|
506
|
+
let errorData = {};
|
|
507
|
+
if (options.error) {
|
|
508
|
+
if (httpStatus === void 0) {
|
|
509
|
+
if (typeof options.error === "object" && "status" in options.error && typeof options.error.status === "number") httpStatus = options.error.status;
|
|
510
|
+
else if (typeof options.error === "object" && "statusCode" in options.error && typeof options.error.statusCode === "number") httpStatus = options.error.statusCode;
|
|
511
|
+
else httpStatus = 500;
|
|
512
|
+
}
|
|
513
|
+
let exceptionId;
|
|
514
|
+
if (client.options?.enableExceptionAutocapture) {
|
|
515
|
+
exceptionId = (0, _posthog_core.uuidv7)();
|
|
516
|
+
client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
|
|
517
|
+
if (typeof options.error === "object") options.error.__posthog_previously_captured_error = true;
|
|
518
|
+
}
|
|
519
|
+
errorData = {
|
|
520
|
+
$ai_is_error: true,
|
|
521
|
+
$ai_error: stringifyError(options.error),
|
|
522
|
+
$exception_event_id: exceptionId
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
httpStatus = httpStatus ?? 200;
|
|
526
|
+
const costOverrideData = {};
|
|
527
|
+
if (options.costOverride) {
|
|
528
|
+
if (usage.inputTokens !== void 0) costOverrideData.$ai_input_cost_usd = (options.costOverride.inputCost ?? 0) * usage.inputTokens;
|
|
529
|
+
if (usage.outputTokens !== void 0) costOverrideData.$ai_output_cost_usd = (options.costOverride.outputCost ?? 0) * usage.outputTokens;
|
|
530
|
+
if (Object.keys(costOverrideData).length > 0) costOverrideData.$ai_total_cost_usd = (costOverrideData.$ai_input_cost_usd ?? 0) + (costOverrideData.$ai_output_cost_usd ?? 0);
|
|
531
|
+
}
|
|
532
|
+
const tokensOverridden = hasTokenOverrides(options.properties);
|
|
533
|
+
const additionalTokenValues = {
|
|
534
|
+
...usage.reasoningTokens ? { $ai_reasoning_tokens: usage.reasoningTokens } : {},
|
|
535
|
+
...usage.cacheReadInputTokens ? { $ai_cache_read_input_tokens: usage.cacheReadInputTokens } : {},
|
|
536
|
+
...usage.cacheCreationInputTokens ? { $ai_cache_creation_input_tokens: usage.cacheCreationInputTokens } : {},
|
|
537
|
+
...usage.cacheReportingExclusive !== void 0 && !tokensOverridden ? { $ai_cache_reporting_exclusive: usage.cacheReportingExclusive } : {},
|
|
538
|
+
...usage.webSearchCount ? { $ai_web_search_count: usage.webSearchCount } : {},
|
|
539
|
+
...usage.rawUsage ? { $ai_usage: usage.rawUsage } : {}
|
|
540
|
+
};
|
|
541
|
+
const properties = {
|
|
542
|
+
$ai_lib: "posthog-ai",
|
|
543
|
+
$ai_lib_version: version,
|
|
544
|
+
$ai_provider: options.providerOverride ?? options.provider,
|
|
545
|
+
$ai_model: options.modelOverride ?? options.model,
|
|
546
|
+
$ai_model_parameters: options.modelParameters ?? {},
|
|
547
|
+
$ai_input: safeInput,
|
|
548
|
+
$ai_output_choices: safeOutput,
|
|
549
|
+
$ai_http_status: httpStatus,
|
|
550
|
+
...usage.inputTokens !== void 0 ? { $ai_input_tokens: usage.inputTokens } : {},
|
|
551
|
+
...usage.outputTokens !== void 0 ? { $ai_output_tokens: usage.outputTokens } : {},
|
|
552
|
+
...additionalTokenValues,
|
|
553
|
+
...options.latency !== void 0 ? { $ai_latency: options.latency } : {},
|
|
554
|
+
...options.timeToFirstToken !== void 0 ? { $ai_time_to_first_token: options.timeToFirstToken } : {},
|
|
555
|
+
$ai_trace_id: traceId,
|
|
556
|
+
...options.baseURL === null ? {} : { $ai_base_url: options.baseURL ?? "" },
|
|
557
|
+
...options.properties,
|
|
558
|
+
$ai_tokens_source: getTokensSource(options.properties),
|
|
559
|
+
...options.distinctId ? {} : { $process_person_profile: false },
|
|
560
|
+
...options.stopReason ? { $ai_stop_reason: options.stopReason } : {},
|
|
561
|
+
...options.tools ? { $ai_tools: options.tools } : {},
|
|
562
|
+
...options.completionId ? { $ai_completion_id: options.completionId } : {},
|
|
563
|
+
...options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? { $ai_provider_metadata: options.providerMetadata } : {},
|
|
564
|
+
...errorData,
|
|
565
|
+
...costOverrideData
|
|
566
|
+
};
|
|
567
|
+
const event = {
|
|
568
|
+
distinctId: options.distinctId ?? traceId,
|
|
569
|
+
event: eventType,
|
|
570
|
+
properties,
|
|
571
|
+
groups: options.groups
|
|
572
|
+
};
|
|
573
|
+
if (options.captureImmediate) await captureAiEventImmediate(client, event);
|
|
574
|
+
else captureAiEvent(client, event);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
try {
|
|
577
|
+
options.onError?.(error);
|
|
578
|
+
} catch {}
|
|
579
|
+
console.warn("[PostHog AI] Failed to capture generation telemetry:", error);
|
|
580
|
+
}
|
|
696
581
|
};
|
|
697
|
-
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/gemini/usage.ts
|
|
698
584
|
/** Map Gemini usage metadata to PostHog's provider-agnostic token fields. */
|
|
699
585
|
function mapGeminiUsage(metadata, additionalUsage = {}) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
cacheReportingExclusive: false
|
|
710
|
-
} : {}),
|
|
711
|
-
...additionalUsage,
|
|
712
|
-
rawUsage: metadata
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
class PostHogGoogleGenAI {
|
|
717
|
-
constructor(config) {
|
|
718
|
-
const {
|
|
719
|
-
posthog,
|
|
720
|
-
...geminiConfig
|
|
721
|
-
} = config;
|
|
722
|
-
this.phClient = posthog;
|
|
723
|
-
this.client = new genai.GoogleGenAI(geminiConfig);
|
|
724
|
-
this.models = new WrappedModels(this.client, this.phClient);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
class WrappedModels {
|
|
728
|
-
constructor(client, phClient) {
|
|
729
|
-
this.client = client;
|
|
730
|
-
this.phClient = phClient;
|
|
731
|
-
}
|
|
732
|
-
async generateContent(params) {
|
|
733
|
-
const {
|
|
734
|
-
providerParams: geminiParams,
|
|
735
|
-
posthogParams
|
|
736
|
-
} = extractPosthogParams(params);
|
|
737
|
-
const startTime = Date.now();
|
|
738
|
-
try {
|
|
739
|
-
const response = await this.client.models.generateContent(geminiParams);
|
|
740
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
741
|
-
const availableTools = extractAvailableToolCalls('gemini', geminiParams);
|
|
742
|
-
const metadata = response.usageMetadata;
|
|
743
|
-
const finishReason = response.candidates?.[0]?.finishReason;
|
|
744
|
-
await captureAiGeneration(this.phClient, {
|
|
745
|
-
...posthogParams,
|
|
746
|
-
model: geminiParams.model,
|
|
747
|
-
provider: 'gemini',
|
|
748
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
749
|
-
output: formatResponseGemini(response, this.phClient),
|
|
750
|
-
latency,
|
|
751
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
752
|
-
modelParameters: getModelParams(params),
|
|
753
|
-
httpStatus: 200,
|
|
754
|
-
usage: mapGeminiUsage(metadata, {
|
|
755
|
-
webSearchCount: calculateGoogleWebSearchCount(response)
|
|
756
|
-
}),
|
|
757
|
-
stopReason: finishReason ?? undefined,
|
|
758
|
-
tools: availableTools
|
|
759
|
-
});
|
|
760
|
-
return response;
|
|
761
|
-
} catch (error) {
|
|
762
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
763
|
-
await captureAiGeneration(this.phClient, {
|
|
764
|
-
...posthogParams,
|
|
765
|
-
model: geminiParams.model,
|
|
766
|
-
provider: 'gemini',
|
|
767
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
768
|
-
output: [],
|
|
769
|
-
latency,
|
|
770
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
771
|
-
modelParameters: getModelParams(params),
|
|
772
|
-
usage: {},
|
|
773
|
-
error
|
|
774
|
-
});
|
|
775
|
-
throw error;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
async *generateContentStream(params) {
|
|
779
|
-
const {
|
|
780
|
-
providerParams: geminiParams,
|
|
781
|
-
posthogParams
|
|
782
|
-
} = extractPosthogParams(params);
|
|
783
|
-
const startTime = Date.now();
|
|
784
|
-
const accumulatedContent = [];
|
|
785
|
-
let firstTokenTime;
|
|
786
|
-
let stopReason;
|
|
787
|
-
let usage = {
|
|
788
|
-
webSearchCount: 0,
|
|
789
|
-
rawUsage: undefined
|
|
790
|
-
};
|
|
791
|
-
let errored = false;
|
|
792
|
-
try {
|
|
793
|
-
const stream = await this.client.models.generateContentStream(geminiParams);
|
|
794
|
-
for await (const chunk of stream) {
|
|
795
|
-
// Track first token time when we get text content
|
|
796
|
-
if (firstTokenTime === undefined && chunk.text) {
|
|
797
|
-
firstTokenTime = Date.now();
|
|
798
|
-
}
|
|
799
|
-
const chunkWebSearchCount = calculateGoogleWebSearchCount(chunk);
|
|
800
|
-
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
|
|
801
|
-
usage.webSearchCount = chunkWebSearchCount;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Handle text content
|
|
805
|
-
if (chunk.text) {
|
|
806
|
-
// Find if we already have a text item to append to
|
|
807
|
-
let lastTextItem;
|
|
808
|
-
for (let i = accumulatedContent.length - 1; i >= 0; i--) {
|
|
809
|
-
if (accumulatedContent[i].type === 'text') {
|
|
810
|
-
lastTextItem = accumulatedContent[i];
|
|
811
|
-
break;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (lastTextItem && lastTextItem.type === 'text') {
|
|
815
|
-
lastTextItem.text += chunk.text;
|
|
816
|
-
} else {
|
|
817
|
-
accumulatedContent.push({
|
|
818
|
-
type: 'text',
|
|
819
|
-
text: chunk.text
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Track finish reason from candidates
|
|
825
|
-
if (chunk.candidates?.[0]?.finishReason) {
|
|
826
|
-
stopReason = chunk.candidates[0].finishReason;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Handle function calls from candidates
|
|
830
|
-
if (chunk.candidates && Array.isArray(chunk.candidates)) {
|
|
831
|
-
for (const candidate of chunk.candidates) {
|
|
832
|
-
if (candidate.content && candidate.content.parts) {
|
|
833
|
-
for (const part of candidate.content.parts) {
|
|
834
|
-
// Type-safe check for functionCall
|
|
835
|
-
if ('functionCall' in part) {
|
|
836
|
-
if (firstTokenTime === undefined) {
|
|
837
|
-
firstTokenTime = Date.now();
|
|
838
|
-
}
|
|
839
|
-
const funcCall = part.functionCall;
|
|
840
|
-
if (funcCall?.name) {
|
|
841
|
-
accumulatedContent.push({
|
|
842
|
-
type: 'function',
|
|
843
|
-
function: {
|
|
844
|
-
name: funcCall.name,
|
|
845
|
-
arguments: funcCall.args || {}
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// Update usage metadata - handle both old and new field names
|
|
856
|
-
if (chunk.usageMetadata) {
|
|
857
|
-
usage = mapGeminiUsage(chunk.usageMetadata, {
|
|
858
|
-
webSearchCount: usage.webSearchCount
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
yield chunk;
|
|
862
|
-
}
|
|
863
|
-
} catch (error) {
|
|
864
|
-
errored = true;
|
|
865
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
866
|
-
await captureAiGeneration(this.phClient, {
|
|
867
|
-
...posthogParams,
|
|
868
|
-
model: geminiParams.model,
|
|
869
|
-
provider: 'gemini',
|
|
870
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
871
|
-
output: [],
|
|
872
|
-
latency,
|
|
873
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
874
|
-
modelParameters: getModelParams(params),
|
|
875
|
-
usage,
|
|
876
|
-
error
|
|
877
|
-
});
|
|
878
|
-
throw error;
|
|
879
|
-
} finally {
|
|
880
|
-
// A consumer that stops iterating resumes the pending yield as a return,
|
|
881
|
-
// skipping both the loop tail and the catch. Only a finally runs then, so
|
|
882
|
-
// the success capture lives here to cover completion and cancellation.
|
|
883
|
-
if (!errored) {
|
|
884
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
885
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
886
|
-
const availableTools = extractAvailableToolCalls('gemini', geminiParams);
|
|
887
|
-
|
|
888
|
-
// Format output similar to formatResponseGemini
|
|
889
|
-
const output = accumulatedContent.length > 0 ? [{
|
|
890
|
-
role: 'assistant',
|
|
891
|
-
content: accumulatedContent
|
|
892
|
-
}] : [];
|
|
893
|
-
await captureAiGeneration(this.phClient, {
|
|
894
|
-
...posthogParams,
|
|
895
|
-
model: geminiParams.model,
|
|
896
|
-
provider: 'gemini',
|
|
897
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
898
|
-
output,
|
|
899
|
-
latency,
|
|
900
|
-
timeToFirstToken,
|
|
901
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
902
|
-
modelParameters: getModelParams(params),
|
|
903
|
-
httpStatus: 200,
|
|
904
|
-
usage: {
|
|
905
|
-
...usage,
|
|
906
|
-
webSearchCount: usage.webSearchCount,
|
|
907
|
-
rawUsage: usage.rawUsage
|
|
908
|
-
},
|
|
909
|
-
stopReason,
|
|
910
|
-
tools: availableTools
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
async embedContent(params) {
|
|
916
|
-
const {
|
|
917
|
-
providerParams: geminiParams,
|
|
918
|
-
posthogParams
|
|
919
|
-
} = extractPosthogParams(params);
|
|
920
|
-
const startTime = Date.now();
|
|
921
|
-
try {
|
|
922
|
-
const response = await this.client.models.embedContent(geminiParams);
|
|
923
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
924
|
-
const inputTokens = extractEmbeddingTokenCount(response);
|
|
925
|
-
await captureAiGeneration(this.phClient, {
|
|
926
|
-
...posthogParams,
|
|
927
|
-
eventType: AIEvent.Embedding,
|
|
928
|
-
model: geminiParams.model,
|
|
929
|
-
provider: 'gemini',
|
|
930
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
|
|
931
|
-
output: null,
|
|
932
|
-
latency,
|
|
933
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
934
|
-
modelParameters: getModelParams(params),
|
|
935
|
-
httpStatus: 200,
|
|
936
|
-
usage: {
|
|
937
|
-
inputTokens
|
|
938
|
-
}
|
|
939
|
-
});
|
|
940
|
-
return response;
|
|
941
|
-
} catch (error) {
|
|
942
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
943
|
-
await captureAiGeneration(this.phClient, {
|
|
944
|
-
...posthogParams,
|
|
945
|
-
eventType: AIEvent.Embedding,
|
|
946
|
-
model: geminiParams.model,
|
|
947
|
-
provider: 'gemini',
|
|
948
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
|
|
949
|
-
output: null,
|
|
950
|
-
latency,
|
|
951
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
952
|
-
modelParameters: getModelParams(params),
|
|
953
|
-
usage: {},
|
|
954
|
-
error
|
|
955
|
-
});
|
|
956
|
-
throw error;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
formatPartsAsContentBlocks(parts) {
|
|
960
|
-
const blocks = [];
|
|
961
|
-
for (const part of parts) {
|
|
962
|
-
// Handle dict/object with text field
|
|
963
|
-
if (part && typeof part === 'object' && 'text' in part && part.text) {
|
|
964
|
-
blocks.push({
|
|
965
|
-
type: 'text',
|
|
966
|
-
text: String(part.text)
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
// Handle string parts
|
|
970
|
-
else if (typeof part === 'string') {
|
|
971
|
-
blocks.push({
|
|
972
|
-
type: 'text',
|
|
973
|
-
text: part
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
// Handle inlineData (images, audio, PDFs)
|
|
977
|
-
else if (part && typeof part === 'object' && 'inlineData' in part) {
|
|
978
|
-
const inlineData = part.inlineData;
|
|
979
|
-
const mimeType = inlineData.mimeType || inlineData.mime_type || 'application/octet-stream';
|
|
980
|
-
blocks.push(buildInlineDataBlock(mimeType, inlineData.data));
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
return blocks;
|
|
984
|
-
}
|
|
985
|
-
formatInput(contents) {
|
|
986
|
-
if (typeof contents === 'string') {
|
|
987
|
-
return [{
|
|
988
|
-
role: 'user',
|
|
989
|
-
content: contents
|
|
990
|
-
}];
|
|
991
|
-
}
|
|
992
|
-
if (Array.isArray(contents)) {
|
|
993
|
-
return contents.map(item => {
|
|
994
|
-
if (typeof item === 'string') {
|
|
995
|
-
return {
|
|
996
|
-
role: 'user',
|
|
997
|
-
content: item
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
if (item && typeof item === 'object') {
|
|
1001
|
-
const obj = item;
|
|
1002
|
-
if ('text' in obj && obj.text) {
|
|
1003
|
-
return {
|
|
1004
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
1005
|
-
content: obj.text
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
if ('content' in obj && obj.content) {
|
|
1009
|
-
// If content is a list, format it as content blocks
|
|
1010
|
-
if (Array.isArray(obj.content)) {
|
|
1011
|
-
const contentBlocks = this.formatPartsAsContentBlocks(obj.content);
|
|
1012
|
-
return {
|
|
1013
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
1014
|
-
content: contentBlocks
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
return {
|
|
1018
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
1019
|
-
content: obj.content
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
if ('parts' in obj && Array.isArray(obj.parts)) {
|
|
1023
|
-
const contentBlocks = this.formatPartsAsContentBlocks(obj.parts);
|
|
1024
|
-
return {
|
|
1025
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
1026
|
-
content: contentBlocks
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
return {
|
|
1031
|
-
role: 'user',
|
|
1032
|
-
content: toContentString(item)
|
|
1033
|
-
};
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
if (contents && typeof contents === 'object') {
|
|
1037
|
-
const obj = contents;
|
|
1038
|
-
if ('text' in obj && obj.text) {
|
|
1039
|
-
return [{
|
|
1040
|
-
role: 'user',
|
|
1041
|
-
content: obj.text
|
|
1042
|
-
}];
|
|
1043
|
-
}
|
|
1044
|
-
if ('content' in obj && obj.content) {
|
|
1045
|
-
return [{
|
|
1046
|
-
role: 'user',
|
|
1047
|
-
content: obj.content
|
|
1048
|
-
}];
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
return [{
|
|
1052
|
-
role: 'user',
|
|
1053
|
-
content: toContentString(contents)
|
|
1054
|
-
}];
|
|
1055
|
-
}
|
|
1056
|
-
extractSystemInstruction(params) {
|
|
1057
|
-
if (!params || typeof params !== 'object' || !params.config) {
|
|
1058
|
-
return null;
|
|
1059
|
-
}
|
|
1060
|
-
const config = params.config;
|
|
1061
|
-
if (!('systemInstruction' in config)) {
|
|
1062
|
-
return null;
|
|
1063
|
-
}
|
|
1064
|
-
const systemInstruction = config.systemInstruction;
|
|
1065
|
-
if (typeof systemInstruction === 'string') {
|
|
1066
|
-
return systemInstruction;
|
|
1067
|
-
}
|
|
1068
|
-
if (systemInstruction && typeof systemInstruction === 'object' && 'text' in systemInstruction) {
|
|
1069
|
-
return systemInstruction.text;
|
|
1070
|
-
}
|
|
1071
|
-
if (systemInstruction && typeof systemInstruction === 'object' && 'parts' in systemInstruction && Array.isArray(systemInstruction.parts)) {
|
|
1072
|
-
for (const part of systemInstruction.parts) {
|
|
1073
|
-
if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
|
|
1074
|
-
return part.text;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
if (Array.isArray(systemInstruction)) {
|
|
1079
|
-
for (const part of systemInstruction) {
|
|
1080
|
-
if (typeof part === 'string') {
|
|
1081
|
-
return part;
|
|
1082
|
-
}
|
|
1083
|
-
if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
|
|
1084
|
-
return part.text;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
return null;
|
|
1089
|
-
}
|
|
1090
|
-
formatInputForPostHog(params) {
|
|
1091
|
-
const sanitized = sanitizeGemini(params.contents, this.phClient);
|
|
1092
|
-
const messages = this.formatInput(sanitized);
|
|
1093
|
-
const systemInstruction = this.extractSystemInstruction(params);
|
|
1094
|
-
if (systemInstruction) {
|
|
1095
|
-
const hasSystemMessage = messages.some(msg => msg.role === 'system');
|
|
1096
|
-
if (!hasSystemMessage) {
|
|
1097
|
-
return [{
|
|
1098
|
-
role: 'system',
|
|
1099
|
-
content: systemInstruction
|
|
1100
|
-
}, ...messages];
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
return messages;
|
|
1104
|
-
}
|
|
586
|
+
return {
|
|
587
|
+
inputTokens: metadata?.promptTokenCount ?? 0,
|
|
588
|
+
outputTokens: metadata?.candidatesTokenCount ?? 0,
|
|
589
|
+
reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
|
|
590
|
+
cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0,
|
|
591
|
+
...metadata?.cachedContentTokenCount ? { cacheReportingExclusive: false } : {},
|
|
592
|
+
...additionalUsage,
|
|
593
|
+
rawUsage: metadata
|
|
594
|
+
};
|
|
1105
595
|
}
|
|
1106
|
-
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region src/gemini/index.ts
|
|
598
|
+
var PostHogGoogleGenAI = class {
|
|
599
|
+
constructor(config) {
|
|
600
|
+
const { posthog, ...geminiConfig } = config;
|
|
601
|
+
this.phClient = posthog;
|
|
602
|
+
this.client = new _google_genai.GoogleGenAI(geminiConfig);
|
|
603
|
+
this.models = new WrappedModels(this.client, this.phClient);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
var WrappedModels = class {
|
|
607
|
+
constructor(client, phClient) {
|
|
608
|
+
this.client = client;
|
|
609
|
+
this.phClient = phClient;
|
|
610
|
+
}
|
|
611
|
+
async generateContent(params) {
|
|
612
|
+
const { providerParams: geminiParams, posthogParams } = extractPosthogParams(params);
|
|
613
|
+
const startTime = Date.now();
|
|
614
|
+
try {
|
|
615
|
+
const response = await this.client.models.generateContent(geminiParams);
|
|
616
|
+
const latency = (Date.now() - startTime) / 1e3;
|
|
617
|
+
const availableTools = extractAvailableToolCalls("gemini", geminiParams);
|
|
618
|
+
const metadata = response.usageMetadata;
|
|
619
|
+
const finishReason = response.candidates?.[0]?.finishReason;
|
|
620
|
+
await captureAiGeneration(this.phClient, {
|
|
621
|
+
...posthogParams,
|
|
622
|
+
model: geminiParams.model,
|
|
623
|
+
provider: "gemini",
|
|
624
|
+
input: this.formatInputForPostHog(geminiParams),
|
|
625
|
+
output: formatResponseGemini(response, this.phClient),
|
|
626
|
+
latency,
|
|
627
|
+
baseURL: "https://generativelanguage.googleapis.com",
|
|
628
|
+
modelParameters: getModelParams(params),
|
|
629
|
+
httpStatus: 200,
|
|
630
|
+
usage: mapGeminiUsage(metadata, { webSearchCount: calculateGoogleWebSearchCount(response) }),
|
|
631
|
+
stopReason: finishReason ?? void 0,
|
|
632
|
+
tools: availableTools
|
|
633
|
+
});
|
|
634
|
+
return response;
|
|
635
|
+
} catch (error) {
|
|
636
|
+
const latency = (Date.now() - startTime) / 1e3;
|
|
637
|
+
await captureAiGeneration(this.phClient, {
|
|
638
|
+
...posthogParams,
|
|
639
|
+
model: geminiParams.model,
|
|
640
|
+
provider: "gemini",
|
|
641
|
+
input: this.formatInputForPostHog(geminiParams),
|
|
642
|
+
output: [],
|
|
643
|
+
latency,
|
|
644
|
+
baseURL: "https://generativelanguage.googleapis.com",
|
|
645
|
+
modelParameters: getModelParams(params),
|
|
646
|
+
usage: {},
|
|
647
|
+
error
|
|
648
|
+
});
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async *generateContentStream(params) {
|
|
653
|
+
const { providerParams: geminiParams, posthogParams } = extractPosthogParams(params);
|
|
654
|
+
const startTime = Date.now();
|
|
655
|
+
const accumulatedContent = [];
|
|
656
|
+
let firstTokenTime;
|
|
657
|
+
let stopReason;
|
|
658
|
+
let usage = {
|
|
659
|
+
webSearchCount: 0,
|
|
660
|
+
rawUsage: void 0
|
|
661
|
+
};
|
|
662
|
+
let errored = false;
|
|
663
|
+
try {
|
|
664
|
+
const stream = await this.client.models.generateContentStream(geminiParams);
|
|
665
|
+
for await (const chunk of stream) {
|
|
666
|
+
if (firstTokenTime === void 0 && chunk.text) firstTokenTime = Date.now();
|
|
667
|
+
const chunkWebSearchCount = calculateGoogleWebSearchCount(chunk);
|
|
668
|
+
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) usage.webSearchCount = chunkWebSearchCount;
|
|
669
|
+
if (chunk.text) {
|
|
670
|
+
let lastTextItem;
|
|
671
|
+
for (let i = accumulatedContent.length - 1; i >= 0; i--) if (accumulatedContent[i].type === "text") {
|
|
672
|
+
lastTextItem = accumulatedContent[i];
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
if (lastTextItem && lastTextItem.type === "text") lastTextItem.text += chunk.text;
|
|
676
|
+
else accumulatedContent.push({
|
|
677
|
+
type: "text",
|
|
678
|
+
text: chunk.text
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
if (chunk.candidates?.[0]?.finishReason) stopReason = chunk.candidates[0].finishReason;
|
|
682
|
+
if (chunk.candidates && Array.isArray(chunk.candidates)) {
|
|
683
|
+
for (const candidate of chunk.candidates) if (candidate.content && candidate.content.parts) {
|
|
684
|
+
for (const part of candidate.content.parts) if ("functionCall" in part) {
|
|
685
|
+
if (firstTokenTime === void 0) firstTokenTime = Date.now();
|
|
686
|
+
const funcCall = part.functionCall;
|
|
687
|
+
if (funcCall?.name) accumulatedContent.push({
|
|
688
|
+
type: "function",
|
|
689
|
+
function: {
|
|
690
|
+
name: funcCall.name,
|
|
691
|
+
arguments: funcCall.args || {}
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (chunk.usageMetadata) usage = mapGeminiUsage(chunk.usageMetadata, { webSearchCount: usage.webSearchCount });
|
|
698
|
+
yield chunk;
|
|
699
|
+
}
|
|
700
|
+
} catch (error) {
|
|
701
|
+
errored = true;
|
|
702
|
+
const latency = (Date.now() - startTime) / 1e3;
|
|
703
|
+
await captureAiGeneration(this.phClient, {
|
|
704
|
+
...posthogParams,
|
|
705
|
+
model: geminiParams.model,
|
|
706
|
+
provider: "gemini",
|
|
707
|
+
input: this.formatInputForPostHog(geminiParams),
|
|
708
|
+
output: [],
|
|
709
|
+
latency,
|
|
710
|
+
baseURL: "https://generativelanguage.googleapis.com",
|
|
711
|
+
modelParameters: getModelParams(params),
|
|
712
|
+
usage,
|
|
713
|
+
error
|
|
714
|
+
});
|
|
715
|
+
throw error;
|
|
716
|
+
} finally {
|
|
717
|
+
if (!errored) {
|
|
718
|
+
const latency = (Date.now() - startTime) / 1e3;
|
|
719
|
+
const timeToFirstToken = firstTokenTime !== void 0 ? (firstTokenTime - startTime) / 1e3 : void 0;
|
|
720
|
+
const availableTools = extractAvailableToolCalls("gemini", geminiParams);
|
|
721
|
+
const output = accumulatedContent.length > 0 ? [{
|
|
722
|
+
role: "assistant",
|
|
723
|
+
content: accumulatedContent
|
|
724
|
+
}] : [];
|
|
725
|
+
await captureAiGeneration(this.phClient, {
|
|
726
|
+
...posthogParams,
|
|
727
|
+
model: geminiParams.model,
|
|
728
|
+
provider: "gemini",
|
|
729
|
+
input: this.formatInputForPostHog(geminiParams),
|
|
730
|
+
output,
|
|
731
|
+
latency,
|
|
732
|
+
timeToFirstToken,
|
|
733
|
+
baseURL: "https://generativelanguage.googleapis.com",
|
|
734
|
+
modelParameters: getModelParams(params),
|
|
735
|
+
httpStatus: 200,
|
|
736
|
+
usage: {
|
|
737
|
+
...usage,
|
|
738
|
+
webSearchCount: usage.webSearchCount,
|
|
739
|
+
rawUsage: usage.rawUsage
|
|
740
|
+
},
|
|
741
|
+
stopReason,
|
|
742
|
+
tools: availableTools
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async embedContent(params) {
|
|
748
|
+
const { providerParams: geminiParams, posthogParams } = extractPosthogParams(params);
|
|
749
|
+
const startTime = Date.now();
|
|
750
|
+
try {
|
|
751
|
+
const response = await this.client.models.embedContent(geminiParams);
|
|
752
|
+
const latency = (Date.now() - startTime) / 1e3;
|
|
753
|
+
const inputTokens = extractEmbeddingTokenCount(response);
|
|
754
|
+
await captureAiGeneration(this.phClient, {
|
|
755
|
+
...posthogParams,
|
|
756
|
+
eventType: "$ai_embedding",
|
|
757
|
+
model: geminiParams.model,
|
|
758
|
+
provider: "gemini",
|
|
759
|
+
input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
|
|
760
|
+
output: null,
|
|
761
|
+
latency,
|
|
762
|
+
baseURL: "https://generativelanguage.googleapis.com",
|
|
763
|
+
modelParameters: getModelParams(params),
|
|
764
|
+
httpStatus: 200,
|
|
765
|
+
usage: { inputTokens }
|
|
766
|
+
});
|
|
767
|
+
return response;
|
|
768
|
+
} catch (error) {
|
|
769
|
+
const latency = (Date.now() - startTime) / 1e3;
|
|
770
|
+
await captureAiGeneration(this.phClient, {
|
|
771
|
+
...posthogParams,
|
|
772
|
+
eventType: "$ai_embedding",
|
|
773
|
+
model: geminiParams.model,
|
|
774
|
+
provider: "gemini",
|
|
775
|
+
input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
|
|
776
|
+
output: null,
|
|
777
|
+
latency,
|
|
778
|
+
baseURL: "https://generativelanguage.googleapis.com",
|
|
779
|
+
modelParameters: getModelParams(params),
|
|
780
|
+
usage: {},
|
|
781
|
+
error
|
|
782
|
+
});
|
|
783
|
+
throw error;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
formatPartsAsContentBlocks(parts) {
|
|
787
|
+
const blocks = [];
|
|
788
|
+
for (const part of parts) if (part && typeof part === "object" && "text" in part && part.text) blocks.push({
|
|
789
|
+
type: "text",
|
|
790
|
+
text: String(part.text)
|
|
791
|
+
});
|
|
792
|
+
else if (typeof part === "string") blocks.push({
|
|
793
|
+
type: "text",
|
|
794
|
+
text: part
|
|
795
|
+
});
|
|
796
|
+
else if (part && typeof part === "object" && "inlineData" in part) {
|
|
797
|
+
const inlineData = part.inlineData;
|
|
798
|
+
const mimeType = inlineData.mimeType || inlineData.mime_type || "application/octet-stream";
|
|
799
|
+
blocks.push(buildInlineDataBlock(mimeType, inlineData.data));
|
|
800
|
+
}
|
|
801
|
+
return blocks;
|
|
802
|
+
}
|
|
803
|
+
formatInput(contents) {
|
|
804
|
+
if (typeof contents === "string") return [{
|
|
805
|
+
role: "user",
|
|
806
|
+
content: contents
|
|
807
|
+
}];
|
|
808
|
+
if (Array.isArray(contents)) return contents.map((item) => {
|
|
809
|
+
if (typeof item === "string") return {
|
|
810
|
+
role: "user",
|
|
811
|
+
content: item
|
|
812
|
+
};
|
|
813
|
+
if (item && typeof item === "object") {
|
|
814
|
+
const obj = item;
|
|
815
|
+
if ("text" in obj && obj.text) return {
|
|
816
|
+
role: isString(obj.role) ? obj.role : "user",
|
|
817
|
+
content: obj.text
|
|
818
|
+
};
|
|
819
|
+
if ("content" in obj && obj.content) {
|
|
820
|
+
if (Array.isArray(obj.content)) {
|
|
821
|
+
const contentBlocks = this.formatPartsAsContentBlocks(obj.content);
|
|
822
|
+
return {
|
|
823
|
+
role: isString(obj.role) ? obj.role : "user",
|
|
824
|
+
content: contentBlocks
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
role: isString(obj.role) ? obj.role : "user",
|
|
829
|
+
content: obj.content
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
if ("parts" in obj && Array.isArray(obj.parts)) {
|
|
833
|
+
const contentBlocks = this.formatPartsAsContentBlocks(obj.parts);
|
|
834
|
+
return {
|
|
835
|
+
role: isString(obj.role) ? obj.role : "user",
|
|
836
|
+
content: contentBlocks
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
role: "user",
|
|
842
|
+
content: toContentString(item)
|
|
843
|
+
};
|
|
844
|
+
});
|
|
845
|
+
if (contents && typeof contents === "object") {
|
|
846
|
+
const obj = contents;
|
|
847
|
+
if ("text" in obj && obj.text) return [{
|
|
848
|
+
role: "user",
|
|
849
|
+
content: obj.text
|
|
850
|
+
}];
|
|
851
|
+
if ("content" in obj && obj.content) return [{
|
|
852
|
+
role: "user",
|
|
853
|
+
content: obj.content
|
|
854
|
+
}];
|
|
855
|
+
}
|
|
856
|
+
return [{
|
|
857
|
+
role: "user",
|
|
858
|
+
content: toContentString(contents)
|
|
859
|
+
}];
|
|
860
|
+
}
|
|
861
|
+
extractSystemInstruction(params) {
|
|
862
|
+
if (!params || typeof params !== "object" || !params.config) return null;
|
|
863
|
+
const config = params.config;
|
|
864
|
+
if (!("systemInstruction" in config)) return null;
|
|
865
|
+
const systemInstruction = config.systemInstruction;
|
|
866
|
+
if (typeof systemInstruction === "string") return systemInstruction;
|
|
867
|
+
if (systemInstruction && typeof systemInstruction === "object" && "text" in systemInstruction) return systemInstruction.text;
|
|
868
|
+
if (systemInstruction && typeof systemInstruction === "object" && "parts" in systemInstruction && Array.isArray(systemInstruction.parts)) {
|
|
869
|
+
for (const part of systemInstruction.parts) if (part && typeof part === "object" && "text" in part && typeof part.text === "string") return part.text;
|
|
870
|
+
}
|
|
871
|
+
if (Array.isArray(systemInstruction)) for (const part of systemInstruction) {
|
|
872
|
+
if (typeof part === "string") return part;
|
|
873
|
+
if (part && typeof part === "object" && "text" in part && typeof part.text === "string") return part.text;
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
formatInputForPostHog(params) {
|
|
878
|
+
const sanitized = sanitizeGemini(params.contents, this.phClient);
|
|
879
|
+
const messages = this.formatInput(sanitized);
|
|
880
|
+
const systemInstruction = this.extractSystemInstruction(params);
|
|
881
|
+
if (systemInstruction) {
|
|
882
|
+
if (!messages.some((msg) => msg.role === "system")) return [{
|
|
883
|
+
role: "system",
|
|
884
|
+
content: systemInstruction
|
|
885
|
+
}, ...messages];
|
|
886
|
+
}
|
|
887
|
+
return messages;
|
|
888
|
+
}
|
|
889
|
+
};
|
|
1107
890
|
/**
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
891
|
+
* Extract total token count from a Gemini embed_content response.
|
|
892
|
+
* Token counts are only available per-embedding via Vertex AI's statistics.tokenCount.
|
|
893
|
+
* Returns 0 if no token counts are available.
|
|
894
|
+
*/
|
|
1112
895
|
function extractEmbeddingTokenCount(response) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
return total;
|
|
896
|
+
let total = 0;
|
|
897
|
+
if (response.embeddings) {
|
|
898
|
+
for (const embedding of response.embeddings) if (embedding.statistics?.tokenCount != null) total += embedding.statistics.tokenCount;
|
|
899
|
+
}
|
|
900
|
+
return total;
|
|
1122
901
|
}
|
|
1123
|
-
|
|
1124
902
|
/**
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
903
|
+
* Detect if Google Search grounding was used in the response.
|
|
904
|
+
* Gemini bills per request that uses grounding, not per individual query.
|
|
905
|
+
* Returns 1 if grounding was used, 0 otherwise.
|
|
906
|
+
*/
|
|
1129
907
|
function calculateGoogleWebSearchCount(response) {
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return true;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// Check for google search in function calls
|
|
1159
|
-
if ('content' in candidate && candidate.content && typeof candidate.content === 'object') {
|
|
1160
|
-
const content = candidate.content;
|
|
1161
|
-
if ('parts' in content && Array.isArray(content.parts)) {
|
|
1162
|
-
return content.parts.some(part => {
|
|
1163
|
-
if (!part || typeof part !== 'object' || !('functionCall' in part)) {
|
|
1164
|
-
return false;
|
|
1165
|
-
}
|
|
1166
|
-
const functionCall = part.functionCall;
|
|
1167
|
-
if (functionCall && typeof functionCall === 'object' && 'name' in functionCall && typeof functionCall.name === 'string') {
|
|
1168
|
-
return functionCall.name.includes('google_search') || functionCall.name.includes('grounding');
|
|
1169
|
-
}
|
|
1170
|
-
return false;
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
return false;
|
|
1175
|
-
});
|
|
1176
|
-
return hasGrounding ? 1 : 0;
|
|
908
|
+
if (!response || typeof response !== "object" || !("candidates" in response)) return 0;
|
|
909
|
+
const candidates = response.candidates;
|
|
910
|
+
if (!Array.isArray(candidates)) return 0;
|
|
911
|
+
return candidates.some((candidate) => {
|
|
912
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
913
|
+
if ("groundingMetadata" in candidate && candidate.groundingMetadata) {
|
|
914
|
+
const metadata = candidate.groundingMetadata;
|
|
915
|
+
if (typeof metadata === "object") {
|
|
916
|
+
if ("webSearchQueries" in metadata && Array.isArray(metadata.webSearchQueries) && metadata.webSearchQueries.length > 0) return true;
|
|
917
|
+
if ("groundingChunks" in metadata && Array.isArray(metadata.groundingChunks) && metadata.groundingChunks.length > 0) return true;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if ("content" in candidate && candidate.content && typeof candidate.content === "object") {
|
|
921
|
+
const content = candidate.content;
|
|
922
|
+
if ("parts" in content && Array.isArray(content.parts)) return content.parts.some((part) => {
|
|
923
|
+
if (!part || typeof part !== "object" || !("functionCall" in part)) return false;
|
|
924
|
+
const functionCall = part.functionCall;
|
|
925
|
+
if (functionCall && typeof functionCall === "object" && "name" in functionCall && typeof functionCall.name === "string") return functionCall.name.includes("google_search") || functionCall.name.includes("grounding");
|
|
926
|
+
return false;
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return false;
|
|
930
|
+
}) ? 1 : 0;
|
|
1177
931
|
}
|
|
1178
|
-
|
|
932
|
+
//#endregion
|
|
1179
933
|
exports.Gemini = PostHogGoogleGenAI;
|
|
1180
934
|
exports.GoogleGenAI = PostHogGoogleGenAI;
|
|
1181
935
|
exports.PostHogGoogleGenAI = PostHogGoogleGenAI;
|
|
1182
|
-
exports.WrappedModels = WrappedModels;
|
|
1183
936
|
exports.default = PostHogGoogleGenAI;
|
|
1184
|
-
|
|
937
|
+
exports.WrappedModels = WrappedModels;
|
|
938
|
+
|
|
939
|
+
//# sourceMappingURL=index.cjs.map
|