@posthog/ai 8.11.2 → 8.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +24 -0
  2. package/dist/adk/index.cjs +1 -1
  3. package/dist/adk/index.cjs.map +1 -1
  4. package/dist/adk/index.mjs +1 -1
  5. package/dist/adk/index.mjs.map +1 -1
  6. package/dist/anthropic/index.cjs +1 -1
  7. package/dist/anthropic/index.cjs.map +1 -1
  8. package/dist/anthropic/index.mjs +1 -1
  9. package/dist/anthropic/index.mjs.map +1 -1
  10. package/dist/claude-agent-sdk/index.cjs +1142 -0
  11. package/dist/claude-agent-sdk/index.cjs.map +1 -0
  12. package/dist/claude-agent-sdk/index.d.ts +148 -0
  13. package/dist/claude-agent-sdk/index.mjs +1139 -0
  14. package/dist/claude-agent-sdk/index.mjs.map +1 -0
  15. package/dist/gemini/index.cjs +1 -1
  16. package/dist/gemini/index.cjs.map +1 -1
  17. package/dist/gemini/index.mjs +1 -1
  18. package/dist/gemini/index.mjs.map +1 -1
  19. package/dist/index.cjs +4 -4
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.mjs +4 -4
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/langchain/index.cjs +1 -1
  24. package/dist/langchain/index.cjs.map +1 -1
  25. package/dist/langchain/index.mjs +1 -1
  26. package/dist/langchain/index.mjs.map +1 -1
  27. package/dist/langchain/middleware/index.cjs +1 -1
  28. package/dist/langchain/middleware/index.cjs.map +1 -1
  29. package/dist/langchain/middleware/index.mjs +1 -1
  30. package/dist/langchain/middleware/index.mjs.map +1 -1
  31. package/dist/openai/index.cjs +1 -1
  32. package/dist/openai/index.cjs.map +1 -1
  33. package/dist/openai/index.mjs +1 -1
  34. package/dist/openai/index.mjs.map +1 -1
  35. package/dist/openai-agents/index.cjs +4 -4
  36. package/dist/openai-agents/index.cjs.map +1 -1
  37. package/dist/openai-agents/index.mjs +4 -4
  38. package/dist/openai-agents/index.mjs.map +1 -1
  39. package/dist/vercel/index.cjs +4 -4
  40. package/dist/vercel/index.cjs.map +1 -1
  41. package/dist/vercel/index.mjs +4 -4
  42. package/dist/vercel/index.mjs.map +1 -1
  43. package/package.json +13 -3
