neatlogs 1.0.2 → 1.0.4

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 CHANGED
@@ -458,6 +458,33 @@ The following libraries are registered in the instrumentation registry for futur
458
458
 
459
459
  `cohere`, `groq`, `together`, `vertexai`, `google_generativeai`, `mistralai`, `ollama`, `watsonx`, `alephalpha`, `replicate`, `sagemaker`, `huggingface_hub`, `litellm`, `langgraph`, `llamaindex`, `autogen`, `haystack`, `dspy`, `chromadb`, `pinecone`, `weaviate`, `qdrant`, `milvus`, `opensearch`, `elasticsearch`, `redis`, `marqo`, `instructor`, `guardrails`, `google_adk`, `agno`, `openai_agents`, `pydantic_ai`, `smolagents`, `strands`, `pipecat`, `portkey`, `promptflow`
460
460
 
461
+ ## Framework Integrations
462
+
463
+ For frameworks that don't fit the auto-instrument-on-init pattern, neatlogs ships dedicated companion packages. Install only the ones you need:
464
+
465
+ | Framework | Package | Helper |
466
+ |-----------|---------|--------|
467
+ | Mastra (`@mastra/core`) | `@neatlogs/instrumentation-mastra` | `getMastraObservability()` — pass to `new Mastra({ observability })` |
468
+ | Vercel AI SDK (`ai`) | `@neatlogs/instrumentation-ai-sdk` | `getAISDKWrapper()` — wraps `generateText` / `streamText` / `generateObject` / `streamObject` |
469
+
470
+ ```typescript
471
+ // Vercel AI SDK
472
+ import { init, getAISDKWrapper, shutdown } from 'neatlogs';
473
+ import * as ai from 'ai';
474
+ import { openai } from '@ai-sdk/openai';
475
+
476
+ await init({ apiKey: process.env.NEATLOGS_API_KEY });
477
+ const wrapAISDK = await getAISDKWrapper();
478
+ const { generateText } = wrapAISDK(ai);
479
+
480
+ const { text } = await generateText({
481
+ model: openai('gpt-4o-mini'),
482
+ prompt: 'What is TypeScript?',
483
+ });
484
+
485
+ await shutdown();
486
+ ```
487
+
461
488
  ## Configuration
462
489
 
463
490
  ### Environment Variables
@@ -559,6 +586,7 @@ See the [`examples/`](./examples/) directory for complete, runnable examples:
559
586
  | [`prompt-management.ts`](./examples/prompt-management.ts) | PromptTemplate + trace() for prompt versioning |
560
587
  | [`multi-agent-workflow.ts`](./examples/multi-agent-workflow.ts) | Nested spans: WORKFLOW → AGENT → TOOL |
561
588
  | [`custom-spans.ts`](./examples/custom-spans.ts) | All span kinds: WORKFLOW, CHAIN, AGENT, TOOL, RETRIEVER, EMBEDDING, GUARDRAIL |
589
+ | [`sdk_examples/ai_sdk_basic/`](./examples/sdk_examples/ai_sdk_basic/) | Vercel AI SDK via `wrapAISDK` — generateText + streamText + tools |
562
590
 
563
591
  Run any example with:
