@posthog/ai 7.20.2 → 7.20.3
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/README.md +1 -1
- package/dist/anthropic/index.cjs +1 -1
- package/dist/anthropic/index.mjs +1 -1
- package/dist/gemini/index.cjs +1 -1
- package/dist/gemini/index.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/langchain/index.cjs +1 -1
- package/dist/langchain/index.mjs +1 -1
- package/dist/openai/index.cjs +1 -1
- package/dist/openai/index.mjs +1 -1
- package/dist/openai-agents/index.cjs +1 -1
- package/dist/openai-agents/index.mjs +1 -1
- package/dist/otel/index.cjs +43 -14
- package/dist/otel/index.cjs.map +1 -1
- package/dist/otel/index.d.ts +12 -9
- package/dist/otel/index.mjs +43 -14
- package/dist/otel/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +1 -1
- package/dist/vercel/index.mjs +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ await phClient.shutdown()
|
|
|
72
72
|
|
|
73
73
|
## OpenTelemetry
|
|
74
74
|
|
|
75
|
-
`@posthog/ai/otel` provides two ways to send AI traces to PostHog via OpenTelemetry. Both automatically filter to AI-related spans only (`gen_ai.*`, `llm.*`, `ai.*`, `traceloop.*`) and PostHog converts them into `$ai_generation` events server-side. This works with any LLM provider SDK that supports OpenTelemetry.
|
|
75
|
+
`@posthog/ai/otel` provides two ways to send AI traces to PostHog via OpenTelemetry. Both automatically filter to AI-related spans only (`gen_ai.*`, `llm.*`, `ai.*`, `traceloop.*`) and PostHog converts them into `$ai_generation` events server-side. Missing or blank project tokens disable the OpenTelemetry integration. This works with any LLM provider SDK that supports OpenTelemetry.
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
npm install @posthog/ai @opentelemetry/sdk-node @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http
|
package/dist/anthropic/index.cjs
CHANGED
package/dist/anthropic/index.mjs
CHANGED
package/dist/gemini/index.cjs
CHANGED
package/dist/gemini/index.mjs
CHANGED
package/dist/index.cjs
CHANGED
package/dist/index.mjs
CHANGED
package/dist/langchain/index.cjs
CHANGED
package/dist/langchain/index.mjs
CHANGED
package/dist/openai/index.cjs
CHANGED
package/dist/openai/index.mjs
CHANGED
package/dist/otel/index.cjs
CHANGED
|
@@ -31,8 +31,8 @@ function normalizeHost$1(value) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Options for the PostHogTraceExporter.
|
|
35
|
-
* optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.
|
|
34
|
+
* Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank
|
|
35
|
+
* tokens disable the exporter. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.
|
|
36
36
|
*
|
|
37
37
|
* @example
|
|
38
38
|
* ```ts
|
|
@@ -63,8 +63,8 @@ function normalizeHost$1(value) {
|
|
|
63
63
|
* plug PostHog into an existing processor chain. Otherwise prefer
|
|
64
64
|
* {@link PostHogSpanProcessor}, which is self-contained.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
67
|
-
* optionally override the `host` URL.
|
|
66
|
+
* Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.
|
|
67
|
+
* You can also optionally override the `host` URL.
|
|
68
68
|
*
|
|
69
69
|
* @example
|
|
70
70
|
* ```ts
|
|
@@ -78,20 +78,30 @@ function normalizeHost$1(value) {
|
|
|
78
78
|
* ```
|
|
79
79
|
*/
|
|
80
80
|
class PostHogTraceExporter extends exporterTraceOtlpHttp.OTLPTraceExporter {
|
|
81
|
-
constructor(options) {
|
|
82
|
-
const token =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
const host = new URL(normalizeHost$1(options.host)).origin;
|
|
81
|
+
constructor(options = {}) {
|
|
82
|
+
const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey);
|
|
83
|
+
const disabled = !token;
|
|
84
|
+
const host = token ? new URL(normalizeHost$1(options.host)).origin : DEFAULT_OTEL_HOST$1;
|
|
87
85
|
super({
|
|
88
86
|
url: `${host}/i/v0/ai/otel`,
|
|
89
|
-
headers: {
|
|
87
|
+
headers: token ? {
|
|
90
88
|
Authorization: `Bearer ${token}`
|
|
91
|
-
}
|
|
89
|
+
} : {}
|
|
92
90
|
});
|
|
91
|
+
this.disabled = disabled;
|
|
92
|
+
if (this.disabled) {
|
|
93
|
+
console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.');
|
|
94
|
+
}
|
|
93
95
|
}
|
|
94
96
|
export(spans, resultCallback) {
|
|
97
|
+
if (this.disabled) {
|
|
98
|
+
// Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.
|
|
99
|
+
// Reporting failure would make OpenTelemetry treat every span as an export error.
|
|
100
|
+
resultCallback({
|
|
101
|
+
code: core.ExportResultCode.SUCCESS
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
95
105
|
const aiSpans = spans.filter(isAISpan);
|
|
96
106
|
if (aiSpans.length === 0) {
|
|
97
107
|
resultCallback({
|
|
@@ -111,9 +121,26 @@ function normalizeHost(value) {
|
|
|
111
121
|
const normalizedValue = typeof value === 'string' ? value.trim() : '';
|
|
112
122
|
return normalizedValue || DEFAULT_OTEL_HOST;
|
|
113
123
|
}
|
|
124
|
+
class NoopSpanProcessor {
|
|
125
|
+
onStart(_span, _parentContext) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
onEnd(_span) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
shutdown() {
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
}
|
|
134
|
+
forceFlush() {
|
|
135
|
+
return Promise.resolve();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
114
139
|
/**
|
|
115
140
|
* An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
|
|
116
141
|
*
|
|
142
|
+
* Missing or blank project API keys disable the processor.
|
|
143
|
+
*
|
|
117
144
|
* Internally batches spans and exports them to PostHog's OTLP ingestion
|
|
118
145
|
* endpoint. Only AI-related spans (those whose name or attribute keys
|
|
119
146
|
* start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;
|
|
@@ -135,10 +162,12 @@ function normalizeHost(value) {
|
|
|
135
162
|
* ```
|
|
136
163
|
*/
|
|
137
164
|
class PostHogSpanProcessor {
|
|
138
|
-
constructor(options) {
|
|
165
|
+
constructor(options = {}) {
|
|
139
166
|
const apiKey = normalizeApiKey(options.apiKey);
|
|
140
167
|
if (!apiKey) {
|
|
141
|
-
|
|
168
|
+
console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.');
|
|
169
|
+
this.inner = new NoopSpanProcessor();
|
|
170
|
+
return;
|
|
142
171
|
}
|
|
143
172
|
if (options._spanProcessor) {
|
|
144
173
|
this.inner = options._spanProcessor;
|
package/dist/otel/index.cjs.map
CHANGED
|
@@ -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\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeToken(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\n/**\n * Options for the PostHogTraceExporter. You must obligatorily provide `projectToken`. You can also\n * optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions =\n | { projectToken: string; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey: string\n projectToken?: never\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 * You must obligatorily provide `projectToken`. You can also\n * optionally override the `host` URL.\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({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n constructor(options: PostHogTraceExporterOptions) {\n const token = 'projectToken' in options ? normalizeToken(options.projectToken) : normalizeToken(options.apiKey)\n if (!token) {\n throw new Error('PostHogTraceExporter requires a projectToken')\n }\n\n const host = new URL(normalizeHost(options.host)).origin\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${token}`,\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","normalizeToken","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","token","projectToken","apiKey","Error","host","URL","origin","url","headers","Authorization","export","spans","resultCallback","aiSpans","filter","length","code","ExportResultCode","SUCCESS","normalizeApiKey","PostHogSpanProcessor","_spanProcessor","inner","exporter","BatchSpanProcessor","onStart","parentContext","onEnd","shutdown","forceFlush"],"mappings":";;;;;;AAEA,MAAMA,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAU;;AAE1E;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAACC,IAAkB,EAAW;AACpD,EAAA,IAAIF,gBAAgB,CAACG,IAAI,CAAEC,MAAM,IAAKF,IAAI,CAACG,IAAI,CAACC,UAAU,CAACF,MAAM,CAAC,CAAC,EAAE;AACnE,IAAA,OAAO,IAAI;AACb,EAAA;AACA,EAAA,MAAMG,UAAU,GAAGL,IAAI,CAACK,UAAU;AAClC,EAAA,IAAIA,UAAU,EAAE;IACd,OAAOC,MAAM,CAACC,IAAI,CAACF,UAAU,CAAC,CAACJ,IAAI,CAAEO,GAAG,IAAKV,gBAAgB,CAACG,IAAI,CAAEC,MAAM,IAAKM,GAAG,CAACJ,UAAU,CAACF,MAAM,CAAC,CAAC,CAAC;AACzG,EAAA;AACA,EAAA,OAAO,KAAK;AACd;;ACXA,MAAMO,mBAAiB,GAAG,0BAA0B;AAEpD,SAASC,cAAcA,CAACC,KAAe,EAAU;EAC/C,OAAO,OAAOA,KAAK,KAAK,QAAQ,GAAGA,KAAK,CAACC,IAAI,EAAE,GAAG,EAAE;AACtD;AAEA,SAASC,eAAaA,CAACF,KAAe,EAAU;AAC9C,EAAA,MAAMG,eAAe,GAAG,OAAOH,KAAK,KAAK,QAAQ,GAAGA,KAAK,CAACC,IAAI,EAAE,GAAG,EAAE;EACrE,OAAOE,eAAe,IAAIL,mBAAiB;AAC7C;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMM,oBAAoB,SAASC,uCAAiB,CAAC;EAC1DC,WAAWA,CAACC,OAAoC,EAAE;AAChD,IAAA,MAAMC,KAAK,GAAG,cAAc,IAAID,OAAO,GAAGR,cAAc,CAACQ,OAAO,CAACE,YAAY,CAAC,GAAGV,cAAc,CAACQ,OAAO,CAACG,MAAM,CAAC;IAC/G,IAAI,CAACF,KAAK,EAAE;AACV,MAAA,MAAM,IAAIG,KAAK,CAAC,8CAA8C,CAAC;AACjE,IAAA;AAEA,IAAA,MAAMC,IAAI,GAAG,IAAIC,GAAG,CAACX,eAAa,CAACK,OAAO,CAACK,IAAI,CAAC,CAAC,CAACE,MAAM;AACxD,IAAA,KAAK,CAAC;MACJC,GAAG,EAAE,CAAA,EAAGH,IAAI,CAAA,aAAA,CAAe;AAC3BI,MAAAA,OAAO,EAAE;QACPC,aAAa,EAAE,UAAUT,KAAK,CAAA;AAChC;AACF,KAAC,CAAC;AACJ,EAAA;AAESU,EAAAA,MAAMA,CAACC,KAAqB,EAAEC,cAAiE,EAAQ;AAC9G,IAAA,MAAMC,OAAO,GAAGF,KAAK,CAACG,MAAM,CAAClC,QAAQ,CAAC;AACtC,IAAA,IAAIiC,OAAO,CAACE,MAAM,KAAK,CAAC,EAAE;AACxBH,MAAAA,cAAc,CAAC;QAAEI,IAAI,EAAEC,qBAAgB,CAACC;AAAQ,OAAC,CAAC;AAClD,MAAA;AACF,IAAA;AACA,IAAA,KAAK,CAACR,MAAM,CAACG,OAAO,EAAED,cAAc,CAAC;AACvC,EAAA;AACF;;AC1FA,MAAMtB,iBAAiB,GAAG,0BAA0B;AAEpD,SAAS6B,eAAeA,CAAC3B,KAAe,EAAU;EAChD,OAAO,OAAOA,KAAK,KAAK,QAAQ,GAAGA,KAAK,CAACC,IAAI,EAAE,GAAG,EAAE;AACtD;AAEA,SAASC,aAAaA,CAACF,KAAe,EAAU;AAC9C,EAAA,MAAMG,eAAe,GAAG,OAAOH,KAAK,KAAK,QAAQ,GAAGA,KAAK,CAACC,IAAI,EAAE,GAAG,EAAE;EACrE,OAAOE,eAAe,IAAIL,iBAAiB;AAC7C;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAM8B,oBAAoB,CAA0B;EAGzDtB,WAAWA,CAACC,OAAoC,EAAE;AAChD,IAAA,MAAMG,MAAM,GAAGiB,eAAe,CAACpB,OAAO,CAACG,MAAM,CAAC;IAC9C,IAAI,CAACA,MAAM,EAAE;AACX,MAAA,MAAM,IAAIC,KAAK,CAAC,yCAAyC,CAAC;AAC5D,IAAA;IAEA,IAAIJ,OAAO,CAACsB,cAAc,EAAE;AAC1B,MAAA,IAAI,CAACC,KAAK,GAAGvB,OAAO,CAACsB,cAAc;AACrC,IAAA,CAAC,MAAM;AACL,MAAA,MAAMjB,IAAI,GAAG,IAAIC,GAAG,CAACX,aAAa,CAACK,OAAO,CAACK,IAAI,CAAC,CAAC,CAACE,MAAM;AACxD,MAAA,MAAMiB,QAAQ,GAAG,IAAI1B,uCAAiB,CAAC;QACrCU,GAAG,EAAE,CAAA,EAAGH,IAAI,CAAA,aAAA,CAAe;AAC3BI,QAAAA,OAAO,EAAE;UACPC,aAAa,EAAE,UAAUP,MAAM,CAAA;AACjC;AACF,OAAC,CAAC;AACF,MAAA,IAAI,CAACoB,KAAK,GAAG,IAAIE,+BAAkB,CAACD,QAAQ,CAAC;AAC/C,IAAA;AACF,EAAA;AAEAE,EAAAA,OAAOA,CAAC5C,IAAU,EAAE6C,aAAsB,EAAQ;AAChD;AACA;AACA;IACA,IAAI,CAACJ,KAAK,CAACG,OAAO,CAAC5C,IAAI,EAAE6C,aAAa,CAAC;AACzC,EAAA;EAEAC,KAAKA,CAAC9C,IAAkB,EAAQ;AAC9B,IAAA,IAAID,QAAQ,CAACC,IAAI,CAAC,EAAE;AAClB,MAAA,IAAI,CAACyC,KAAK,CAACK,KAAK,CAAC9C,IAAI,CAAC;AACxB,IAAA;AACF,EAAA;AAEA+C,EAAAA,QAAQA,GAAkB;AACxB,IAAA,OAAO,IAAI,CAACN,KAAK,CAACM,QAAQ,EAAE;AAC9B,EAAA;AAEAC,EAAAA,UAAUA,GAAkB;AAC1B,IAAA,OAAO,IAAI,CAACP,KAAK,CAACO,UAAU,EAAE;AAChC,EAAA;AACF;;;;;"}
|
|
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 normalizeToken(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\n/**\n * Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank\n * tokens disable the exporter. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions =\n | { projectToken?: string; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey?: string\n projectToken?: never\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 * Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.\n * You can also optionally override the `host` URL.\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({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n private readonly disabled: boolean\n\n constructor(options: PostHogTraceExporterOptions = {}) {\n const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey)\n const disabled = !token\n const host = token ? new URL(normalizeHost(options.host)).origin : DEFAULT_OTEL_HOST\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : {},\n })\n\n this.disabled = disabled\n if (this.disabled) {\n console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.')\n }\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n if (this.disabled) {\n // Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.\n // Reporting failure would make OpenTelemetry treat every span as an export error.\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n\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\nclass NoopSpanProcessor implements SpanProcessor {\n onStart(_span: Span, _parentContext: Context): void {\n return\n }\n onEnd(_span: ReadableSpan): void {\n return\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * Missing or blank project API keys disable the processor.\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 console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\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","normalizeToken","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","token","projectToken","apiKey","disabled","host","URL","origin","url","headers","Authorization","console","warn","export","spans","resultCallback","code","ExportResultCode","SUCCESS","aiSpans","filter","length","normalizeApiKey","NoopSpanProcessor","onStart","_span","_parentContext","onEnd","shutdown","Promise","resolve","forceFlush","PostHogSpanProcessor","inner","_spanProcessor","exporter","BatchSpanProcessor","parentContext"],"mappings":"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"}
|
package/dist/otel/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { ReadableSpan, SpanProcessor, Span } from '@opentelemetry/sdk-trace-base
|
|
|
3
3
|
import { Context } from '@opentelemetry/api';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Options for the PostHogTraceExporter.
|
|
7
|
-
* optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.
|
|
6
|
+
* Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank
|
|
7
|
+
* tokens disable the exporter. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```ts
|
|
@@ -21,12 +21,12 @@ import { Context } from '@opentelemetry/api';
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
type PostHogTraceExporterOptions = {
|
|
24
|
-
projectToken
|
|
24
|
+
projectToken?: string;
|
|
25
25
|
apiKey?: never;
|
|
26
26
|
host?: string;
|
|
27
27
|
} | {
|
|
28
28
|
/** @deprecated Use `projectToken` instead */
|
|
29
|
-
apiKey
|
|
29
|
+
apiKey?: string;
|
|
30
30
|
projectToken?: never;
|
|
31
31
|
host?: string;
|
|
32
32
|
};
|
|
@@ -44,8 +44,8 @@ type PostHogTraceExporterOptions = {
|
|
|
44
44
|
* plug PostHog into an existing processor chain. Otherwise prefer
|
|
45
45
|
* {@link PostHogSpanProcessor}, which is self-contained.
|
|
46
46
|
*
|
|
47
|
-
*
|
|
48
|
-
* optionally override the `host` URL.
|
|
47
|
+
* Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.
|
|
48
|
+
* You can also optionally override the `host` URL.
|
|
49
49
|
*
|
|
50
50
|
* @example
|
|
51
51
|
* ```ts
|
|
@@ -59,7 +59,8 @@ type PostHogTraceExporterOptions = {
|
|
|
59
59
|
* ```
|
|
60
60
|
*/
|
|
61
61
|
declare class PostHogTraceExporter extends OTLPTraceExporter {
|
|
62
|
-
|
|
62
|
+
private readonly disabled;
|
|
63
|
+
constructor(options?: PostHogTraceExporterOptions);
|
|
63
64
|
export(spans: ReadableSpan[], resultCallback: (result: {
|
|
64
65
|
code: number;
|
|
65
66
|
error?: Error;
|
|
@@ -70,7 +71,7 @@ interface PostHogSpanProcessorOptions {
|
|
|
70
71
|
/**
|
|
71
72
|
* Your PostHog project API key.
|
|
72
73
|
*/
|
|
73
|
-
apiKey
|
|
74
|
+
apiKey?: string;
|
|
74
75
|
/**
|
|
75
76
|
* PostHog host URL. Defaults to `https://us.i.posthog.com`.
|
|
76
77
|
*/
|
|
@@ -83,6 +84,8 @@ interface PostHogSpanProcessorOptions {
|
|
|
83
84
|
/**
|
|
84
85
|
* An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
|
|
85
86
|
*
|
|
87
|
+
* Missing or blank project API keys disable the processor.
|
|
88
|
+
*
|
|
86
89
|
* Internally batches spans and exports them to PostHog's OTLP ingestion
|
|
87
90
|
* endpoint. Only AI-related spans (those whose name or attribute keys
|
|
88
91
|
* start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;
|
|
@@ -105,7 +108,7 @@ interface PostHogSpanProcessorOptions {
|
|
|
105
108
|
*/
|
|
106
109
|
declare class PostHogSpanProcessor implements SpanProcessor {
|
|
107
110
|
private readonly inner;
|
|
108
|
-
constructor(options
|
|
111
|
+
constructor(options?: PostHogSpanProcessorOptions);
|
|
109
112
|
onStart(span: Span, parentContext: Context): void;
|
|
110
113
|
onEnd(span: ReadableSpan): void;
|
|
111
114
|
shutdown(): Promise<void>;
|
package/dist/otel/index.mjs
CHANGED
|
@@ -29,8 +29,8 @@ function normalizeHost$1(value) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Options for the PostHogTraceExporter.
|
|
33
|
-
* optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.
|
|
32
|
+
* Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank
|
|
33
|
+
* tokens disable the exporter. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.
|
|
34
34
|
*
|
|
35
35
|
* @example
|
|
36
36
|
* ```ts
|
|
@@ -61,8 +61,8 @@ function normalizeHost$1(value) {
|
|
|
61
61
|
* plug PostHog into an existing processor chain. Otherwise prefer
|
|
62
62
|
* {@link PostHogSpanProcessor}, which is self-contained.
|
|
63
63
|
*
|
|
64
|
-
*
|
|
65
|
-
* optionally override the `host` URL.
|
|
64
|
+
* Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.
|
|
65
|
+
* You can also optionally override the `host` URL.
|
|
66
66
|
*
|
|
67
67
|
* @example
|
|
68
68
|
* ```ts
|
|
@@ -76,20 +76,30 @@ function normalizeHost$1(value) {
|
|
|
76
76
|
* ```
|
|
77
77
|
*/
|
|
78
78
|
class PostHogTraceExporter extends OTLPTraceExporter {
|
|
79
|
-
constructor(options) {
|
|
80
|
-
const token =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
const host = new URL(normalizeHost$1(options.host)).origin;
|
|
79
|
+
constructor(options = {}) {
|
|
80
|
+
const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey);
|
|
81
|
+
const disabled = !token;
|
|
82
|
+
const host = token ? new URL(normalizeHost$1(options.host)).origin : DEFAULT_OTEL_HOST$1;
|
|
85
83
|
super({
|
|
86
84
|
url: `${host}/i/v0/ai/otel`,
|
|
87
|
-
headers: {
|
|
85
|
+
headers: token ? {
|
|
88
86
|
Authorization: `Bearer ${token}`
|
|
89
|
-
}
|
|
87
|
+
} : {}
|
|
90
88
|
});
|
|
89
|
+
this.disabled = disabled;
|
|
90
|
+
if (this.disabled) {
|
|
91
|
+
console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.');
|
|
92
|
+
}
|
|
91
93
|
}
|
|
92
94
|
export(spans, resultCallback) {
|
|
95
|
+
if (this.disabled) {
|
|
96
|
+
// Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.
|
|
97
|
+
// Reporting failure would make OpenTelemetry treat every span as an export error.
|
|
98
|
+
resultCallback({
|
|
99
|
+
code: ExportResultCode.SUCCESS
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
93
103
|
const aiSpans = spans.filter(isAISpan);
|
|
94
104
|
if (aiSpans.length === 0) {
|
|
95
105
|
resultCallback({
|
|
@@ -109,9 +119,26 @@ function normalizeHost(value) {
|
|
|
109
119
|
const normalizedValue = typeof value === 'string' ? value.trim() : '';
|
|
110
120
|
return normalizedValue || DEFAULT_OTEL_HOST;
|
|
111
121
|
}
|
|
122
|
+
class NoopSpanProcessor {
|
|
123
|
+
onStart(_span, _parentContext) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
onEnd(_span) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
shutdown() {
|
|
130
|
+
return Promise.resolve();
|
|
131
|
+
}
|
|
132
|
+
forceFlush() {
|
|
133
|
+
return Promise.resolve();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
112
137
|
/**
|
|
113
138
|
* An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
|
|
114
139
|
*
|
|
140
|
+
* Missing or blank project API keys disable the processor.
|
|
141
|
+
*
|
|
115
142
|
* Internally batches spans and exports them to PostHog's OTLP ingestion
|
|
116
143
|
* endpoint. Only AI-related spans (those whose name or attribute keys
|
|
117
144
|
* start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;
|
|
@@ -133,10 +160,12 @@ function normalizeHost(value) {
|
|
|
133
160
|
* ```
|
|
134
161
|
*/
|
|
135
162
|
class PostHogSpanProcessor {
|
|
136
|
-
constructor(options) {
|
|
163
|
+
constructor(options = {}) {
|
|
137
164
|
const apiKey = normalizeApiKey(options.apiKey);
|
|
138
165
|
if (!apiKey) {
|
|
139
|
-
|
|
166
|
+
console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.');
|
|
167
|
+
this.inner = new NoopSpanProcessor();
|
|
168
|
+
return;
|
|
140
169
|
}
|
|
141
170
|
if (options._spanProcessor) {
|
|
142
171
|
this.inner = options._spanProcessor;
|
package/dist/otel/index.mjs.map
CHANGED
|
@@ -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\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeToken(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\n/**\n * Options for the PostHogTraceExporter. You must obligatorily provide `projectToken`. You can also\n * optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions =\n | { projectToken: string; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey: string\n projectToken?: never\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 * You must obligatorily provide `projectToken`. You can also\n * optionally override the `host` URL.\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({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n constructor(options: PostHogTraceExporterOptions) {\n const token = 'projectToken' in options ? normalizeToken(options.projectToken) : normalizeToken(options.apiKey)\n if (!token) {\n throw new Error('PostHogTraceExporter requires a projectToken')\n }\n\n const host = new URL(normalizeHost(options.host)).origin\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${token}`,\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","normalizeToken","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","token","projectToken","apiKey","Error","host","URL","origin","url","headers","Authorization","export","spans","resultCallback","aiSpans","filter","length","code","ExportResultCode","SUCCESS","normalizeApiKey","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 normalizeToken(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\n/**\n * Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank\n * tokens disable the exporter. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions =\n | { projectToken?: string; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey?: string\n projectToken?: never\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 * Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.\n * You can also optionally override the `host` URL.\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({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n private readonly disabled: boolean\n\n constructor(options: PostHogTraceExporterOptions = {}) {\n const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey)\n const disabled = !token\n const host = token ? new URL(normalizeHost(options.host)).origin : DEFAULT_OTEL_HOST\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : {},\n })\n\n this.disabled = disabled\n if (this.disabled) {\n console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.')\n }\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n if (this.disabled) {\n // Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.\n // Reporting failure would make OpenTelemetry treat every span as an export error.\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n\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\nclass NoopSpanProcessor implements SpanProcessor {\n onStart(_span: Span, _parentContext: Context): void {\n return\n }\n onEnd(_span: ReadableSpan): void {\n return\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * Missing or blank project API keys disable the processor.\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 console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\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","normalizeToken","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","token","projectToken","apiKey","disabled","host","URL","origin","url","headers","Authorization","console","warn","export","spans","resultCallback","code","ExportResultCode","SUCCESS","aiSpans","filter","length","normalizeApiKey","NoopSpanProcessor","onStart","_span","_parentContext","onEnd","shutdown","Promise","resolve","forceFlush","PostHogSpanProcessor","inner","_spanProcessor","exporter","BatchSpanProcessor","parentContext"],"mappings":"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"}
|
package/dist/vercel/index.cjs
CHANGED
package/dist/vercel/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/ai",
|
|
3
|
-
"version": "7.20.
|
|
3
|
+
"version": "7.20.3",
|
|
4
4
|
"description": "PostHog Node.js AI integrations",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"@types/jest": "^29.5.14",
|
|
26
26
|
"jest": "29.7.0",
|
|
27
27
|
"node-fetch": "^3.3.2",
|
|
28
|
-
"@posthog-tooling/
|
|
29
|
-
"posthog-node": "5.35.
|
|
30
|
-
"@posthog-tooling/
|
|
28
|
+
"@posthog-tooling/tsconfig-base": "1.1.1",
|
|
29
|
+
"posthog-node": "5.35.9",
|
|
30
|
+
"@posthog-tooling/rollup-utils": "1.1.1"
|
|
31
31
|
},
|
|
32
32
|
"keywords": [
|
|
33
33
|
"posthog",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"openai": "^6.25.0",
|
|
51
51
|
"uuid": "^11.1.0",
|
|
52
52
|
"zod": "^4.1.13",
|
|
53
|
-
"@posthog/core": "1.
|
|
53
|
+
"@posthog/core": "1.30.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@opentelemetry/api": "^1.9.0",
|