@@ -0,0 +1,1142 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
3
+ let uuid = require("uuid");
4
+ let _posthog_core = require("@posthog/core");
5
+ //#region package.json
6
+ var version = "8.12.0";
7
+ //#endregion
8
+ //#region src/captureAiEvent.ts
9
+ /** @internal */
10
+ function isFullAiCaptureEnabled(client) {
11
+ return client?.enableFullAiCapture === true;
12
+ }
13
+ /** @internal */
14
+ function captureAiEvent(client, event) {
15
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAi === "function") {
16
+ client.captureAi(event);
17
+ return;
18
+ }
19
+ client.capture(event);
20
+ }
21
+ /** @internal */
22
+ async function captureAiEventImmediate(client, event) {
23
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAiImmediate === "function") {
24
+ await client.captureAiImmediate(event);
25
+ return;
26
+ }
27
+ await client.captureImmediate(event);
28
+ }
29
+ //#endregion
30
+ //#region src/sanitization/base64_recognizer.ts
31
+ const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
32
+ const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
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
162
+ const STRONG_CONTEXT_MIN_LENGTH = 64;
163
+ const WEAK_CONTEXT_MIN_LENGTH = 1024;
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
205
+ const redactor = new BinaryContentRedactor();
206
+ const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
207
+ const sanitizeAnthropic = (data, client) => sanitize(data, client);
208
+ //#endregion
209
+ //#region src/utils.ts
210
+ const TOKEN_PROPERTY_KEYS = /* @__PURE__ */ new Set([
211
+ "$ai_input_tokens",
212
+ "$ai_output_tokens",
213
+ "$ai_cache_read_input_tokens",
214
+ "$ai_cache_creation_input_tokens",
215
+ "$ai_total_tokens",
216
+ "$ai_reasoning_tokens"
217
+ ]);
218
+ /**
219
+ * Whether the caller supplied their own token counts, which override the ones the SDK
220
+ * derived from the provider response.
221
+ */
222
+ function hasTokenOverrides(posthogProperties) {
223
+ return !!posthogProperties && Object.keys(posthogProperties).some((key) => TOKEN_PROPERTY_KEYS.has(key));
224
+ }
225
+ function getTokensSource(posthogProperties) {
226
+ return hasTokenOverrides(posthogProperties) ? "passthrough" : "sdk";
227
+ }
228
+ const MAX_OUTPUT_SIZE = 2e5;
229
+ const STRING_FORMAT = "utf8";
230
+ const sharedTextEncoder = new TextEncoder();
231
+ const sharedTextDecoder = new TextDecoder(STRING_FORMAT, { fatal: false });
232
+ /**
233
+ * Safely converts content to a string, preserving structure for objects/arrays.
234
+ * - If content is already a string, returns it as-is
235
+ * - If content is an object or array, stringifies it with JSON.stringify to preserve structure
236
+ * - Otherwise, converts to string with String()
237
+ *
238
+ * This prevents the "[object Object]" bug when objects are naively converted to strings.
239
+ *
240
+ * @param content - The content to convert to a string
241
+ * @returns A string representation that preserves structure for complex types
242
+ */
243
+ function toContentString(content) {
244
+ if (typeof content === "string") return content;
245
+ if (content !== void 0 && content !== null && typeof content === "object") try {
246
+ return JSON.stringify(content);
247
+ } catch {
248
+ try {
249
+ return String(content);
250
+ } catch {
251
+ return "";
252
+ }
253
+ }
254
+ return String(content);
255
+ }
256
+ const withPrivacyMode = (client, privacyMode, input) => {
257
+ return client.privacy_mode || privacyMode ? null : input;
258
+ };
259
+ function toSafeString(input) {
260
+ if (input === void 0 || input === null) return "";
261
+ if (typeof input === "string") return input;
262
+ try {
263
+ return JSON.stringify(input);
264
+ } catch {
265
+ console.warn("Failed to stringify input", input);
266
+ return "";
267
+ }
268
+ }
269
+ const truncate = (input, client, maxBytes = MAX_OUTPUT_SIZE) => {
270
+ const str = toSafeString(input);
271
+ if (str === "") return "";
272
+ if (isFullAiCaptureEnabled(client)) return str;
273
+ const buffer = sharedTextEncoder.encode(str);
274
+ if (buffer.length <= maxBytes) return sharedTextDecoder.decode(buffer);
275
+ const truncatedBuffer = buffer.slice(0, maxBytes);
276
+ let truncatedStr = sharedTextDecoder.decode(truncatedBuffer);
277
+ if (truncatedStr.endsWith("�")) truncatedStr = truncatedStr.slice(0, -1);
278
+ return `${truncatedStr}... [truncated]`;
279
+ };
280
+ function sanitizeValues(obj) {
281
+ if (obj === void 0 || obj === null) return obj;
282
+ const jsonSafe = JSON.parse(JSON.stringify(obj));
283
+ if (typeof jsonSafe === "string") return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
284
+ else if (Array.isArray(jsonSafe)) return jsonSafe.map(sanitizeValues);
285
+ else if (jsonSafe && typeof jsonSafe === "object") return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
286
+ return jsonSafe;
287
+ }
288
+ //#endregion
289
+ //#region src/serializeError.ts
290
+ const DEFAULT_MAX_DEPTH = 3;
291
+ const MAX_STACK_LINES = 20;
292
+ function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
293
+ if (depth < 0 || value === null || typeof value !== "object") return value;
294
+ if (value instanceof Error) {
295
+ const out = {
296
+ name: value.name,
297
+ message: value.message,
298
+ stack: truncateStack(value.stack)
299
+ };
300
+ for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
301
+ if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
302
+ return out;
303
+ }
304
+ if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
305
+ return value;
306
+ }
307
+ function stringifyError(error) {
308
+ try {
309
+ return JSON.stringify(sanitizeValues(serializeError(error)));
310
+ } catch {
311
+ if (error instanceof Error) return JSON.stringify({
312
+ name: error.name,
313
+ message: error.message
314
+ });
315
+ return JSON.stringify({ message: String(error) });
316
+ }
317
+ }
318
+ function truncateStack(stack) {
319
+ if (!stack) return stack;
320
+ const lines = stack.split("\n");
321
+ if (lines.length <= MAX_STACK_LINES) return stack;
322
+ return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
323
+ }
324
+ //#endregion
325
+ //#region src/gatewayWarning.ts
326
+ const POSTHOG_AI_GATEWAY_HOSTS = [
327
+ "gateway.posthog.com",
328
+ "gateway.us.posthog.com",
329
+ "gateway.eu.posthog.com",
330
+ "ai-gateway.us.posthog.com",
331
+ "ai-gateway.eu.posthog.com"
332
+ ];
333
+ const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
334
+ const extractHost = (baseURL) => {
335
+ try {
336
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
337
+ return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
338
+ } catch {
339
+ return;
340
+ }
341
+ };
342
+ const isPostHogAiGatewayUrl = (baseURL) => {
343
+ if (!baseURL) return false;
344
+ const host = extractHost(baseURL);
345
+ return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
346
+ };
347
+ const warnIfPostHogAiGateway = (baseURL) => {
348
+ if (!isPostHogAiGatewayUrl(baseURL)) return;
349
+ 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}.`);
350
+ };
351
+ //#endregion
352
+ //#region src/captureAiGeneration.ts
353
+ /**
354
+ * Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
355
+ *
356
+ * This is the canonical primitive that every `@posthog/ai` wrapper
357
+ * (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
358
+ * external code can use it directly to instrument LLM calls made through
359
+ * arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
360
+ * same events the SDK wrappers produce.
361
+ *
362
+ * When `error` is set, the event is captured as an error. If the error is an
363
+ * object, it is mutated in place to set `__posthog_previously_captured_error`
364
+ * so callers can re-throw the original error reference safely.
365
+ */
366
+ const captureAiGeneration = async (client, options) => {
367
+ try {
368
+ if (!client.capture) return;
369
+ warnIfPostHogAiGateway(options.baseURL);
370
+ const traceId = options.traceId ?? (0, uuid.v4)();
371
+ const eventType = options.eventType ?? "$ai_generation";
372
+ const privacyMode = options.privacyMode ?? false;
373
+ const usage = options.usage ?? {};
374
+ const shouldRedact = withPrivacyMode(client, privacyMode, false) === null;
375
+ const safeInput = shouldRedact ? null : (0, _posthog_core.toJsonSafeValue)(options.input);
376
+ const safeOutput = shouldRedact ? null : (0, _posthog_core.toJsonSafeValue)(options.output);
377
+ let httpStatus = options.httpStatus;
378
+ let errorData = {};
379
+ if (options.error) {
380
+ if (httpStatus === void 0) {
381
+ if (typeof options.error === "object" && "status" in options.error && typeof options.error.status === "number") httpStatus = options.error.status;
382
+ else if (typeof options.error === "object" && "statusCode" in options.error && typeof options.error.statusCode === "number") httpStatus = options.error.statusCode;
383
+ else httpStatus = 500;
384
+ }
385
+ let exceptionId;
386
+ if (client.options?.enableExceptionAutocapture) {
387
+ exceptionId = (0, _posthog_core.uuidv7)();
388
+ client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
389
+ if (typeof options.error === "object") options.error.__posthog_previously_captured_error = true;
390
+ }
391
+ errorData = {
392
+ $ai_is_error: true,
393
+ $ai_error: stringifyError(options.error),
394
+ $exception_event_id: exceptionId
395
+ };
396
+ }
397
+ httpStatus = httpStatus ?? 200;
398
+ const costOverrideData = {};
399
+ if (options.costOverride) {
400
+ if (usage.inputTokens !== void 0) costOverrideData.$ai_input_cost_usd = (options.costOverride.inputCost ?? 0) * usage.inputTokens;
401
+ if (usage.outputTokens !== void 0) costOverrideData.$ai_output_cost_usd = (options.costOverride.outputCost ?? 0) * usage.outputTokens;
402
+ if (Object.keys(costOverrideData).length > 0) costOverrideData.$ai_total_cost_usd = (costOverrideData.$ai_input_cost_usd ?? 0) + (costOverrideData.$ai_output_cost_usd ?? 0);
403
+ }
404
+ const tokensOverridden = hasTokenOverrides(options.properties);
405
+ const additionalTokenValues = {
406
+ ...usage.reasoningTokens ? { $ai_reasoning_tokens: usage.reasoningTokens } : {},
407
+ ...usage.cacheReadInputTokens ? { $ai_cache_read_input_tokens: usage.cacheReadInputTokens } : {},
408
+ ...usage.cacheCreationInputTokens ? { $ai_cache_creation_input_tokens: usage.cacheCreationInputTokens } : {},
409
+ ...usage.cacheReportingExclusive !== void 0 && !tokensOverridden ? { $ai_cache_reporting_exclusive: usage.cacheReportingExclusive } : {},
410
+ ...usage.webSearchCount ? { $ai_web_search_count: usage.webSearchCount } : {},
411
+ ...usage.rawUsage ? { $ai_usage: usage.rawUsage } : {}
412
+ };
413
+ const properties = {
414
+ $ai_lib: "posthog-ai",
415
+ $ai_lib_version: version,
416
+ $ai_provider: options.providerOverride ?? options.provider,
417
+ $ai_model: options.modelOverride ?? options.model,
418
+ $ai_model_parameters: options.modelParameters ?? {},
419
+ ...options.servedServiceTier != null ? { $ai_service_tier: options.servedServiceTier } : {},
420
+ $ai_input: safeInput,
421
+ $ai_output_choices: safeOutput,
422
+ $ai_http_status: httpStatus,
423
+ ...usage.inputTokens !== void 0 ? { $ai_input_tokens: usage.inputTokens } : {},
424
+ ...usage.outputTokens !== void 0 ? { $ai_output_tokens: usage.outputTokens } : {},
425
+ ...additionalTokenValues,
426
+ ...options.latency !== void 0 ? { $ai_latency: options.latency } : {},
427
+ ...options.timeToFirstToken !== void 0 ? { $ai_time_to_first_token: options.timeToFirstToken } : {},
428
+ $ai_trace_id: traceId,
429
+ ...options.baseURL === null ? {} : { $ai_base_url: options.baseURL ?? "" },
430
+ ...options.properties,
431
+ $ai_tokens_source: getTokensSource(options.properties),
432
+ ...options.distinctId ? {} : { $process_person_profile: false },
433
+ ...options.stopReason ? { $ai_stop_reason: options.stopReason } : {},
434
+ ...options.tools ? { $ai_tools: options.tools } : {},
435
+ ...options.completionId ? { $ai_completion_id: options.completionId } : {},
436
+ ...options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? { $ai_provider_metadata: options.providerMetadata } : {},
437
+ ...errorData,
438
+ ...costOverrideData
439
+ };
440
+ const event = {
441
+ distinctId: options.distinctId ?? traceId,
442
+ event: eventType,
443
+ properties,
444
+ groups: options.groups
445
+ };
446
+ if (options.captureImmediate) await captureAiEventImmediate(client, event);
447
+ else captureAiEvent(client, event);
448
+ } catch (error) {
449
+ try {
450
+ options.onError?.(error);
451
+ } catch {}
452
+ console.warn("[PostHog AI] Failed to capture generation telemetry:", error);
453
+ }
454
+ };
455
+ //#endregion
456
+ //#region src/claude-agent-sdk/formatting.ts
457
+ /**
458
+ * Tool results in an agent run carry whole files and command output, and the
459
+ * same result is replayed as input on every later turn, so each string is
460
+ * capped before it reaches the event.
461
+ */
462
+ const TOOL_RESULT_MAX_STRING_LENGTH = 5e3;
463
+ function capStrings(value, max) {
464
+ if (typeof value === "string") return truncate(value, void 0, max);
465
+ if (Array.isArray(value)) return value.map((item) => capStrings(item, max));
466
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, capStrings(item, max)]));
467
+ return value;
468
+ }
469
+ function formatContent(content, client, maxBytes = MAX_OUTPUT_SIZE) {
470
+ const redacted = sanitizeAnthropic((0, _posthog_core.toJsonSafeValue)(content), client);
471
+ return isFullAiCaptureEnabled(client) ? redacted : capStrings(redacted, maxBytes);
472
+ }
473
+ /** Read the system prompt out of the SDK options, whatever shape it takes. */
474
+ function extractSystemPrompt(options) {
475
+ const systemPrompt = options?.systemPrompt;
476
+ if (typeof systemPrompt === "string") return systemPrompt;
477
+ if (Array.isArray(systemPrompt)) return systemPrompt.filter((part) => part !== _anthropic_ai_claude_agent_sdk.SYSTEM_PROMPT_DYNAMIC_BOUNDARY).join("");
478
+ return systemPrompt?.append;
479
+ }
480
+ /** Convert Anthropic assistant content blocks into PostHog content items. */
481
+ function formatAssistantBlocks(blocks, client) {
482
+ if (!Array.isArray(blocks)) return [];
483
+ const content = [];
484
+ for (const block of formatContent(blocks, client)) {
485
+ if (block == null) continue;
486
+ if (typeof block.thinking === "string") content.push({
487
+ type: "reasoning",
488
+ text: block.thinking
489
+ });
490
+ else if (block.type === "tool_use") content.push({
491
+ type: "function",
492
+ id: block.id,
493
+ function: {
494
+ name: block.name,
495
+ arguments: block.input ?? {}
496
+ }
497
+ });
498
+ else if (typeof block.text === "string") content.push({
499
+ type: "text",
500
+ text: block.text
501
+ });
502
+ else content.push({
503
+ type: "text",
504
+ text: toContentString(block)
505
+ });
506
+ }
507
+ return content;
508
+ }
509
+ /** Convert an Anthropic user message body into PostHog content items. */
510
+ function formatUserContent(content, client) {
511
+ if (typeof content === "string") return content;
512
+ if (!Array.isArray(content)) return [];
513
+ const formatted = [];
514
+ for (const block of content) {
515
+ if (block == null) continue;
516
+ if (block.type === "tool_result") formatted.push({
517
+ type: "tool_result",
518
+ tool_use_id: block.tool_use_id,
519
+ content: formatContent(block.content, client, TOOL_RESULT_MAX_STRING_LENGTH),
520
+ ...typeof block.is_error === "boolean" ? { is_error: block.is_error } : {}
521
+ });
522
+ else if (typeof block.text === "string") formatted.push({
523
+ type: "text",
524
+ text: block.text
525
+ });
526
+ else formatted.push(formatContent(block, client));
527
+ }
528
+ return formatted;
529
+ }
530
+ //#endregion
531
+ //#region src/claude-agent-sdk/processor.ts
532
+ const FRAMEWORK = "claude-agent-sdk";
533
+ const PROVIDER = "anthropic";
534
+ /**
535
+ * Reconstructs per-generation metrics from the Anthropic streaming protocol the
536
+ * Agent SDK relays: every `message_start` to `message_stop` cycle is one model
537
+ * call.
538
+ */
539
+ var GenerationTracker = class {
540
+ constructor() {
541
+ this._completed = [];
542
+ this._sawStreamEvents = false;
543
+ }
544
+ /**
545
+ * Queue the input of the next model call. The first call answers the prompt,
546
+ * and every later one answers the tool results of the call before it.
547
+ */
548
+ setPendingInput(input) {
549
+ this._pendingInput = input;
550
+ }
551
+ appendPendingInput(input) {
552
+ this._pendingInput = [...this._pendingInput ?? [], ...input];
553
+ }
554
+ /** The queued input, still waiting for its model call. */
555
+ get pendingInput() {
556
+ return this._pendingInput;
557
+ }
558
+ processStreamEvent(event, client, ttftMs) {
559
+ this._sawStreamEvents = true;
560
+ if (event.type === "message_start") {
561
+ this.retryCurrent(/* @__PURE__ */ new Error("Model stream restarted before completing"));
562
+ const usage = event.message.usage;
563
+ this._current = {
564
+ spanId: (0, uuid.v4)(),
565
+ input: this._pendingInput,
566
+ startTime: performance.now() - (ttftMs ?? 0),
567
+ timeToFirstToken: ttftMs != null ? ttftMs / 1e3 : void 0,
568
+ model: event.message.model,
569
+ usage: readUsage(usage),
570
+ streamedOutput: []
571
+ };
572
+ this._pendingInput = void 0;
573
+ } else if (event.type === "content_block_start" && this._current?.streamedOutput) this._current.streamedOutput[event.index] = formatAssistantBlocks([event.content_block], client)[0];
574
+ else if (event.type === "content_block_delta") {
575
+ const block = this._current?.streamedOutput?.[event.index];
576
+ if (event.delta.type === "text_delta" && block?.type === "text") block.text += event.delta.text;
577
+ else if (event.delta.type === "thinking_delta" && block?.type === "reasoning") block.text += event.delta.thinking;
578
+ else if (event.delta.type === "input_json_delta" && block?.type === "function") {
579
+ const previous = block.function.arguments;
580
+ block.function.arguments = (typeof previous === "string" ? previous : "") + event.delta.partial_json;
581
+ }
582
+ } else if (event.type === "message_delta" && this._current) {
583
+ const usage = event.usage;
584
+ this._current.usage = {
585
+ ...this._current.usage,
586
+ ...readUsage(usage),
587
+ rawUsage: {
588
+ ...this._current.usage.rawUsage,
589
+ ...usage
590
+ }
591
+ };
592
+ if (event.delta?.stop_reason != null) this._current.stopReason = event.delta.stop_reason;
593
+ } else if (event.type === "message_stop") this.finishCurrent();
594
+ }
595
+ finishCurrent() {
596
+ if (this._current) {
597
+ this._current.endTime = performance.now();
598
+ this._completed.push(this._current);
599
+ this._lastModel = this._current.model;
600
+ this._current = void 0;
601
+ }
602
+ }
603
+ retryCurrent(error) {
604
+ if (this._current) {
605
+ this._pendingInput = this._current.input;
606
+ this._current.error = error;
607
+ this.finishCurrent();
608
+ }
609
+ }
610
+ setModel(model) {
611
+ if (model) this._lastModel = model;
612
+ }
613
+ popCompleted() {
614
+ return this._completed.shift();
615
+ }
616
+ /** Span ID of the generation in progress, before its `message_stop`. */
617
+ get currentSpanId() {
618
+ return this._current?.spanId;
619
+ }
620
+ get lastModel() {
621
+ return this._lastModel;
622
+ }
623
+ get sawStreamEvents() {
624
+ return this._sawStreamEvents;
625
+ }
626
+ };
627
+ /**
628
+ * Wraps `query()` from `@anthropic-ai/claude-agent-sdk` to capture PostHog LLM
629
+ * analytics.
630
+ *
631
+ * The Agent SDK runs Claude Code itself, so no request passes through an
632
+ * Anthropic client the `@posthog/ai` Anthropic wrapper could patch. This
633
+ * processor reads the SDK's own message stream instead and captures:
634
+ *
635
+ * - `$ai_generation` for each main-agent model call, reconstructed from the streamed
636
+ * Anthropic events;
637
+ * - `$ai_span` for every tool use, parented to the generation that asked for it;
638
+ * - `$ai_trace` for every turn, carrying its latency and reported cost.
639
+ *
640
+ * @example
641
+ * ```typescript
642
+ * import { PostHogClaudeAgentProcessor } from '@posthog/ai/claude-agent-sdk'
643
+ * import { PostHog } from 'posthog-node'
644
+ *
645
+ * const phClient = new PostHog('<POSTHOG_API_KEY>')
646
+ * const processor = new PostHogClaudeAgentProcessor({ client: phClient, distinctId: 'user@example.com' })
647
+ *
648
+ * for await (const message of processor.query({ prompt: 'Explain this repo' })) {
649
+ * console.log(message)
650
+ * }
651
+ * ```
652
+ */
653
+ var PostHogClaudeAgentProcessor = class {
654
+ constructor(options) {
655
+ this._client = options.client;
656
+ this._traceOptions = options;
657
+ this._captureImmediate = options.captureImmediate ?? false;
658
+ this._onError = options.onError;
659
+ }
660
+ /**
661
+ * Drop-in replacement for `query()` from the Claude Agent SDK. Hidden partial
662
+ * messages pass their reply correlation to the first assistant message.
663
+ * Other messages and the control methods (`interrupt`,
664
+ * `setPermissionMode`, …) retain the SDK behavior.
665
+ */
666
+ query({ prompt, options, posthog }) {
667
+ const trace = {
668
+ ...this._traceOptions,
669
+ ...posthog,
670
+ privacyMode: this._traceOptions.privacyMode === true || posthog?.privacyMode === true,
671
+ properties: {
672
+ ...this._traceOptions.properties,
673
+ ...posthog?.properties
674
+ }
675
+ };
676
+ for (const key of [
677
+ "$ai_trace_id",
678
+ "$ai_span_id",
679
+ "$ai_parent_id",
680
+ "$ai_session_id"
681
+ ]) delete trace.properties?.[key];
682
+ const forwardStreamEvents = options?.includePartialMessages === true;
683
+ const state = this._createState(prompt, options, trace);
684
+ const inner = (0, _anthropic_ai_claude_agent_sdk.query)({
685
+ prompt: typeof prompt === "string" ? prompt : this._observePrompt(prompt, state),
686
+ options: {
687
+ ...options,
688
+ includePartialMessages: true
689
+ }
690
+ });
691
+ const instrumented = this._instrument(inner, state, trace, forwardStreamEvents);
692
+ const iteration = {
693
+ next: instrumented.next.bind(instrumented),
694
+ streamInput: (stream) => inner.streamInput(this._observePrompt(stream, state)),
695
+ return: async (value) => {
696
+ try {
697
+ return await inner.return(value);
698
+ } finally {
699
+ await instrumented.return();
700
+ }
701
+ },
702
+ throw: async (error) => {
703
+ state.failure = error;
704
+ try {
705
+ return await inner.throw(error);
706
+ } finally {
707
+ await instrumented.return();
708
+ }
709
+ },
710
+ close: () => {
711
+ try {
712
+ inner.close();
713
+ } finally {
714
+ instrumented.return().catch((error) => this._handleError(error));
715
+ }
716
+ },
717
+ [Symbol.asyncDispose]: async () => {
718
+ try {
719
+ const dispose = Reflect.get(inner, Symbol.asyncDispose);
720
+ if (typeof dispose === "function") await dispose.call(inner);
721
+ else await inner.return();
722
+ } finally {
723
+ await instrumented.return();
724
+ }
725
+ },
726
+ [Symbol.asyncIterator]: () => wrapper
727
+ };
728
+ const wrapper = new Proxy(inner, { get(target, property) {
729
+ if (Object.hasOwn(iteration, property)) return iteration[property];
730
+ const value = Reflect.get(target, property, target);
731
+ return typeof value === "function" && property !== "constructor" ? value.bind(target) : value;
732
+ } });
733
+ return wrapper;
734
+ }
735
+ async *_instrument(inner, state, trace, forwardStreamEvents) {
736
+ try {
737
+ for await (const message of inner) {
738
+ try {
739
+ await this._handleMessage(message, state, trace);
740
+ } catch (error) {
741
+ this._handleError(error);
742
+ }
743
+ if (message.type !== "stream_event" || forwardStreamEvents) {
744
+ if (!forwardStreamEvents && message.type === "assistant" && !message.parent_tool_use_id && state.userMessageUuid) {
745
+ const userMessageUuid = state.userMessageUuid;
746
+ state.userMessageUuid = void 0;
747
+ yield {
748
+ ...message,
749
+ user_message_uuid: message.user_message_uuid ?? userMessageUuid
750
+ };
751
+ } else yield message;
752
+ }
753
+ }
754
+ } catch (error) {
755
+ state.failure = error;
756
+ throw error;
757
+ } finally {
758
+ const failure = state.failure;
759
+ if (state.turnActive || state.turnCaptured || failure !== void 0 && !state.hasCapturedTrace) {
760
+ try {
761
+ state.tracker.finishCurrent();
762
+ await this._captureCompletedGenerations(state, trace, failure);
763
+ if (!state.tracker.sawStreamEvents && state.pendingOutput.length > 0) {
764
+ state.generationIndex += 1;
765
+ await this._captureGeneration({
766
+ spanId: (0, uuid.v4)(),
767
+ input: state.tracker.pendingInput,
768
+ startTime: state.turnStart,
769
+ endTime: performance.now(),
770
+ usage: {},
771
+ error: failure
772
+ }, state, trace);
773
+ }
774
+ } catch (error) {
775
+ this._handleError(error);
776
+ }
777
+ try {
778
+ await this._captureTrace(state, trace, void 0, failure);
779
+ } catch (error) {
780
+ this._handleError(error);
781
+ }
782
+ }
783
+ }
784
+ }
785
+ _createState(prompt, options, trace) {
786
+ const initialInput = [];
787
+ const systemPrompt = extractSystemPrompt(options);
788
+ if (systemPrompt) initialInput.push({
789
+ role: "system",
790
+ content: systemPrompt
791
+ });
792
+ const tracker = new GenerationTracker();
793
+ tracker.setPendingInput(initialInput.length > 0 ? initialInput : void 0);
794
+ return {
795
+ tracker,
796
+ systemPrompt,
797
+ pendingCaptures: [],
798
+ pendingTools: /* @__PURE__ */ new Map(),
799
+ traceId: trace.traceId ?? (0, uuid.v4)(),
800
+ turnStart: performance.now(),
801
+ turnCaptured: false,
802
+ turnActive: false,
803
+ hasCapturedTrace: false,
804
+ pendingPrompts: typeof prompt === "string" ? [{
805
+ type: "user",
806
+ message: {
807
+ role: "user",
808
+ content: prompt
809
+ },
810
+ parent_tool_use_id: null
811
+ }] : [],
812
+ generationIndex: 0,
813
+ pendingOutput: [],
814
+ totalCost: 0
815
+ };
816
+ }
817
+ async *_observePrompt(prompt, state) {
818
+ for await (const message of prompt) {
819
+ state.pendingPrompts.push(message);
820
+ yield message;
821
+ }
822
+ }
823
+ _beginTurn(state, userMessageUuid) {
824
+ if (state.turnActive) return;
825
+ state.turnActive = true;
826
+ let end = userMessageUuid ? state.pendingPrompts.findIndex((message) => message.uuid === userMessageUuid) : -1;
827
+ if (end < 0) {
828
+ const candidates = state.pendingPrompts.filter((message) => message.shouldQuery !== false);
829
+ if (candidates.length !== 1 || userMessageUuid && candidates[0].uuid) return;
830
+ end = state.pendingPrompts.indexOf(candidates[0]);
831
+ }
832
+ if (end < 0) return;
833
+ let start = end;
834
+ while (start > 0 && state.pendingPrompts[start - 1].shouldQuery === false) start -= 1;
835
+ for (const message of state.pendingPrompts.splice(start, end - start + 1)) state.tracker.appendPendingInput([{
836
+ role: "user",
837
+ content: formatUserContent(message.message.content, this._client)
838
+ }]);
839
+ }
840
+ async _handleMessage(message, state, trace) {
841
+ if ("session_id" in message && message.session_id) state.sessionId = message.session_id;
842
+ if (message.type === "conversation_reset") state.totalCost = 0;
843
+ else if (message.type === "system" && message.subtype === "api_retry") {
844
+ state.tracker.retryCurrent(Object.assign(new Error(message.error), { status: message.error_status }));
845
+ await this._captureCompletedGenerations(state, trace);
846
+ } else if (message.type === "stream_event") {
847
+ if (message.parent_tool_use_id) return;
848
+ if (message.user_message_uuid) state.userMessageUuid = message.user_message_uuid;
849
+ if (message.event.type === "message_start") this._beginTurn(state, message.user_message_uuid);
850
+ state.tracker.processStreamEvent(message.event, this._client, message.ttft_ms);
851
+ await this._captureCompletedGenerations(state, trace);
852
+ } else if (message.type === "assistant") {
853
+ if (!message.parent_tool_use_id) this._beginTurn(state, message.user_message_uuid);
854
+ this._handleAssistantMessage(message, state, trace);
855
+ } else if (message.type === "user") {
856
+ if ("isReplay" in message && message.isReplay) return;
857
+ if (Array.isArray(message.message.content)) {
858
+ for (const block of message.message.content) if (block.type === "tool_result") await this._finishToolSpan(block.tool_use_id, state, trace, block);
859
+ }
860
+ if (message.parent_tool_use_id) return;
861
+ const content = formatUserContent(message.message?.content, this._client);
862
+ if (typeof content === "string" || Array.isArray(content) && content.length > 0) state.tracker.appendPendingInput([{
863
+ role: "user",
864
+ content
865
+ }]);
866
+ } else if (message.type === "result") try {
867
+ this._beginTurn(state, message.user_message_uuid);
868
+ state.tracker.finishCurrent();
869
+ await this._captureCompletedGenerations(state, trace, resultError(message), message);
870
+ if (message.total_cost_usd != null) {
871
+ state.turnCost = message.total_cost_usd - (message.total_cost_usd >= state.totalCost ? state.totalCost : 0);
872
+ state.totalCost = message.total_cost_usd;
873
+ }
874
+ if (!state.tracker.sawStreamEvents) await this._captureGenerationFromResult(message, state, trace);
875
+ } finally {
876
+ await this._captureTrace(state, trace, message);
877
+ }
878
+ }
879
+ async _captureCompletedGenerations(state, trace, failure, result) {
880
+ let generation = state.tracker.popCompleted();
881
+ while (generation) {
882
+ generation.error ??= failure;
883
+ state.generationIndex += 1;
884
+ state.lastGenerationSpanId = generation.spanId;
885
+ state.turnCaptured = true;
886
+ await this._captureGeneration(generation, state, trace, {}, result);
887
+ state.pendingOutput = [];
888
+ generation = state.tracker.popCompleted();
889
+ }
890
+ }
891
+ _handleAssistantMessage(message, state, trace) {
892
+ if (!message.parent_tool_use_id) state.tracker.setModel(message.message?.model);
893
+ const parentSpanId = message.parent_tool_use_id ?? state.tracker.currentSpanId ?? state.lastGenerationSpanId;
894
+ const blocks = Array.isArray(message.message?.content) ? message.message.content : [];
895
+ for (const block of blocks) if (block?.type === "tool_use") {
896
+ state.turnCaptured = true;
897
+ this._startToolSpan(block, state, trace, parentSpanId);
898
+ }
899
+ if (!message.parent_tool_use_id) state.pendingOutput.push(...formatAssistantBlocks(message.message?.content, this._client));
900
+ }
901
+ async _captureGeneration(generation, state, trace, extraProperties = {}, result) {
902
+ const options = {
903
+ traceId: state.traceId,
904
+ model: generation.model ?? state.tracker.lastModel,
905
+ provider: PROVIDER,
906
+ baseURL: null,
907
+ httpStatus: result?.subtype === "success" ? result.api_error_status ?? void 0 : void 0,
908
+ error: generation.error,
909
+ input: generation.input ?? [],
910
+ output: formatContent(formatOutput([...state.pendingOutput, ...generation.streamedOutput?.slice(state.pendingOutput.length).filter(Boolean) ?? []]), this._client),
911
+ latency: generation.endTime != null ? (generation.endTime - generation.startTime) / 1e3 : void 0,
912
+ timeToFirstToken: generation.timeToFirstToken,
913
+ usage: generation.usage,
914
+ stopReason: generation.stopReason,
915
+ groups: trace.groups,
916
+ privacyMode: trace.privacyMode ?? false,
917
+ captureImmediate: this._captureImmediate,
918
+ onError: this._onError,
919
+ properties: {
920
+ $ai_framework: FRAMEWORK,
921
+ $ai_span_id: generation.spanId,
922
+ $ai_span_name: `generation_${state.generationIndex}`,
923
+ ...state.sessionId ? { $ai_session_id: state.sessionId } : {},
924
+ ...extraProperties,
925
+ ...trace.properties
926
+ }
927
+ };
928
+ await this._withDistinctId(state, trace, (distinctId) => captureAiGeneration(this._client, {
929
+ ...options,
930
+ distinctId
931
+ }));
932
+ }
933
+ /**
934
+ * Without partial messages there is no per-call metric, so the aggregate the
935
+ * result message reports becomes one generation.
936
+ */
937
+ async _captureGenerationFromResult(result, state, trace) {
938
+ state.generationIndex += 1;
939
+ const endTime = performance.now();
940
+ const generation = {
941
+ spanId: (0, uuid.v4)(),
942
+ input: state.tracker.pendingInput ?? [],
943
+ startTime: endTime - (result.duration_api_ms ?? 0),
944
+ endTime,
945
+ usage: readUsage(result.usage),
946
+ stopReason: result.stop_reason ?? void 0,
947
+ error: resultError(result)
948
+ };
949
+ await this._captureGeneration(generation, state, trace, { ...state.turnCost != null ? { $ai_total_cost_usd: state.turnCost } : {} }, result);
950
+ }
951
+ _startToolSpan(block, state, trace, parentSpanId) {
952
+ const properties = {
953
+ $ai_span_id: block.id ?? (0, uuid.v4)(),
954
+ ...parentSpanId ? { $ai_parent_id: parentSpanId } : {},
955
+ $ai_span_name: block.name,
956
+ $ai_span_type: "tool",
957
+ $ai_input_state: withPrivacyMode(this._client, trace.privacyMode ?? false, formatContent(block.input ?? {}, this._client))
958
+ };
959
+ state.pendingTools.set(properties.$ai_span_id, {
960
+ startTime: performance.now(),
961
+ properties
962
+ });
963
+ }
964
+ async _finishToolSpan(id, state, trace, result) {
965
+ const tool = state.pendingTools.get(id);
966
+ if (!tool) return;
967
+ state.pendingTools.delete(id);
968
+ const properties = {
969
+ ...tool.properties,
970
+ $ai_latency: (performance.now() - tool.startTime) / 1e3,
971
+ ...result ? {
972
+ $ai_output_state: withPrivacyMode(this._client, trace.privacyMode ?? false, formatContent(result.content, this._client)),
973
+ $ai_is_error: result.is_error === true
974
+ } : {}
975
+ };
976
+ await this._withDistinctId(state, trace, (distinctId) => this._captureLifecycleEvent("$ai_span", distinctId, state, trace, properties));
977
+ }
978
+ async _withDistinctId(state, trace, capture) {
979
+ if (typeof trace.distinctId === "function") state.pendingCaptures.push(capture);
980
+ else await capture(resolveDistinctId(trace.distinctId));
981
+ }
982
+ async _captureTrace(state, trace, result, failure) {
983
+ try {
984
+ const latency = result?.duration_ms != null ? result.duration_ms / 1e3 : (performance.now() - state.turnStart) / 1e3;
985
+ const isError = failure !== void 0 || result?.is_error === true;
986
+ const error = failure ?? (result && resultError(result));
987
+ for (const id of state.pendingTools.keys()) await this._finishToolSpan(id, state, trace);
988
+ let distinctId;
989
+ try {
990
+ distinctId = resolveDistinctId(trace.distinctId, result);
991
+ } catch (error) {
992
+ this._handleError(error);
993
+ }
994
+ for (const capture of state.pendingCaptures.splice(0)) try {
995
+ await capture(distinctId);
996
+ } catch (error) {
997
+ this._handleError(error);
998
+ }
999
+ await this._captureLifecycleEvent("$ai_trace", distinctId, state, trace, {
1000
+ $ai_trace_name: "claude_agent_sdk_query",
1001
+ $ai_latency: latency,
1002
+ ...state.turnCost != null ? { $ai_total_cost_usd: state.turnCost } : {},
1003
+ ...isError ? { $ai_is_error: true } : {},
1004
+ ...error !== void 0 ? { $ai_error: stringifyError(error) } : {},
1005
+ ...result?.subtype === "success" && result.api_error_status != null ? { $ai_http_status: result.api_error_status } : {}
1006
+ });
1007
+ } finally {
1008
+ state.hasCapturedTrace = true;
1009
+ state.traceId = trace.traceId ?? (0, uuid.v4)();
1010
+ state.turnStart = performance.now();
1011
+ state.turnCaptured = false;
1012
+ state.turnActive = false;
1013
+ state.userMessageUuid = void 0;
1014
+ state.failure = void 0;
1015
+ state.generationIndex = 0;
1016
+ state.lastGenerationSpanId = void 0;
1017
+ state.turnCost = void 0;
1018
+ state.pendingCaptures = [];
1019
+ state.pendingTools.clear();
1020
+ if (result?.queued_turn_count === 0) state.pendingPrompts = state.pendingPrompts.filter((message) => message.shouldQuery === false);
1021
+ state.tracker = new GenerationTracker();
1022
+ if (state.systemPrompt) state.tracker.setPendingInput([{
1023
+ role: "system",
1024
+ content: state.systemPrompt
1025
+ }]);
1026
+ state.pendingOutput = [];
1027
+ }
1028
+ }
1029
+ async _captureLifecycleEvent(event, distinctId, state, trace, properties) {
1030
+ const message = {
1031
+ distinctId: distinctId ?? state.traceId,
1032
+ event,
1033
+ properties: {
1034
+ $ai_lib: "posthog-ai",
1035
+ $ai_lib_version: version,
1036
+ $ai_framework: FRAMEWORK,
1037
+ $ai_provider: PROVIDER,
1038
+ $ai_trace_id: state.traceId,
1039
+ ...state.sessionId ? { $ai_session_id: state.sessionId } : {},
1040
+ ...properties,
1041
+ ...trace.properties,
1042
+ ...distinctId ? {} : { $process_person_profile: false }
1043
+ },
1044
+ groups: trace.groups
1045
+ };
1046
+ try {
1047
+ if (this._captureImmediate) await captureAiEventImmediate(this._client, message);
1048
+ else captureAiEvent(this._client, message);
1049
+ } catch (error) {
1050
+ this._handleError(error);
1051
+ }
1052
+ }
1053
+ _handleError(error) {
1054
+ try {
1055
+ this._onError?.(error);
1056
+ } catch {}
1057
+ }
1058
+ };
1059
+ function formatOutput(content) {
1060
+ return content.length > 0 ? [{
1061
+ role: "assistant",
1062
+ content
1063
+ }] : [];
1064
+ }
1065
+ function resultError(result) {
1066
+ if (!result.is_error) return;
1067
+ return (result.subtype === "success" ? result.result : result.errors.join("\n")) || result.subtype;
1068
+ }
1069
+ function readUsage(usage) {
1070
+ return {
1071
+ ...usage && Object.keys(usage).length > 0 ? { rawUsage: { ...usage } } : {},
1072
+ ...usage?.input_tokens != null ? { inputTokens: usage.input_tokens } : {},
1073
+ ...usage?.output_tokens != null ? { outputTokens: usage.output_tokens } : {},
1074
+ ...usage?.cache_read_input_tokens != null ? { cacheReadInputTokens: usage.cache_read_input_tokens } : {},
1075
+ ...usage?.cache_creation_input_tokens != null ? { cacheCreationInputTokens: usage.cache_creation_input_tokens } : {},
1076
+ ...usage?.server_tool_use?.web_search_requests != null ? { webSearchCount: usage.server_tool_use.web_search_requests } : {}
1077
+ };
1078
+ }
1079
+ function resolveDistinctId(resolver, result) {
1080
+ if (typeof resolver === "function") {
1081
+ if (!result) return;
1082
+ const resolved = resolver(result);
1083
+ return resolved ? String(resolved) : void 0;
1084
+ }
1085
+ return resolver ? String(resolver) : void 0;
1086
+ }
1087
+ //#endregion
1088
+ //#region src/claude-agent-sdk/index.ts
1089
+ /**
1090
+ * Create a PostHog-instrumented `query()` for the Claude Agent SDK.
1091
+ *
1092
+ * @param options - Configuration options
1093
+ * @returns A processor whose `query()` method replaces `query()` from the SDK
1094
+ *
1095
+ * @example
1096
+ * ```typescript
1097
+ * import { instrument } from '@posthog/ai/claude-agent-sdk'
1098
+ * import { PostHog } from 'posthog-node'
1099
+ *
1100
+ * const phClient = new PostHog('<POSTHOG_API_KEY>')
1101
+ * const claude = instrument({ client: phClient, distinctId: 'user@example.com' })
1102
+ *
1103
+ * for await (const message of claude.query({ prompt: 'Explain this repo' })) {
1104
+ * console.log(message)
1105
+ * }
1106
+ * ```
1107
+ */
1108
+ function instrument(options) {
1109
+ return new PostHogClaudeAgentProcessor(options);
1110
+ }
1111
+ /**
1112
+ * Drop-in replacement for `query()` from the Claude Agent SDK, for a single
1113
+ * query. Use {@link instrument} to share one configuration across queries.
1114
+ *
1115
+ * @example
1116
+ * ```typescript
1117
+ * import { query } from '@posthog/ai/claude-agent-sdk'
1118
+ * import { PostHog } from 'posthog-node'
1119
+ *
1120
+ * const phClient = new PostHog('<POSTHOG_API_KEY>')
1121
+ *
1122
+ * for await (const message of query({
1123
+ * prompt: 'Explain this repo',
1124
+ * posthog: { client: phClient, distinctId: 'user@example.com' },
1125
+ * })) {
1126
+ * console.log(message)
1127
+ * }
1128
+ * ```
1129
+ */
1130
+ function query(params) {
1131
+ const { prompt, options, posthog } = params;
1132
+ return new PostHogClaudeAgentProcessor(posthog).query({
1133
+ prompt,
1134
+ options
1135
+ });
1136
+ }
1137
+ //#endregion
1138
+ exports.PostHogClaudeAgentProcessor = PostHogClaudeAgentProcessor;
1139
+ exports.instrument = instrument;
1140
+ exports.query = query;
1141
+
1142
+ //# sourceMappingURL=index.cjs.map