564
592
 
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/ai-sdk.ts
21
+ var ai_sdk_exports = {};
22
+ __export(ai_sdk_exports, {
23
+ createAITelemetry: () => createAITelemetry,
24
+ wrapAISDK: () => wrapAISDK
25
+ });
26
+ module.exports = __toCommonJS(ai_sdk_exports);
27
+ var import_api = require("@opentelemetry/api");
28
+ var TRACER_NAME = "neatlogs.ai-sdk";
29
+ function createAITelemetry(opts = {}) {
30
+ const userMeta = opts.metadata ?? {};
31
+ return {
32
+ isEnabled: true,
33
+ recordInputs: true,
34
+ recordOutputs: true,
35
+ tracer: import_api.trace.getTracer(TRACER_NAME),
36
+ metadata: { ...userMeta, neatlogsWrapped: true }
37
+ };
38
+ }
39
+ function safeStringify(value) {
40
+ try {
41
+ return JSON.stringify(value);
42
+ } catch {
43
+ return "";
44
+ }
45
+ }
46
+ function setInputValue(span, opts) {
47
+ if (opts && ("prompt" in opts || "messages" in opts)) {
48
+ const input = opts.messages ?? opts.prompt;
49
+ const stringified2 = safeStringify(input);
50
+ if (stringified2) {
51
+ span.setAttribute("input.value", stringified2);
52
+ }
53
+ return;
54
+ }
55
+ const stringified = safeStringify(opts);
56
+ if (stringified) {
57
+ span.setAttribute("input.value", stringified);
58
+ }
59
+ }
60
+ function setOutputValue(span, result) {
61
+ if (result && typeof result === "object") {
62
+ const r = result;
63
+ if ("text" in r && "finishReason" in r) {
64
+ const text = String(r.text ?? "");
65
+ if (text) {
66
+ span.setAttribute("output.value", text);
67
+ }
68
+ if (r.finishReason) {
69
+ span.setAttribute("gen_ai.finish_reason", String(r.finishReason));
70
+ }
71
+ return;
72
+ }
73
+ }
74
+ const stringified = safeStringify(result);
75
+ if (stringified) {
76
+ span.setAttribute("output.value", stringified);
77
+ }
78
+ }
79
+ var WRAPPED_FUNCTIONS = [
80
+ "generateText",
81
+ "streamText",
82
+ "generateObject",
83
+ "streamObject",
84
+ "embed",
85
+ "embedMany",
86
+ "rerank"
87
+ ];
88
+ function wrapAISDK(aiModule) {
89
+ const wrapped = { ...aiModule };
90
+ for (const name of WRAPPED_FUNCTIONS) {
91
+ const original = aiModule[name];
92
+ if (typeof original !== "function") continue;
93
+ if (name === "streamText" || name === "streamObject") {
94
+ wrapped[name] = createSyncWrapper(name, original);
95
+ } else {
96
+ wrapped[name] = createAsyncWrapper(name, original);
97
+ }
98
+ }
99
+ return wrapped;
100
+ }
101
+ function rootSpanKind(name) {
102
+ if (name === "embed" || name === "embedMany" || name === "rerank") return "CHAIN";
103
+ return "WORKFLOW";
104
+ }
105
+ function getParentContext() {
106
+ const activeSpan = import_api.trace.getActiveSpan();
107
+ if (activeSpan) {
108
+ const instrScope = activeSpan.instrumentationLibrary?.name ?? "";
109
+ if (instrScope === "next.js") {
110
+ return import_api.ROOT_CONTEXT;
111
+ }
112
+ }
113
+ return import_api.context.active();
114
+ }
115
+ function createAsyncWrapper(name, original) {
116
+ return async function wrappedAsyncFn(opts) {
117
+ const tracer = import_api.trace.getTracer(TRACER_NAME);
118
+ return tracer.startActiveSpan(
119
+ `ai.${name}`,
120
+ { attributes: { "openinference.span.kind": rootSpanKind(name) } },
121
+ getParentContext(),
122
+ async (span) => {
123
+ try {
124
+ const isEmbedOrRerank = name === "embed" || name === "embedMany" || name === "rerank";
125
+ if (!isEmbedOrRerank) {
126
+ setInputValue(span, opts);
127
+ }
128
+ if (name === "rerank" && opts?.query) {
129
+ span.setAttribute("ai.rerank.query", String(opts.query));
130
+ }
131
+ const merged = mergeTelemetry(opts);
132
+ const result = await original(merged);
133
+ if (!isEmbedOrRerank) {
134
+ setOutputValue(span, result);
135
+ }
136
+ return result;
137
+ } catch (err) {
138
+ recordSpanError(span, err);
139
+ throw err;
140
+ } finally {
141
+ span.end();
142
+ }
143
+ }
144
+ );
145
+ };
146
+ }
147
+ function createSyncWrapper(name, original) {
148
+ return function wrappedSyncFn(opts) {
149
+ const tracer = import_api.trace.getTracer(TRACER_NAME);
150
+ return tracer.startActiveSpan(`ai.${name}`, { attributes: { "openinference.span.kind": rootSpanKind(name) } }, getParentContext(), (span) => {
151
+ try {
152
+ setInputValue(span, opts);
153
+ const merged = mergeTelemetry(opts);
154
+ const result = original(merged);
155
+ return result;
156
+ } catch (err) {
157
+ recordSpanError(span, err);
158
+ throw err;
159
+ } finally {
160
+ span.end();
161
+ }
162
+ });
163
+ };
164
+ }
165
+ function mergeTelemetry(opts) {
166
+ const baseTelemetry = createAITelemetry({
167
+ metadata: opts?.experimental_telemetry?.metadata
168
+ });
169
+ return {
170
+ ...opts,
171
+ experimental_telemetry: {
172
+ ...opts?.experimental_telemetry,
173
+ ...baseTelemetry
174
+ }
175
+ };
176
+ }
177
+ function recordSpanError(span, err) {
178
+ if (err instanceof Error) {
179
+ span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: err.message });
180
+ span.recordException(err);
181
+ } else {
182
+ span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: String(err) });
183
+ }
184
+ }
185
+ // Annotate the CommonJS export names for ESM import in node:
186
+ 0 && (module.exports = {
187
+ createAITelemetry,
188
+ wrapAISDK
189
+ });
190
+ //# sourceMappingURL=ai-sdk.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ai-sdk.ts"],"sourcesContent":["/**\n * Vercel AI SDK wrapper — inline implementation.\n *\n * Wraps `generateText`, `streamText`, `generateObject`, `streamObject` from\n * the `ai` package with OTel parent spans + forced telemetry. Static export,\n * no dynamic imports — bundler-friendly (works with Turbopack, webpack, esbuild).\n *\n * Usage:\n * import { wrapAISDK } from 'neatlogs';\n * import * as ai from 'ai';\n * const { streamText, generateText } = wrapAISDK(ai);\n */\n\nimport { trace, context as otelContext, ROOT_CONTEXT, SpanStatusCode, type Span, type Tracer } from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.ai-sdk';\n\n// -- Telemetry config --------------------------------------------------------\n\nexport interface CreateAITelemetryOptions {\n metadata?: Record<string, unknown>;\n}\n\nexport interface AITelemetryConfig {\n isEnabled: true;\n recordInputs: true;\n recordOutputs: true;\n tracer: Tracer;\n metadata: Record<string, unknown>;\n}\n\nexport function createAITelemetry(\n opts: CreateAITelemetryOptions = {},\n): AITelemetryConfig {\n const userMeta = opts.metadata ?? {};\n return {\n isEnabled: true,\n recordInputs: true,\n recordOutputs: true,\n tracer: trace.getTracer(TRACER_NAME),\n metadata: { ...userMeta, neatlogsWrapped: true },\n };\n}\n\n// -- Span attributes ---------------------------------------------------------\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nfunction setInputValue(span: Span, opts: Record<string, unknown>): void {\n // For generateText/streamText — only capture prompt or messages, not full model config\n if (opts && ('prompt' in opts || 'messages' in opts)) {\n const input = opts.messages ?? opts.prompt;\n const stringified = safeStringify(input);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n return;\n }\n const stringified = safeStringify(opts);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n}\n\nfunction setOutputValue(span: Span, result: unknown): void {\n if (result && typeof result === 'object') {\n const r = result as Record<string, unknown>;\n // GenerateTextResult / StreamTextResult — extract meaningful fields\n if ('text' in r && 'finishReason' in r) {\n const text = String(r.text ?? '');\n if (text) {\n span.setAttribute('output.value', text);\n }\n if (r.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(r.finishReason));\n }\n return;\n }\n }\n const stringified = safeStringify(result);\n if (stringified) {\n span.setAttribute('output.value', stringified);\n }\n}\n\n// -- Wrapping ----------------------------------------------------------------\n\ntype WrappedFunctionName = 'generateText' | 'streamText' | 'generateObject' | 'streamObject' | 'embed' | 'embedMany' | 'rerank';\n\nconst WRAPPED_FUNCTIONS: readonly WrappedFunctionName[] = [\n 'generateText',\n 'streamText',\n 'generateObject',\n 'streamObject',\n 'embed',\n 'embedMany',\n 'rerank',\n] as const;\n\n/**\n * Wrap the `ai` module namespace so that every `generateText` / `streamText` /\n * `generateObject` / `streamObject` call:\n *\n * 1. Opens a parent OTel span on the active TracerProvider.\n * 2. Forces `experimental_telemetry: { isEnabled: true }`, merging user metadata.\n * 3. Records input/output on the parent span and propagates errors.\n *\n * Other exports (types, helpers) pass through unchanged.\n */\nexport function wrapAISDK<T extends Record<string, unknown>>(aiModule: T): T {\n const wrapped: Record<string, unknown> = { ...aiModule };\n\n for (const name of WRAPPED_FUNCTIONS) {\n const original = aiModule[name];\n if (typeof original !== 'function') continue;\n\n if (name === 'streamText' || name === 'streamObject') {\n wrapped[name] = createSyncWrapper(name, original as (opts: any) => unknown);\n } else {\n wrapped[name] = createAsyncWrapper(name, original as (opts: any) => Promise<unknown>);\n }\n }\n\n return wrapped as T;\n}\n\nfunction rootSpanKind(name: WrappedFunctionName): string {\n if (name === 'embed' || name === 'embedMany' || name === 'rerank') return 'CHAIN';\n return 'WORKFLOW';\n}\n\n\nfunction getParentContext() {\n const activeSpan = trace.getActiveSpan();\n if (activeSpan) {\n const instrScope = (activeSpan as any).instrumentationLibrary?.name ?? '';\n if (instrScope === 'next.js') {\n return ROOT_CONTEXT;\n }\n }\n return otelContext.active();\n}\n\nfunction createAsyncWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => Promise<unknown>,\n): (opts: any) => Promise<unknown> {\n return async function wrappedAsyncFn(opts: any): Promise<unknown> {\n const tracer = trace.getTracer(TRACER_NAME);\n return tracer.startActiveSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n getParentContext(),\n async (span) => {\n try {\n const isEmbedOrRerank = name === 'embed' || name === 'embedMany' || name === 'rerank';\n if (!isEmbedOrRerank) {\n setInputValue(span, opts);\n }\n if (name === 'rerank' && opts?.query) {\n span.setAttribute('ai.rerank.query', String(opts.query));\n }\n const merged = mergeTelemetry(opts);\n const result = await original(merged);\n if (!isEmbedOrRerank) {\n setOutputValue(span, result);\n }\n return result;\n } catch (err) {\n recordSpanError(span, err);\n throw err;\n } finally {\n span.end();\n }\n },\n );\n };\n}\n\nfunction createSyncWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => unknown,\n): (opts: any) => unknown {\n return function wrappedSyncFn(opts: any): unknown {\n const tracer = trace.getTracer(TRACER_NAME);\n return tracer.startActiveSpan(`ai.${name}`, { attributes: { 'openinference.span.kind': rootSpanKind(name) } }, getParentContext(), (span) => {\n try {\n setInputValue(span, opts);\n const merged = mergeTelemetry(opts);\n const result = original(merged);\n return result;\n } catch (err) {\n recordSpanError(span, err);\n throw err;\n } finally {\n span.end();\n }\n });\n };\n}\n\nfunction mergeTelemetry(opts: any): any {\n const baseTelemetry: AITelemetryConfig = createAITelemetry({\n metadata: opts?.experimental_telemetry?.metadata,\n });\n return {\n ...opts,\n experimental_telemetry: {\n ...opts?.experimental_telemetry,\n ...baseTelemetry,\n },\n };\n}\n\n\nfunction recordSpanError(span: Span, err: unknown): void {\n if (err instanceof Error) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });\n span.recordException(err);\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });\n }\n}\n"],"mappings":"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","names":["stringified","otelContext"]}
@@ -0,0 +1,39 @@
1
+ import { Tracer } from '@opentelemetry/api';
2
+
3
+ /**
4
+ * Vercel AI SDK wrapper — inline implementation.
5
+ *
6
+ * Wraps `generateText`, `streamText`, `generateObject`, `streamObject` from
7
+ * the `ai` package with OTel parent spans + forced telemetry. Static export,
8
+ * no dynamic imports — bundler-friendly (works with Turbopack, webpack, esbuild).
9
+ *
10
+ * Usage:
11
+ * import { wrapAISDK } from 'neatlogs';
12
+ * import * as ai from 'ai';
13
+ * const { streamText, generateText } = wrapAISDK(ai);
14
+ */
15
+
16
+ interface CreateAITelemetryOptions {
17
+ metadata?: Record<string, unknown>;
18
+ }
19
+ interface AITelemetryConfig {
20
+ isEnabled: true;
21
+ recordInputs: true;
22
+ recordOutputs: true;
23
+ tracer: Tracer;
24
+ metadata: Record<string, unknown>;
25
+ }
26
+ declare function createAITelemetry(opts?: CreateAITelemetryOptions): AITelemetryConfig;
27
+ /**
28
+ * Wrap the `ai` module namespace so that every `generateText` / `streamText` /
29
+ * `generateObject` / `streamObject` call:
30
+ *
31
+ * 1. Opens a parent OTel span on the active TracerProvider.
32
+ * 2. Forces `experimental_telemetry: { isEnabled: true }`, merging user metadata.
33
+ * 3. Records input/output on the parent span and propagates errors.
34
+ *
35
+ * Other exports (types, helpers) pass through unchanged.
36
+ */
37
+ declare function wrapAISDK<T extends Record<string, unknown>>(aiModule: T): T;
38
+
39
+ export { type AITelemetryConfig, type CreateAITelemetryOptions, createAITelemetry, wrapAISDK };
@@ -0,0 +1,164 @@
1
+ // src/ai-sdk.ts
2
+ import { trace, context as otelContext, ROOT_CONTEXT, SpanStatusCode } from "@opentelemetry/api";
3
+ var TRACER_NAME = "neatlogs.ai-sdk";
4
+ function createAITelemetry(opts = {}) {
5
+ const userMeta = opts.metadata ?? {};
6
+ return {
7
+ isEnabled: true,
8
+ recordInputs: true,
9
+ recordOutputs: true,
10
+ tracer: trace.getTracer(TRACER_NAME),
11
+ metadata: { ...userMeta, neatlogsWrapped: true }
12
+ };
13
+ }
14
+ function safeStringify(value) {
15
+ try {
16
+ return JSON.stringify(value);
17
+ } catch {
18
+ return "";
19
+ }
20
+ }
21
+ function setInputValue(span, opts) {
22
+ if (opts && ("prompt" in opts || "messages" in opts)) {
23
+ const input = opts.messages ?? opts.prompt;
24
+ const stringified2 = safeStringify(input);
25
+ if (stringified2) {
26
+ span.setAttribute("input.value", stringified2);
27
+ }
28
+ return;
29
+ }
30
+ const stringified = safeStringify(opts);
31
+ if (stringified) {
32
+ span.setAttribute("input.value", stringified);
33
+ }
34
+ }
35
+ function setOutputValue(span, result) {
36
+ if (result && typeof result === "object") {
37
+ const r = result;
38
+ if ("text" in r && "finishReason" in r) {
39
+ const text = String(r.text ?? "");
40
+ if (text) {
41
+ span.setAttribute("output.value", text);
42
+ }
43
+ if (r.finishReason) {
44
+ span.setAttribute("gen_ai.finish_reason", String(r.finishReason));
45
+ }
46
+ return;
47
+ }
48
+ }
49
+ const stringified = safeStringify(result);
50
+ if (stringified) {
51
+ span.setAttribute("output.value", stringified);
52
+ }
53
+ }
54
+ var WRAPPED_FUNCTIONS = [
55
+ "generateText",
56
+ "streamText",
57
+ "generateObject",
58
+ "streamObject",
59
+ "embed",
60
+ "embedMany",
61
+ "rerank"
62
+ ];
63
+ function wrapAISDK(aiModule) {
64
+ const wrapped = { ...aiModule };
65
+ for (const name of WRAPPED_FUNCTIONS) {
66
+ const original = aiModule[name];
67
+ if (typeof original !== "function") continue;
68
+ if (name === "streamText" || name === "streamObject") {
69
+ wrapped[name] = createSyncWrapper(name, original);
70
+ } else {
71
+ wrapped[name] = createAsyncWrapper(name, original);
72
+ }
73
+ }
74
+ return wrapped;
75
+ }
76
+ function rootSpanKind(name) {
77
+ if (name === "embed" || name === "embedMany" || name === "rerank") return "CHAIN";
78
+ return "WORKFLOW";
79
+ }
80
+ function getParentContext() {
81
+ const activeSpan = trace.getActiveSpan();
82
+ if (activeSpan) {
83
+ const instrScope = activeSpan.instrumentationLibrary?.name ?? "";
84
+ if (instrScope === "next.js") {
85
+ return ROOT_CONTEXT;
86
+ }
87
+ }
88
+ return otelContext.active();
89
+ }
90
+ function createAsyncWrapper(name, original) {
91
+ return async function wrappedAsyncFn(opts) {
92
+ const tracer = trace.getTracer(TRACER_NAME);
93
+ return tracer.startActiveSpan(
94
+ `ai.${name}`,
95
+ { attributes: { "openinference.span.kind": rootSpanKind(name) } },
96
+ getParentContext(),
97
+ async (span) => {
98
+ try {
99
+ const isEmbedOrRerank = name === "embed" || name === "embedMany" || name === "rerank";
100
+ if (!isEmbedOrRerank) {
101
+ setInputValue(span, opts);
102
+ }
103
+ if (name === "rerank" && opts?.query) {
104
+ span.setAttribute("ai.rerank.query", String(opts.query));
105
+ }
106
+ const merged = mergeTelemetry(opts);
107
+ const result = await original(merged);
108
+ if (!isEmbedOrRerank) {
109
+ setOutputValue(span, result);
110
+ }
111
+ return result;
112
+ } catch (err) {
113
+ recordSpanError(span, err);
114
+ throw err;
115
+ } finally {
116
+ span.end();
117
+ }
118
+ }
119
+ );
120
+ };
121
+ }
122
+ function createSyncWrapper(name, original) {
123
+ return function wrappedSyncFn(opts) {
124
+ const tracer = trace.getTracer(TRACER_NAME);
125
+ return tracer.startActiveSpan(`ai.${name}`, { attributes: { "openinference.span.kind": rootSpanKind(name) } }, getParentContext(), (span) => {
126
+ try {
127
+ setInputValue(span, opts);
128
+ const merged = mergeTelemetry(opts);
129
+ const result = original(merged);
130
+ return result;
131
+ } catch (err) {
132
+ recordSpanError(span, err);
133
+ throw err;
134
+ } finally {
135
+ span.end();
136
+ }
137
+ });
138
+ };
139
+ }
140
+ function mergeTelemetry(opts) {
141
+ const baseTelemetry = createAITelemetry({
142
+ metadata: opts?.experimental_telemetry?.metadata
143
+ });
144
+ return {
145
+ ...opts,
146
+ experimental_telemetry: {
147
+ ...opts?.experimental_telemetry,
148
+ ...baseTelemetry
149
+ }
150
+ };
151
+ }
152
+ function recordSpanError(span, err) {
153
+ if (err instanceof Error) {
154
+ span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
155
+ span.recordException(err);
156
+ } else {
157
+ span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
158
+ }
159
+ }
160
+ export {
161
+ createAITelemetry,
162
+ wrapAISDK
163
+ };
164
+ //# sourceMappingURL=ai-sdk.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ai-sdk.ts"],"sourcesContent":["/**\n * Vercel AI SDK wrapper — inline implementation.\n *\n * Wraps `generateText`, `streamText`, `generateObject`, `streamObject` from\n * the `ai` package with OTel parent spans + forced telemetry. Static export,\n * no dynamic imports — bundler-friendly (works with Turbopack, webpack, esbuild).\n *\n * Usage:\n * import { wrapAISDK } from 'neatlogs';\n * import * as ai from 'ai';\n * const { streamText, generateText } = wrapAISDK(ai);\n */\n\nimport { trace, context as otelContext, ROOT_CONTEXT, SpanStatusCode, type Span, type Tracer } from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.ai-sdk';\n\n// -- Telemetry config --------------------------------------------------------\n\nexport interface CreateAITelemetryOptions {\n metadata?: Record<string, unknown>;\n}\n\nexport interface AITelemetryConfig {\n isEnabled: true;\n recordInputs: true;\n recordOutputs: true;\n tracer: Tracer;\n metadata: Record<string, unknown>;\n}\n\nexport function createAITelemetry(\n opts: CreateAITelemetryOptions = {},\n): AITelemetryConfig {\n const userMeta = opts.metadata ?? {};\n return {\n isEnabled: true,\n recordInputs: true,\n recordOutputs: true,\n tracer: trace.getTracer(TRACER_NAME),\n metadata: { ...userMeta, neatlogsWrapped: true },\n };\n}\n\n// -- Span attributes ---------------------------------------------------------\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nfunction setInputValue(span: Span, opts: Record<string, unknown>): void {\n // For generateText/streamText — only capture prompt or messages, not full model config\n if (opts && ('prompt' in opts || 'messages' in opts)) {\n const input = opts.messages ?? opts.prompt;\n const stringified = safeStringify(input);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n return;\n }\n const stringified = safeStringify(opts);\n if (stringified) {\n span.setAttribute('input.value', stringified);\n }\n}\n\nfunction setOutputValue(span: Span, result: unknown): void {\n if (result && typeof result === 'object') {\n const r = result as Record<string, unknown>;\n // GenerateTextResult / StreamTextResult — extract meaningful fields\n if ('text' in r && 'finishReason' in r) {\n const text = String(r.text ?? '');\n if (text) {\n span.setAttribute('output.value', text);\n }\n if (r.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(r.finishReason));\n }\n return;\n }\n }\n const stringified = safeStringify(result);\n if (stringified) {\n span.setAttribute('output.value', stringified);\n }\n}\n\n// -- Wrapping ----------------------------------------------------------------\n\ntype WrappedFunctionName = 'generateText' | 'streamText' | 'generateObject' | 'streamObject' | 'embed' | 'embedMany' | 'rerank';\n\nconst WRAPPED_FUNCTIONS: readonly WrappedFunctionName[] = [\n 'generateText',\n 'streamText',\n 'generateObject',\n 'streamObject',\n 'embed',\n 'embedMany',\n 'rerank',\n] as const;\n\n/**\n * Wrap the `ai` module namespace so that every `generateText` / `streamText` /\n * `generateObject` / `streamObject` call:\n *\n * 1. Opens a parent OTel span on the active TracerProvider.\n * 2. Forces `experimental_telemetry: { isEnabled: true }`, merging user metadata.\n * 3. Records input/output on the parent span and propagates errors.\n *\n * Other exports (types, helpers) pass through unchanged.\n */\nexport function wrapAISDK<T extends Record<string, unknown>>(aiModule: T): T {\n const wrapped: Record<string, unknown> = { ...aiModule };\n\n for (const name of WRAPPED_FUNCTIONS) {\n const original = aiModule[name];\n if (typeof original !== 'function') continue;\n\n if (name === 'streamText' || name === 'streamObject') {\n wrapped[name] = createSyncWrapper(name, original as (opts: any) => unknown);\n } else {\n wrapped[name] = createAsyncWrapper(name, original as (opts: any) => Promise<unknown>);\n }\n }\n\n return wrapped as T;\n}\n\nfunction rootSpanKind(name: WrappedFunctionName): string {\n if (name === 'embed' || name === 'embedMany' || name === 'rerank') return 'CHAIN';\n return 'WORKFLOW';\n}\n\n\nfunction getParentContext() {\n const activeSpan = trace.getActiveSpan();\n if (activeSpan) {\n const instrScope = (activeSpan as any).instrumentationLibrary?.name ?? '';\n if (instrScope === 'next.js') {\n return ROOT_CONTEXT;\n }\n }\n return otelContext.active();\n}\n\nfunction createAsyncWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => Promise<unknown>,\n): (opts: any) => Promise<unknown> {\n return async function wrappedAsyncFn(opts: any): Promise<unknown> {\n const tracer = trace.getTracer(TRACER_NAME);\n return tracer.startActiveSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n getParentContext(),\n async (span) => {\n try {\n const isEmbedOrRerank = name === 'embed' || name === 'embedMany' || name === 'rerank';\n if (!isEmbedOrRerank) {\n setInputValue(span, opts);\n }\n if (name === 'rerank' && opts?.query) {\n span.setAttribute('ai.rerank.query', String(opts.query));\n }\n const merged = mergeTelemetry(opts);\n const result = await original(merged);\n if (!isEmbedOrRerank) {\n setOutputValue(span, result);\n }\n return result;\n } catch (err) {\n recordSpanError(span, err);\n throw err;\n } finally {\n span.end();\n }\n },\n );\n };\n}\n\nfunction createSyncWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => unknown,\n): (opts: any) => unknown {\n return function wrappedSyncFn(opts: any): unknown {\n const tracer = trace.getTracer(TRACER_NAME);\n return tracer.startActiveSpan(`ai.${name}`, { attributes: { 'openinference.span.kind': rootSpanKind(name) } }, getParentContext(), (span) => {\n try {\n setInputValue(span, opts);\n const merged = mergeTelemetry(opts);\n const result = original(merged);\n return result;\n } catch (err) {\n recordSpanError(span, err);\n throw err;\n } finally {\n span.end();\n }\n });\n };\n}\n\nfunction mergeTelemetry(opts: any): any {\n const baseTelemetry: AITelemetryConfig = createAITelemetry({\n metadata: opts?.experimental_telemetry?.metadata,\n });\n return {\n ...opts,\n experimental_telemetry: {\n ...opts?.experimental_telemetry,\n ...baseTelemetry,\n },\n };\n}\n\n\nfunction recordSpanError(span: Span, err: unknown): void {\n if (err instanceof Error) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });\n span.recordException(err);\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });\n }\n}\n"],"mappings":"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","names":["stringified"]}