@posthog/ai 7.16.0 → 7.16.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.
@@ -23,7 +23,7 @@ function _interopNamespace(e) {
23
23
 
24
24
  var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
25
25
 
26
- var version = "7.16.0";
26
+ var version = "7.16.2";
27
27
 
28
28
  // Type guards for safer type checking
29
29
 
@@ -1,7 +1,7 @@
1
1
  import * as uuid from 'uuid';
2
2
  import '@posthog/core';
3
3
 
4
- var version = "7.16.0";
4
+ var version = "7.16.2";
5
5
 
6
6
  // Type guards for safer type checking
7
7
 
@@ -6,7 +6,7 @@ var openai = require('openai');
6
6
  var uuid = require('uuid');
7
7
  var core = require('@posthog/core');
8
8
 
9
- var version = "7.16.0";
9
+ var version = "7.16.2";
10
10
 
11
11
  // Type guards for safer type checking
12
12
 
@@ -2,7 +2,7 @@ import { OpenAI } from 'openai';
2
2
  import { v4 } from 'uuid';
3
3
  import { uuidv7 } from '@posthog/core';
4
4
 
5
- var version = "7.16.0";
5
+ var version = "7.16.2";
6
6
 
7
7
  // Type guards for safer type checking
8
8
 
@@ -3,7 +3,7 @@
3
3
  require('uuid');
4
4
  require('@posthog/core');
5
5
 
6
- var version = "7.16.0";
6
+ var version = "7.16.2";
7
7
 
8
8
  // limit large outputs by truncating to 200kb (approx 200k bytes)
9
9
  const MAX_OUTPUT_SIZE = 200000;
@@ -1,7 +1,7 @@
1
1
  import 'uuid';
2
2
  import '@posthog/core';
3
3
 
4
- var version = "7.16.0";
4
+ var version = "7.16.2";
5
5
 
6
6
  // limit large outputs by truncating to 200kb (approx 200k bytes)
7
7
  const MAX_OUTPUT_SIZE = 200000;
@@ -21,6 +21,14 @@ function isAISpan(span) {
21
21
  return false;
22
22
  }
23
23
 
24
+ const DEFAULT_OTEL_HOST$1 = 'https://us.i.posthog.com';
25
+ function normalizeApiKey$1(value) {
26
+ return typeof value === 'string' ? value.trim() : '';
27
+ }
28
+ function normalizeHost$1(value) {
29
+ const normalizedValue = typeof value === 'string' ? value.trim() : '';
30
+ return normalizedValue || DEFAULT_OTEL_HOST$1;
31
+ }
24
32
  /**
25
33
  * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP
26
34
  * ingestion endpoint. PostHog converts `gen_ai.*` spans into
@@ -48,14 +56,15 @@ function isAISpan(span) {
48
56
  */
49
57
  class PostHogTraceExporter extends exporterTraceOtlpHttp.OTLPTraceExporter {
50
58
  constructor(options) {
51
- if (!options.apiKey) {
59
+ const apiKey = normalizeApiKey$1(options.apiKey);
60
+ if (!apiKey) {
52
61
  throw new Error('PostHogTraceExporter requires an apiKey');
53
62
  }
54
- const host = new URL(options.host || 'https://us.i.posthog.com').origin;
63
+ const host = new URL(normalizeHost$1(options.host)).origin;
55
64
  super({
56
65
  url: `${host}/i/v0/ai/otel`,
57
66
  headers: {
58
- Authorization: `Bearer ${options.apiKey}`
67
+ Authorization: `Bearer ${apiKey}`
59
68
  }
60
69
  });
61
70
  }
@@ -71,6 +80,14 @@ class PostHogTraceExporter extends exporterTraceOtlpHttp.OTLPTraceExporter {
71
80
  }
72
81
  }
73
82
 
83
+ const DEFAULT_OTEL_HOST = 'https://us.i.posthog.com';
84
+ function normalizeApiKey(value) {
85
+ return typeof value === 'string' ? value.trim() : '';
86
+ }
87
+ function normalizeHost(value) {
88
+ const normalizedValue = typeof value === 'string' ? value.trim() : '';
89
+ return normalizedValue || DEFAULT_OTEL_HOST;
90
+ }
74
91
  /**
75
92
  * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
76
93
  *
@@ -96,17 +113,18 @@ class PostHogTraceExporter extends exporterTraceOtlpHttp.OTLPTraceExporter {
96
113
  */
97
114
  class PostHogSpanProcessor {
98
115
  constructor(options) {
99
- if (!options.apiKey) {
116
+ const apiKey = normalizeApiKey(options.apiKey);
117
+ if (!apiKey) {
100
118
  throw new Error('PostHogSpanProcessor requires an apiKey');
101
119
  }
102
120
  if (options._spanProcessor) {
103
121
  this.inner = options._spanProcessor;
104
122
  } else {
105
- const host = new URL(options.host || 'https://us.i.posthog.com').origin;
123
+ const host = new URL(normalizeHost(options.host)).origin;
106
124
  const exporter = new exporterTraceOtlpHttp.OTLPTraceExporter({
107
125
  url: `${host}/i/v0/ai/otel`,
108
126
  headers: {
109
- Authorization: `Bearer ${options.apiKey}`
127
+ Authorization: `Bearer ${apiKey}`
110
128
  }
111
129
  });
112
130
  this.inner = new sdkTraceBase.BatchSpanProcessor(exporter);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/otel/spans.ts","../../src/otel/exporter.ts","../../src/otel/processor.ts"],"sourcesContent":["import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { isAISpan } from './spans'\n\nexport interface PostHogTraceExporterOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ apiKey: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n constructor(options: PostHogTraceExporterOptions) {\n if (!options.apiKey) {\n throw new Error('PostHogTraceExporter requires an apiKey')\n }\n const host = new URL(options.host || 'https://us.i.posthog.com').origin\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${options.apiKey}`,\n },\n })\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans, resultCallback)\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport { BatchSpanProcessor, type SpanProcessor, type ReadableSpan, type Span } from '@opentelemetry/sdk-trace-base'\n\nimport { isAISpan } from './spans'\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions) {\n if (!options.apiKey) {\n throw new Error('PostHogSpanProcessor requires an apiKey')\n }\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(options.host || 'https://us.i.posthog.com').origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${options.apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n if (isAISpan(span)) {\n this.inner.onEnd(span)\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"names":["AI_SPAN_PREFIXES","isAISpan","span","some","prefix","name","startsWith","attributes","Object","keys","key","PostHogTraceExporter","OTLPTraceExporter","constructor","options","apiKey","Error","host","URL","origin","url","headers","Authorization","export","spans","resultCallback","aiSpans","filter","length","code","ExportResultCode","SUCCESS","PostHogSpanProcessor","_spanProcessor","inner","exporter","BatchSpanProcessor","onStart","parentContext","onEnd","shutdown","forceFlush"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/otel/spans.ts","../../src/otel/exporter.ts","../../src/otel/processor.ts"],"sourcesContent":["import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogTraceExporterOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ apiKey: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n constructor(options: PostHogTraceExporterOptions) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n throw new Error('PostHogTraceExporter requires an apiKey')\n }\n const host = new URL(normalizeHost(options.host)).origin\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n })\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans, resultCallback)\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport { BatchSpanProcessor, type SpanProcessor, type ReadableSpan, type Span } from '@opentelemetry/sdk-trace-base'\n\nimport { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n throw new Error('PostHogSpanProcessor requires an apiKey')\n }\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(normalizeHost(options.host)).origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n if (isAISpan(span)) {\n this.inner.onEnd(span)\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"names":["AI_SPAN_PREFIXES","isAISpan","span","some","prefix","name","startsWith","attributes","Object","keys","key","DEFAULT_OTEL_HOST","normalizeApiKey","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","apiKey","Error","host","URL","origin","url","headers","Authorization","export","spans","resultCallback","aiSpans","filter","length","code","ExportResultCode","SUCCESS","PostHogSpanProcessor","_spanProcessor","inner","exporter","BatchSpanProcessor","onStart","parentContext","onEnd","shutdown","forceFlush"],"mappings":"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"}
@@ -19,6 +19,14 @@ function isAISpan(span) {
19
19
  return false;
20
20
  }
21
21
 
22
+ const DEFAULT_OTEL_HOST$1 = 'https://us.i.posthog.com';
23
+ function normalizeApiKey$1(value) {
24
+ return typeof value === 'string' ? value.trim() : '';
25
+ }
26
+ function normalizeHost$1(value) {
27
+ const normalizedValue = typeof value === 'string' ? value.trim() : '';
28
+ return normalizedValue || DEFAULT_OTEL_HOST$1;
29
+ }
22
30
  /**
23
31
  * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP
24
32
  * ingestion endpoint. PostHog converts `gen_ai.*` spans into
@@ -46,14 +54,15 @@ function isAISpan(span) {
46
54
  */
47
55
  class PostHogTraceExporter extends OTLPTraceExporter {
48
56
  constructor(options) {
49
- if (!options.apiKey) {
57
+ const apiKey = normalizeApiKey$1(options.apiKey);
58
+ if (!apiKey) {
50
59
  throw new Error('PostHogTraceExporter requires an apiKey');
51
60
  }
52
- const host = new URL(options.host || 'https://us.i.posthog.com').origin;
61
+ const host = new URL(normalizeHost$1(options.host)).origin;
53
62
  super({
54
63
  url: `${host}/i/v0/ai/otel`,
55
64
  headers: {
56
- Authorization: `Bearer ${options.apiKey}`
65
+ Authorization: `Bearer ${apiKey}`
57
66
  }
58
67
  });
59
68
  }
@@ -69,6 +78,14 @@ class PostHogTraceExporter extends OTLPTraceExporter {
69
78
  }
70
79
  }
71
80
 
81
+ const DEFAULT_OTEL_HOST = 'https://us.i.posthog.com';
82
+ function normalizeApiKey(value) {
83
+ return typeof value === 'string' ? value.trim() : '';
84
+ }
85
+ function normalizeHost(value) {
86
+ const normalizedValue = typeof value === 'string' ? value.trim() : '';
87
+ return normalizedValue || DEFAULT_OTEL_HOST;
88
+ }
72
89
  /**
73
90
  * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
74
91
  *
@@ -94,17 +111,18 @@ class PostHogTraceExporter extends OTLPTraceExporter {
94
111
  */
95
112
  class PostHogSpanProcessor {
96
113
  constructor(options) {
97
- if (!options.apiKey) {
114
+ const apiKey = normalizeApiKey(options.apiKey);
115
+ if (!apiKey) {
98
116
  throw new Error('PostHogSpanProcessor requires an apiKey');
99
117
  }
100
118
  if (options._spanProcessor) {
101
119
  this.inner = options._spanProcessor;
102
120
  } else {
103
- const host = new URL(options.host || 'https://us.i.posthog.com').origin;
121
+ const host = new URL(normalizeHost(options.host)).origin;
104
122
  const exporter = new OTLPTraceExporter({
105
123
  url: `${host}/i/v0/ai/otel`,
106
124
  headers: {
107
- Authorization: `Bearer ${options.apiKey}`
125
+ Authorization: `Bearer ${apiKey}`
108
126
  }
109
127
  });
110
128
  this.inner = new BatchSpanProcessor(exporter);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/otel/spans.ts","../../src/otel/exporter.ts","../../src/otel/processor.ts"],"sourcesContent":["import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { isAISpan } from './spans'\n\nexport interface PostHogTraceExporterOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ apiKey: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n constructor(options: PostHogTraceExporterOptions) {\n if (!options.apiKey) {\n throw new Error('PostHogTraceExporter requires an apiKey')\n }\n const host = new URL(options.host || 'https://us.i.posthog.com').origin\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${options.apiKey}`,\n },\n })\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans, resultCallback)\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport { BatchSpanProcessor, type SpanProcessor, type ReadableSpan, type Span } from '@opentelemetry/sdk-trace-base'\n\nimport { isAISpan } from './spans'\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions) {\n if (!options.apiKey) {\n throw new Error('PostHogSpanProcessor requires an apiKey')\n }\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(options.host || 'https://us.i.posthog.com').origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${options.apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n if (isAISpan(span)) {\n this.inner.onEnd(span)\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"names":["AI_SPAN_PREFIXES","isAISpan","span","some","prefix","name","startsWith","attributes","Object","keys","key","PostHogTraceExporter","OTLPTraceExporter","constructor","options","apiKey","Error","host","URL","origin","url","headers","Authorization","export","spans","resultCallback","aiSpans","filter","length","code","ExportResultCode","SUCCESS","PostHogSpanProcessor","_spanProcessor","inner","exporter","BatchSpanProcessor","onStart","parentContext","onEnd","shutdown","forceFlush"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../src/otel/spans.ts","../../src/otel/exporter.ts","../../src/otel/processor.ts"],"sourcesContent":["import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogTraceExporterOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ apiKey: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n constructor(options: PostHogTraceExporterOptions) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n throw new Error('PostHogTraceExporter requires an apiKey')\n }\n const host = new URL(normalizeHost(options.host)).origin\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n })\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans, resultCallback)\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport { BatchSpanProcessor, type SpanProcessor, type ReadableSpan, type Span } from '@opentelemetry/sdk-trace-base'\n\nimport { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project API key.\n */\n apiKey: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n throw new Error('PostHogSpanProcessor requires an apiKey')\n }\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(normalizeHost(options.host)).origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n if (isAISpan(span)) {\n this.inner.onEnd(span)\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"names":["AI_SPAN_PREFIXES","isAISpan","span","some","prefix","name","startsWith","attributes","Object","keys","key","DEFAULT_OTEL_HOST","normalizeApiKey","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","apiKey","Error","host","URL","origin","url","headers","Authorization","export","spans","resultCallback","aiSpans","filter","length","code","ExportResultCode","SUCCESS","PostHogSpanProcessor","_spanProcessor","inner","exporter","BatchSpanProcessor","onStart","parentContext","onEnd","shutdown","forceFlush"],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  var uuid = require('uuid');
4
4
  var core = require('@posthog/core');
5
5
 
6
- var version = "7.16.0";
6
+ var version = "7.16.2";
7
7
 
8
8
  // Type guards for safer type checking
9
9
 
@@ -566,12 +566,14 @@ const mapVercelOutput = result => {
566
566
  };
567
567
  }
568
568
  if (item.type === 'tool-call') {
569
+ const toolCall = item;
570
+ const rawArgs = toolCall.input ?? toolCall.args ?? toolCall.arguments ?? {};
569
571
  return {
570
572
  type: 'tool-call',
571
573
  id: item.toolCallId,
572
574
  function: {
573
575
  name: item.toolName,
574
- arguments: item.args || JSON.stringify(item.arguments || {})
576
+ arguments: typeof rawArgs === 'string' ? rawArgs : JSON.stringify(rawArgs)
575
577
  }
576
578
  };
577
579
  }