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