neatlogs 1.1.6 → 1.1.8
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 +16 -27
- package/dist/ai-sdk.cjs +142 -54
- package/dist/ai-sdk.cjs.map +1 -1
- package/dist/ai-sdk.mjs +145 -53
- package/dist/ai-sdk.mjs.map +1 -1
- package/dist/anthropic.cjs +65 -29
- package/dist/anthropic.cjs.map +1 -1
- package/dist/anthropic.mjs +73 -36
- package/dist/anthropic.mjs.map +1 -1
- package/dist/azure-openai.cjs +65 -29
- package/dist/azure-openai.cjs.map +1 -1
- package/dist/azure-openai.mjs +73 -36
- package/dist/azure-openai.mjs.map +1 -1
- package/dist/bedrock.cjs +65 -29
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.mjs +73 -36
- package/dist/bedrock.mjs.map +1 -1
- package/dist/claude-agent-sdk.cjs +55 -15
- package/dist/claude-agent-sdk.cjs.map +1 -1
- package/dist/claude-agent-sdk.mjs +52 -8
- package/dist/claude-agent-sdk.mjs.map +1 -1
- package/dist/google-genai.cjs +69 -35
- package/dist/google-genai.cjs.map +1 -1
- package/dist/google-genai.mjs +77 -42
- package/dist/google-genai.mjs.map +1 -1
- package/dist/index.cjs +963 -682
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +44 -2
- package/dist/index.mjs +893 -609
- package/dist/index.mjs.map +1 -1
- package/dist/langchain.cjs +46 -12
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.mjs +54 -19
- package/dist/langchain.mjs.map +1 -1
- package/dist/mastra-wrap.cjs +330 -46
- package/dist/mastra-wrap.cjs.map +1 -1
- package/dist/mastra-wrap.mjs +331 -41
- package/dist/mastra-wrap.mjs.map +1 -1
- package/dist/openai-agents.cjs +45 -9
- package/dist/openai-agents.cjs.map +1 -1
- package/dist/openai-agents.mjs +45 -5
- package/dist/openai-agents.mjs.map +1 -1
- package/dist/openai.cjs +65 -29
- package/dist/openai.cjs.map +1 -1
- package/dist/openai.mjs +73 -36
- package/dist/openai.mjs.map +1 -1
- package/dist/opencode-plugin.cjs +1 -1
- package/dist/opencode-plugin.cjs.map +1 -1
- package/dist/opencode-plugin.mjs +1 -1
- package/dist/opencode-plugin.mjs.map +1 -1
- package/dist/openrouter-agent.cjs +48 -12
- package/dist/openrouter-agent.cjs.map +1 -1
- package/dist/openrouter-agent.mjs +56 -19
- package/dist/openrouter-agent.mjs.map +1 -1
- package/dist/pi-agent.cjs +45 -9
- package/dist/pi-agent.cjs.map +1 -1
- package/dist/pi-agent.mjs +45 -6
- package/dist/pi-agent.mjs.map +1 -1
- package/dist/strands.cjs +4 -136
- package/dist/strands.cjs.map +1 -1
- package/dist/strands.d.ts +5 -24
- package/dist/strands.mjs +4 -136
- package/dist/strands.mjs.map +1 -1
- package/dist/vertex-ai.cjs +69 -35
- package/dist/vertex-ai.cjs.map +1 -1
- package/dist/vertex-ai.mjs +77 -42
- package/dist/vertex-ai.mjs.map +1 -1
- package/package.json +2 -7
package/README.md
CHANGED
|
@@ -138,7 +138,9 @@ await init({
|
|
|
138
138
|
| `metadata` | `Record<string, any>` | — | Custom metadata attached to all spans. |
|
|
139
139
|
| `debug` | `boolean` | `false` | Enable debug logging. |
|
|
140
140
|
| `disableExport` | `boolean` | `false` | Disable export to Neatlogs backend. |
|
|
141
|
-
| `instrumentations` | `string[]` | — |
|
|
141
|
+
| `instrumentations` | `string[]` | — | Legacy manager path. Instrumentors that depend on global OTel context are rejected; use explicit wrappers below. |
|
|
142
|
+
| `tracerProvider` | `BasicTracerProvider` | Private SDK provider | Optional caller-owned private provider. It is never registered globally or shut down by Neatlogs. |
|
|
143
|
+
| `registerShutdownHandlers` | `boolean` | `true` for SDK-owned provider | Register process exit/signal handlers. Set `false` when the host application owns shutdown. |
|
|
142
144
|
| `mask` | `MaskFunction` | — | Global mask function applied to all spans. |
|
|
143
145
|
| `sampleRate` | `number` | `1.0` | Sampling rate (0.0 to 1.0). |
|
|
144
146
|
| `captureLogs` | `boolean` | `false` | Capture log records via OTel LoggerProvider. |
|
|
@@ -431,26 +433,13 @@ registerCrewaiTask('research-task', 'Research the latest AI developments');
|
|
|
431
433
|
|
|
432
434
|
## Supported Instrumentations
|
|
433
435
|
|
|
434
|
-
###
|
|
436
|
+
### Isolation policy
|
|
435
437
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
| `anthropic` | `@anthropic-ai/sdk` | `@arizeai/openinference-instrumentation-anthropic` |
|
|
442
|
-
| `bedrock` | `@aws-sdk/client-bedrock-runtime` | `@arizeai/openinference-instrumentation-bedrock` |
|
|
443
|
-
| `langchain` | `@langchain/core` | `@arizeai/openinference-instrumentation-langchain` |
|
|
444
|
-
| `mcp` | `@modelcontextprotocol/sdk` | `@arizeai/openinference-instrumentation-mcp` |
|
|
445
|
-
| `beeai` | `beeai-framework` | `@arizeai/openinference-instrumentation-beeai` |
|
|
446
|
-
| `claude_agent_sdk` | `@anthropic-ai/claude-agent-sdk` | `@arizeai/openinference-instrumentation-claude-agent-sdk` |
|
|
447
|
-
|
|
448
|
-
### Custom Instrumentors (built into neatlogs)
|
|
449
|
-
|
|
450
|
-
| Library | Package | Notes |
|
|
451
|
-
|---------|---------|-------|
|
|
452
|
-
| `google_genai` | `@google/genai` | Custom neatlogs instrumentor |
|
|
453
|
-
| `crewai` | `crewai` | Custom neatlogs instrumentor; auto-loads `litellm` |
|
|
438
|
+
Neatlogs always runs on a private provider and private async context. Third-party
|
|
439
|
+
auto-instrumentors that call the global OpenTelemetry context API cannot provide
|
|
440
|
+
bidirectional isolation, so the manager rejects them at initialization before
|
|
441
|
+
creating any provider state. Use the explicit provider/framework wrappers
|
|
442
|
+
instead.
|
|
454
443
|
|
|
455
444
|
### Registry Entries (not yet instrumented in TypeScript)
|
|
456
445
|
|
|
@@ -460,21 +449,21 @@ The following libraries are registered in the instrumentation registry for futur
|
|
|
460
449
|
|
|
461
450
|
## Framework Integrations
|
|
462
451
|
|
|
463
|
-
For frameworks that don't fit the auto-instrument-on-init pattern,
|
|
452
|
+
For frameworks that don't fit the auto-instrument-on-init pattern, use the SDK's explicit wrappers. These wrappers use Neatlogs' private context and remain isolated from other tracing SDKs:
|
|
464
453
|
|
|
465
|
-
| Framework |
|
|
466
|
-
|
|
467
|
-
| Mastra (`@mastra/core`) |
|
|
468
|
-
| Vercel AI SDK (`ai`) |
|
|
454
|
+
| Framework | Helper |
|
|
455
|
+
|-----------|--------|
|
|
456
|
+
| Mastra (`@mastra/core`) | `wrapMastra()` from `neatlogs/mastra` |
|
|
457
|
+
| Vercel AI SDK (`ai`) | `wrapAISDK()` from `neatlogs/ai` |
|
|
469
458
|
|
|
470
459
|
```typescript
|
|
471
460
|
// Vercel AI SDK
|
|
472
|
-
import { init,
|
|
461
|
+
import { init, shutdown } from 'neatlogs';
|
|
462
|
+
import { wrapAISDK } from 'neatlogs/ai';
|
|
473
463
|
import * as ai from 'ai';
|
|
474
464
|
import { openai } from '@ai-sdk/openai';
|
|
475
465
|
|
|
476
466
|
await init({ apiKey: process.env.NEATLOGS_API_KEY });
|
|
477
|
-
const wrapAISDK = await getAISDKWrapper();
|
|
478
467
|
const { generateText } = wrapAISDK(ai);
|
|
479
468
|
|
|
480
469
|
const { text } = await generateText({
|
package/dist/ai-sdk.cjs
CHANGED
|
@@ -24,7 +24,74 @@ __export(ai_sdk_exports, {
|
|
|
24
24
|
wrapAISDK: () => wrapAISDK
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(ai_sdk_exports);
|
|
27
|
+
var import_api2 = require("@opentelemetry/api");
|
|
28
|
+
|
|
29
|
+
// src/core/provider.ts
|
|
30
|
+
var import_node_async_hooks = require("async_hooks");
|
|
27
31
|
var import_api = require("@opentelemetry/api");
|
|
32
|
+
var PRIVATE_SPAN_STORAGE_KEY = /* @__PURE__ */ Symbol.for(
|
|
33
|
+
"neatlogs.private_span_async_local_storage"
|
|
34
|
+
);
|
|
35
|
+
var PRIVATE_PROVIDER_STATE_KEY = /* @__PURE__ */ Symbol.for(
|
|
36
|
+
"neatlogs.private_provider_state"
|
|
37
|
+
);
|
|
38
|
+
var neatlogsGlobal = globalThis;
|
|
39
|
+
var privateContextStorage = neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ?? (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new import_node_async_hooks.AsyncLocalStorage());
|
|
40
|
+
var providerState = neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ?? (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {
|
|
41
|
+
provider: null
|
|
42
|
+
});
|
|
43
|
+
var preInitTracer = {
|
|
44
|
+
startSpan() {
|
|
45
|
+
return import_api.trace.wrapSpanContext(import_api.INVALID_SPAN_CONTEXT);
|
|
46
|
+
},
|
|
47
|
+
startActiveSpan(_name, arg2, arg3, arg4) {
|
|
48
|
+
const fn = typeof arg2 === "function" ? arg2 : typeof arg3 === "function" ? arg3 : arg4;
|
|
49
|
+
return fn(import_api.trace.wrapSpanContext(import_api.INVALID_SPAN_CONTEXT));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function getNeatlogsTracer(name) {
|
|
53
|
+
return providerState.provider?.getTracer(name) ?? preInitTracer;
|
|
54
|
+
}
|
|
55
|
+
function isolateTracer(tracer) {
|
|
56
|
+
const facade = {
|
|
57
|
+
startSpan(name, options, context) {
|
|
58
|
+
const parent = context ?? getNeatlogsActiveContext();
|
|
59
|
+
return tracer.startSpan(name, options, parent);
|
|
60
|
+
},
|
|
61
|
+
startActiveSpan(name, arg2, arg3, arg4) {
|
|
62
|
+
let options;
|
|
63
|
+
let context;
|
|
64
|
+
let fn;
|
|
65
|
+
if (typeof arg2 === "function") {
|
|
66
|
+
fn = arg2;
|
|
67
|
+
} else if (typeof arg3 === "function") {
|
|
68
|
+
options = arg2;
|
|
69
|
+
fn = arg3;
|
|
70
|
+
} else {
|
|
71
|
+
options = arg2;
|
|
72
|
+
context = arg3;
|
|
73
|
+
fn = arg4;
|
|
74
|
+
}
|
|
75
|
+
const parent = context ?? getNeatlogsActiveContext();
|
|
76
|
+
const span = tracer.startSpan(name, options, parent);
|
|
77
|
+
return withNeatlogsSpan(span, () => fn(span), parent);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return facade;
|
|
81
|
+
}
|
|
82
|
+
function getNeatlogsActiveContext() {
|
|
83
|
+
return privateContextStorage.getStore() ?? import_api.ROOT_CONTEXT;
|
|
84
|
+
}
|
|
85
|
+
function getNeatlogsParentContext(baseContext) {
|
|
86
|
+
return baseContext ?? getNeatlogsActiveContext();
|
|
87
|
+
}
|
|
88
|
+
function withNeatlogsSpan(span, fn, baseContext) {
|
|
89
|
+
const base = baseContext ?? getNeatlogsActiveContext();
|
|
90
|
+
const ctx = import_api.trace.setSpan(base, span);
|
|
91
|
+
return privateContextStorage.run(ctx, fn);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/ai-sdk.ts
|
|
28
95
|
var TRACER_NAME = "neatlogs.ai-sdk";
|
|
29
96
|
function createAITelemetry(opts = {}) {
|
|
30
97
|
const userMeta = opts.metadata ?? {};
|
|
@@ -32,7 +99,11 @@ function createAITelemetry(opts = {}) {
|
|
|
32
99
|
isEnabled: true,
|
|
33
100
|
recordInputs: true,
|
|
34
101
|
recordOutputs: true,
|
|
35
|
-
tracer:
|
|
102
|
+
// Hand the AI SDK an isolation-aware tracer: it calls startActiveSpan()
|
|
103
|
+
// internally, which would otherwise parent its native spans from the foreign
|
|
104
|
+
// global context AND push them onto it (so a co-tenant's next span inherits
|
|
105
|
+
// ours). The facade routes both through the private Neatlogs context.
|
|
106
|
+
tracer: isolateTracer(getNeatlogsTracer(TRACER_NAME)),
|
|
36
107
|
metadata: { ...userMeta, neatlogsWrapped: true }
|
|
37
108
|
};
|
|
38
109
|
}
|
|
@@ -70,6 +141,15 @@ function setOutputValue(span, result) {
|
|
|
70
141
|
}
|
|
71
142
|
return;
|
|
72
143
|
}
|
|
144
|
+
if ("object" in r && "finishReason" in r) {
|
|
145
|
+
if (r.object !== void 0) {
|
|
146
|
+
span.setAttribute("output.value", safeStringify(r.object));
|
|
147
|
+
}
|
|
148
|
+
if (r.finishReason) {
|
|
149
|
+
span.setAttribute("gen_ai.finish_reason", String(r.finishReason));
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
73
153
|
}
|
|
74
154
|
const stringified = safeStringify(result);
|
|
75
155
|
if (stringified) {
|
|
@@ -116,23 +196,20 @@ function rootSpanKind(name) {
|
|
|
116
196
|
return "WORKFLOW";
|
|
117
197
|
}
|
|
118
198
|
function getParentContext() {
|
|
119
|
-
|
|
120
|
-
if (activeSpan) {
|
|
121
|
-
const instrScope = activeSpan.instrumentationLibrary?.name ?? "";
|
|
122
|
-
if (instrScope === "next.js") {
|
|
123
|
-
return import_api.ROOT_CONTEXT;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return import_api.context.active();
|
|
199
|
+
return getNeatlogsParentContext();
|
|
127
200
|
}
|
|
128
201
|
function createAsyncWrapper(name, original) {
|
|
129
202
|
return async function wrappedAsyncFn(opts) {
|
|
130
|
-
const tracer =
|
|
131
|
-
|
|
203
|
+
const tracer = getNeatlogsTracer(TRACER_NAME);
|
|
204
|
+
const parentContext = getParentContext();
|
|
205
|
+
const span = tracer.startSpan(
|
|
132
206
|
`ai.${name}`,
|
|
133
207
|
{ attributes: { "openinference.span.kind": rootSpanKind(name) } },
|
|
134
|
-
|
|
135
|
-
|
|
208
|
+
parentContext
|
|
209
|
+
);
|
|
210
|
+
return withNeatlogsSpan(
|
|
211
|
+
span,
|
|
212
|
+
async () => {
|
|
136
213
|
try {
|
|
137
214
|
const isEmbedOrRerank = name === "embed" || name === "embedMany" || name === "rerank";
|
|
138
215
|
if (!isEmbedOrRerank) {
|
|
@@ -153,52 +230,63 @@ function createAsyncWrapper(name, original) {
|
|
|
153
230
|
} finally {
|
|
154
231
|
span.end();
|
|
155
232
|
}
|
|
156
|
-
}
|
|
233
|
+
},
|
|
234
|
+
parentContext
|
|
157
235
|
);
|
|
158
236
|
};
|
|
159
237
|
}
|
|
160
238
|
function createStreamWrapper(name, original) {
|
|
161
239
|
return function wrappedStreamFn(opts) {
|
|
162
|
-
const tracer =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
240
|
+
const tracer = getNeatlogsTracer(TRACER_NAME);
|
|
241
|
+
const parentContext = getParentContext();
|
|
242
|
+
const span = tracer.startSpan(
|
|
243
|
+
`ai.${name}`,
|
|
244
|
+
{ attributes: { "openinference.span.kind": rootSpanKind(name) } },
|
|
245
|
+
parentContext
|
|
246
|
+
);
|
|
247
|
+
return withNeatlogsSpan(
|
|
248
|
+
span,
|
|
249
|
+
() => {
|
|
250
|
+
let spanEnded = false;
|
|
251
|
+
const endOnce = () => {
|
|
252
|
+
if (spanEnded) return;
|
|
253
|
+
spanEnded = true;
|
|
254
|
+
span.end();
|
|
255
|
+
};
|
|
256
|
+
try {
|
|
257
|
+
setInputValue(span, opts);
|
|
258
|
+
const merged = mergeTelemetry(opts);
|
|
259
|
+
const userOnFinish = opts?.onFinish;
|
|
260
|
+
const userOnError = opts?.onError;
|
|
261
|
+
const wrappedOpts = {
|
|
262
|
+
...merged,
|
|
263
|
+
onFinish: async (event) => {
|
|
264
|
+
try {
|
|
265
|
+
setStreamOutputValue(span, event);
|
|
266
|
+
} finally {
|
|
267
|
+
endOnce();
|
|
268
|
+
}
|
|
269
|
+
if (typeof userOnFinish === "function") {
|
|
270
|
+
return userOnFinish(event);
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
onError: (event) => {
|
|
274
|
+
recordSpanError(span, (event && event.error) ?? event);
|
|
181
275
|
endOnce();
|
|
276
|
+
if (typeof userOnError === "function") {
|
|
277
|
+
return userOnError(event);
|
|
278
|
+
}
|
|
182
279
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
return original(wrappedOpts);
|
|
196
|
-
} catch (err) {
|
|
197
|
-
recordSpanError(span, err);
|
|
198
|
-
endOnce();
|
|
199
|
-
throw err;
|
|
200
|
-
}
|
|
201
|
-
});
|
|
280
|
+
};
|
|
281
|
+
return original(wrappedOpts);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
recordSpanError(span, err);
|
|
284
|
+
endOnce();
|
|
285
|
+
throw err;
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
parentContext
|
|
289
|
+
);
|
|
202
290
|
};
|
|
203
291
|
}
|
|
204
292
|
function mergeTelemetry(opts) {
|
|
@@ -215,10 +303,10 @@ function mergeTelemetry(opts) {
|
|
|
215
303
|
}
|
|
216
304
|
function recordSpanError(span, err) {
|
|
217
305
|
if (err instanceof Error) {
|
|
218
|
-
span.setStatus({ code:
|
|
306
|
+
span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: err.message });
|
|
219
307
|
span.recordException(err);
|
|
220
308
|
} else {
|
|
221
|
-
span.setStatus({ code:
|
|
309
|
+
span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: String(err) });
|
|
222
310
|
}
|
|
223
311
|
}
|
|
224
312
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/ai-sdk.cjs.map
CHANGED
|
@@ -1 +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// Extract output from a streamText/streamObject `onFinish` event. The event\n// extends StepResult, so `text` (streamText) / `object` (streamObject) sit at\n// the top level alongside `finishReason` — but the event also carries `steps`,\n// `usage`, etc., so we pull only the meaningful fields instead of stringifying\n// the whole envelope.\nfunction setStreamOutputValue(span: Span, event: unknown): void {\n if (!event || typeof event !== 'object') return;\n const e = event as Record<string, unknown>;\n if (typeof e.text === 'string' && e.text) {\n span.setAttribute('output.value', e.text);\n } else if ('object' in e && e.object !== undefined) {\n const stringified = safeStringify(e.object);\n if (stringified) span.setAttribute('output.value', stringified);\n }\n if (e.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(e.finishReason));\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] = createStreamWrapper(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\n// streamText/streamObject return synchronously while the model keeps producing\n// tokens for seconds afterwards. Ending the span in a `finally` (as a plain sync\n// wrapper would) closes it in ~2ms with no output — the output only exists once\n// the stream finishes. Instead we keep the span open and end it from the AI SDK's\n// `onFinish` callback, where the final text/object is available. Any user-provided\n// `onFinish` is preserved and invoked first.\nfunction createStreamWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => unknown,\n): (opts: any) => unknown {\n return function wrappedStreamFn(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 let spanEnded = false;\n const endOnce = () => {\n if (spanEnded) return;\n spanEnded = true;\n span.end();\n };\n try {\n setInputValue(span, opts);\n const merged = mergeTelemetry(opts);\n const userOnFinish = opts?.onFinish;\n const userOnError = opts?.onError;\n const wrappedOpts = {\n ...merged,\n onFinish: async (event: any) => {\n try {\n setStreamOutputValue(span, event);\n } finally {\n endOnce();\n }\n if (typeof userOnFinish === 'function') {\n return userOnFinish(event);\n }\n },\n onError: (event: any) => {\n recordSpanError(span, (event && event.error) ?? event);\n endOnce();\n if (typeof userOnError === 'function') {\n return userOnError(event);\n }\n },\n };\n return original(wrappedOpts);\n } catch (err) {\n // Synchronous throw (e.g. bad arguments) — the stream never started.\n recordSpanError(span, err);\n endOnce();\n throw err;\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/ai-sdk.ts","../src/core/provider.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 { SpanStatusCode, type Span, type Tracer } from '@opentelemetry/api';\nimport { getNeatlogsTracer, getNeatlogsParentContext, isolateTracer, withNeatlogsSpan } from './core/provider.js';\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 // Hand the AI SDK an isolation-aware tracer: it calls startActiveSpan()\n // internally, which would otherwise parent its native spans from the foreign\n // global context AND push them onto it (so a co-tenant's next span inherits\n // ours). The facade routes both through the private Neatlogs context.\n tracer: isolateTracer(getNeatlogsTracer(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 // GenerateObjectResult — the structured object is the output, not `text`.\n // Without this the envelope (object+usage+response+…) gets stringified whole.\n if ('object' in r && 'finishReason' in r) {\n if (r.object !== undefined) {\n span.setAttribute('output.value', safeStringify(r.object));\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// Extract output from a streamText/streamObject `onFinish` event. The event\n// extends StepResult, so `text` (streamText) / `object` (streamObject) sit at\n// the top level alongside `finishReason` — but the event also carries `steps`,\n// `usage`, etc., so we pull only the meaningful fields instead of stringifying\n// the whole envelope.\nfunction setStreamOutputValue(span: Span, event: unknown): void {\n if (!event || typeof event !== 'object') return;\n const e = event as Record<string, unknown>;\n if (typeof e.text === 'string' && e.text) {\n span.setAttribute('output.value', e.text);\n } else if ('object' in e && e.object !== undefined) {\n const stringified = safeStringify(e.object);\n if (stringified) span.setAttribute('output.value', stringified);\n }\n if (e.finishReason) {\n span.setAttribute('gen_ai.finish_reason', String(e.finishReason));\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] = createStreamWrapper(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 // Our parent comes solely from the private span store; a\n // foreign provider's active span must never become our ancestor.\n return getNeatlogsParentContext();\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 = getNeatlogsTracer(TRACER_NAME);\n // startSpan (NOT startActiveSpan) + withNeatlogsSpan: startActiveSpan would\n // push our span onto the GLOBAL OTel context, so a foreign tracer's\n // startSpan() inside generateText() would read it as parent and inherit our\n // trace id. withNeatlogsSpan carries the parent in the private store in\n // the private context, leaving the global context untouched.\n const parentContext = getParentContext();\n const span = tracer.startSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n parentContext,\n );\n return withNeatlogsSpan(\n span,\n async () => {\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 parentContext,\n );\n };\n}\n\n// streamText/streamObject return synchronously while the model keeps producing\n// tokens for seconds afterwards. Ending the span in a `finally` (as a plain sync\n// wrapper would) closes it in ~2ms with no output — the output only exists once\n// the stream finishes. Instead we keep the span open and end it from the AI SDK's\n// `onFinish` callback, where the final text/object is available. Any user-provided\n// `onFinish` is preserved and invoked first.\nfunction createStreamWrapper(\n name: WrappedFunctionName,\n original: (opts: any) => unknown,\n): (opts: any) => unknown {\n return function wrappedStreamFn(opts: any): unknown {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n // startSpan + withNeatlogsSpan (see createAsyncWrapper) so streamText's\n // internals never see our span on the global OTel context. The span stays\n // open past the run scope and is ended from onFinish/onError.\n const parentContext = getParentContext();\n const span = tracer.startSpan(\n `ai.${name}`,\n { attributes: { 'openinference.span.kind': rootSpanKind(name) } },\n parentContext,\n );\n return withNeatlogsSpan(\n span,\n () => {\n let spanEnded = false;\n const endOnce = () => {\n if (spanEnded) return;\n spanEnded = true;\n span.end();\n };\n try {\n setInputValue(span, opts);\n const merged = mergeTelemetry(opts);\n const userOnFinish = opts?.onFinish;\n const userOnError = opts?.onError;\n const wrappedOpts = {\n ...merged,\n onFinish: async (event: any) => {\n try {\n setStreamOutputValue(span, event);\n } finally {\n endOnce();\n }\n if (typeof userOnFinish === 'function') {\n return userOnFinish(event);\n }\n },\n onError: (event: any) => {\n recordSpanError(span, (event && event.error) ?? event);\n endOnce();\n if (typeof userOnError === 'function') {\n return userOnError(event);\n }\n },\n };\n return original(wrappedOpts);\n } catch (err) {\n // Synchronous throw (e.g. bad arguments) — the stream never started.\n recordSpanError(span, err);\n endOnce();\n throw err;\n }\n },\n parentContext,\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","/**\n * Neatlogs-owned tracing state.\n *\n * Spans are created by the private Neatlogs provider and their parent is carried\n * in a private context key. The process-global OpenTelemetry span is\n * deliberately left untouched so other observability SDKs cannot export or\n * become parents of Neatlogs spans.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n ROOT_CONTEXT,\n INVALID_SPAN_CONTEXT,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\n\n// Entry points are bundled independently (`neatlogs`, `neatlogs/openai`,\n// `neatlogs/ai`, `neatlogs/mastra`, … each in both CJS and ESM), so every piece\n// of shared tracing state — the private span store AND the resolved provider /\n// provider — must live on `globalThis` behind a `Symbol.for` key.\n// Otherwise `init()` (run from the `neatlogs` bundle) sets `_provider` in ITS\n// module copy while a wrapper imported from `neatlogs/openai` reads a different,\n// still-null copy and silently falls back to the foreign global provider.\nconst PRIVATE_SPAN_STORAGE_KEY = Symbol.for(\n 'neatlogs.private_span_async_local_storage',\n);\nconst PRIVATE_PROVIDER_STATE_KEY = Symbol.for(\n 'neatlogs.private_provider_state',\n);\ninterface PrivateProviderState {\n provider: TracerProvider | null;\n}\ntype NeatlogsGlobal = typeof globalThis & {\n [PRIVATE_SPAN_STORAGE_KEY]?: AsyncLocalStorage<Context>;\n [PRIVATE_PROVIDER_STATE_KEY]?: PrivateProviderState;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\n// Stores the full Neatlogs Context (parent span PLUS any threaded values such as\n// trace()'s prompt-template keys), not just the span. We never\n// activate the OTel global context, so this private store is the ONLY channel\n// through which those values propagate down to descendant spans.\nconst privateContextStorage =\n neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ??\n (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage<Context>());\nconst providerState: PrivateProviderState =\n neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ??\n (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {\n provider: null,\n });\n// Wrappers may be constructed or accidentally invoked before init(). They must\n// never fall back to a foreign process-global provider, so pre-init calls use a\n// local no-op tracer and safely emit no exported spans.\nconst preInitTracer: Tracer = {\n startSpan(): Span {\n return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n _name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n const fn =\n typeof arg2 === 'function'\n ? arg2\n : typeof arg3 === 'function'\n ? arg3\n : arg4;\n return fn!(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT)) as ReturnType<F>;\n },\n};\n\n/** @internal Configure the provider used by Neatlogs-created spans. */\nexport function _setNeatlogsProvider(provider: TracerProvider | null): void {\n providerState.provider = provider;\n}\n\n/** Resolve a tracer from the private provider when one is configured. */\nexport function getNeatlogsTracer(name: string): Tracer {\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/**\n * @internal The private Neatlogs provider, or null before\n * init(). Used by integrations that must repoint a self-instrumenting library's\n * captured provider onto ours.\n */\nexport function getNeatlogsProvider(): TracerProvider | null {\n return providerState.provider;\n}\n\n/**\n * Wrap a tracer so that spans it creates parent from — and, for\n * `startActiveSpan`, activate on — the PRIVATE Neatlogs context instead of the\n * global one.\n *\n * We hand this to libraries that create their own spans off a tracer we give\n * them (the Vercel AI SDK's `experimental_telemetry.tracer`, which calls\n * `tracer.startActiveSpan()` internally). Without the facade the AI SDK's native\n * spans would parent from `context.active()` — the foreign co-tenant's context —\n * and `startActiveSpan` would push them onto the GLOBAL context, so a foreign\n * tracer's next span reads our native span as its parent. Both directions leak.\n *\n */\nexport function isolateTracer(tracer: Tracer): Tracer {\n const facade: Tracer = {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const parent = context ?? getNeatlogsActiveContext();\n return tracer.startSpan(name, options, parent);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n // Normalize the 2/3/4-arg overloads of startActiveSpan.\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n if (typeof arg2 === 'function') {\n fn = arg2 as F;\n } else if (typeof arg3 === 'function') {\n options = arg2 as SpanOptions;\n fn = arg3 as F;\n } else {\n options = arg2 as SpanOptions;\n context = arg3 as Context;\n fn = arg4 as F;\n }\n const parent = context ?? getNeatlogsActiveContext();\n const span = tracer.startSpan(name, options, parent);\n return withNeatlogsSpan(span, () => fn(span), parent) as ReturnType<F>;\n },\n };\n return facade;\n}\n\n/**\n * The base context to build new Neatlogs spans and values on. NEVER contains a\n * foreign provider's span: it reads our private store, or ROOT_CONTEXT when\n * nothing is active.\n */\nexport function getNeatlogsActiveContext(): Context {\n return privateContextStorage.getStore() ?? ROOT_CONTEXT;\n}\n\n/** Return only the active Neatlogs span (never a foreign provider's span). */\nexport function getActiveNeatlogsSpan(): Span | undefined {\n return otelTrace.getSpan(getNeatlogsActiveContext());\n}\n\n/**\n * Build a parent context that cannot contain a foreign provider's span.\n *\n * The active Neatlogs context already carries the parent span AND any values\n * the caller threaded in upstream (e.g. `trace()`'s prompt-template values), so\n * those values reach the span processor via `onStart(parentContext)`. An\n * explicit `baseContext` (a caller's own value-carrying context) is honored as-is.\n */\nexport function getNeatlogsParentContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * A base context for callers that thread parent linkage themselves\n * (callback/event handlers that keep their own run-id → span map: LangChain,\n * OpenAI-Agents, Claude Agent SDK).\n *\n * This is the ACTIVE Neatlogs context — the private store's\n * context if a Neatlogs `trace()`/`span()` encloses this call, else\n * ROOT_CONTEXT. So a handler's own root/entry span nests under an enclosing\n * Neatlogs trace (preserving its session + end-user id) when one exists, and\n * auto-roots cleanly when one doesn't. A foreign provider's active span can\n * never leak in as an ancestor because the global context is never read.\n */\nexport function getNeatlogsBaseContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * Build an execution context for a Neatlogs span while preserving the active\n * foreign context. The span rides our private context store.\n */\nexport function getNeatlogsExecutionContext(\n span: Span,\n baseContext: Context = ROOT_CONTEXT,\n): Context {\n return otelTrace.setSpan(baseContext, span);\n}\n\n/**\n * Run a callback with a Neatlogs span active under the appropriate policy.\n *\n * The stored context is `setSpan(base, span)` — carrying the span PLUS whatever\n * values `base` holds — so threaded values (prompt templates)\n * propagate to descendant spans through our private store instead of the global\n * OTel context we deliberately never touch.\n */\nexport function withNeatlogsSpan<T>(\n span: Span,\n fn: () => T,\n baseContext?: Context,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n const ctx = otelTrace.setSpan(base, span);\n return privateContextStorage.run(ctx, fn);\n}\n"],"mappings":"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","names":["import_api","otelTrace","otelTrace","stringified"]}
|
package/dist/ai-sdk.mjs
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
// src/ai-sdk.ts
|
|
2
|
-
import {
|
|
2
|
+
import { SpanStatusCode } from "@opentelemetry/api";
|
|
3
|
+
|
|
4
|
+
// src/core/provider.ts
|
|
5
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
6
|
+
import {
|
|
7
|
+
ROOT_CONTEXT,
|
|
8
|
+
INVALID_SPAN_CONTEXT,
|
|
9
|
+
trace as otelTrace
|
|
10
|
+
} from "@opentelemetry/api";
|
|
11
|
+
var PRIVATE_SPAN_STORAGE_KEY = /* @__PURE__ */ Symbol.for(
|
|
12
|
+
"neatlogs.private_span_async_local_storage"
|
|
13
|
+
);
|
|
14
|
+
var PRIVATE_PROVIDER_STATE_KEY = /* @__PURE__ */ Symbol.for(
|
|
15
|
+
"neatlogs.private_provider_state"
|
|
16
|
+
);
|
|
17
|
+
var neatlogsGlobal = globalThis;
|
|
18
|
+
var privateContextStorage = neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ?? (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage());
|
|
19
|
+
var providerState = neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ?? (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {
|
|
20
|
+
provider: null
|
|
21
|
+
});
|
|
22
|
+
var preInitTracer = {
|
|
23
|
+
startSpan() {
|
|
24
|
+
return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);
|
|
25
|
+
},
|
|
26
|
+
startActiveSpan(_name, arg2, arg3, arg4) {
|
|
27
|
+
const fn = typeof arg2 === "function" ? arg2 : typeof arg3 === "function" ? arg3 : arg4;
|
|
28
|
+
return fn(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT));
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function getNeatlogsTracer(name) {
|
|
32
|
+
return providerState.provider?.getTracer(name) ?? preInitTracer;
|
|
33
|
+
}
|
|
34
|
+
function isolateTracer(tracer) {
|
|
35
|
+
const facade = {
|
|
36
|
+
startSpan(name, options, context) {
|
|
37
|
+
const parent = context ?? getNeatlogsActiveContext();
|
|
38
|
+
return tracer.startSpan(name, options, parent);
|
|
39
|
+
},
|
|
40
|
+
startActiveSpan(name, arg2, arg3, arg4) {
|
|
41
|
+
let options;
|
|
42
|
+
let context;
|
|
43
|
+
let fn;
|
|
44
|
+
if (typeof arg2 === "function") {
|
|
45
|
+
fn = arg2;
|
|
46
|
+
} else if (typeof arg3 === "function") {
|
|
47
|
+
options = arg2;
|
|
48
|
+
fn = arg3;
|
|
49
|
+
} else {
|
|
50
|
+
options = arg2;
|
|
51
|
+
context = arg3;
|
|
52
|
+
fn = arg4;
|
|
53
|
+
}
|
|
54
|
+
const parent = context ?? getNeatlogsActiveContext();
|
|
55
|
+
const span = tracer.startSpan(name, options, parent);
|
|
56
|
+
return withNeatlogsSpan(span, () => fn(span), parent);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return facade;
|
|
60
|
+
}
|
|
61
|
+
function getNeatlogsActiveContext() {
|
|
62
|
+
return privateContextStorage.getStore() ?? ROOT_CONTEXT;
|
|
63
|
+
}
|
|
64
|
+
function getNeatlogsParentContext(baseContext) {
|
|
65
|
+
return baseContext ?? getNeatlogsActiveContext();
|
|
66
|
+
}
|
|
67
|
+
function withNeatlogsSpan(span, fn, baseContext) {
|
|
68
|
+
const base = baseContext ?? getNeatlogsActiveContext();
|
|
69
|
+
const ctx = otelTrace.setSpan(base, span);
|
|
70
|
+
return privateContextStorage.run(ctx, fn);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/ai-sdk.ts
|
|
3
74
|
var TRACER_NAME = "neatlogs.ai-sdk";
|
|
4
75
|
function createAITelemetry(opts = {}) {
|
|
5
76
|
const userMeta = opts.metadata ?? {};
|
|
@@ -7,7 +78,11 @@ function createAITelemetry(opts = {}) {
|
|
|
7
78
|
isEnabled: true,
|
|
8
79
|
recordInputs: true,
|
|
9
80
|
recordOutputs: true,
|
|
10
|
-
tracer:
|
|
81
|
+
// Hand the AI SDK an isolation-aware tracer: it calls startActiveSpan()
|
|
82
|
+
// internally, which would otherwise parent its native spans from the foreign
|
|
83
|
+
// global context AND push them onto it (so a co-tenant's next span inherits
|
|
84
|
+
// ours). The facade routes both through the private Neatlogs context.
|
|
85
|
+
tracer: isolateTracer(getNeatlogsTracer(TRACER_NAME)),
|
|
11
86
|
metadata: { ...userMeta, neatlogsWrapped: true }
|
|
12
87
|
};
|
|
13
88
|
}
|
|
@@ -45,6 +120,15 @@ function setOutputValue(span, result) {
|
|
|
45
120
|
}
|
|
46
121
|
return;
|
|
47
122
|
}
|
|
123
|
+
if ("object" in r && "finishReason" in r) {
|
|
124
|
+
if (r.object !== void 0) {
|
|
125
|
+
span.setAttribute("output.value", safeStringify(r.object));
|
|
126
|
+
}
|
|
127
|
+
if (r.finishReason) {
|
|
128
|
+
span.setAttribute("gen_ai.finish_reason", String(r.finishReason));
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
48
132
|
}
|
|
49
133
|
const stringified = safeStringify(result);
|
|
50
134
|
if (stringified) {
|
|
@@ -91,23 +175,20 @@ function rootSpanKind(name) {
|
|
|
91
175
|
return "WORKFLOW";
|
|
92
176
|
}
|
|
93
177
|
function getParentContext() {
|
|
94
|
-
|
|
95
|
-
if (activeSpan) {
|
|
96
|
-
const instrScope = activeSpan.instrumentationLibrary?.name ?? "";
|
|
97
|
-
if (instrScope === "next.js") {
|
|
98
|
-
return ROOT_CONTEXT;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return otelContext.active();
|
|
178
|
+
return getNeatlogsParentContext();
|
|
102
179
|
}
|
|
103
180
|
function createAsyncWrapper(name, original) {
|
|
104
181
|
return async function wrappedAsyncFn(opts) {
|
|
105
|
-
const tracer =
|
|
106
|
-
|
|
182
|
+
const tracer = getNeatlogsTracer(TRACER_NAME);
|
|
183
|
+
const parentContext = getParentContext();
|
|
184
|
+
const span = tracer.startSpan(
|
|
107
185
|
`ai.${name}`,
|
|
108
186
|
{ attributes: { "openinference.span.kind": rootSpanKind(name) } },
|
|
109
|
-
|
|
110
|
-
|
|
187
|
+
parentContext
|
|
188
|
+
);
|
|
189
|
+
return withNeatlogsSpan(
|
|
190
|
+
span,
|
|
191
|
+
async () => {
|
|
111
192
|
try {
|
|
112
193
|
const isEmbedOrRerank = name === "embed" || name === "embedMany" || name === "rerank";
|
|
113
194
|
if (!isEmbedOrRerank) {
|
|
@@ -128,52 +209,63 @@ function createAsyncWrapper(name, original) {
|
|
|
128
209
|
} finally {
|
|
129
210
|
span.end();
|
|
130
211
|
}
|
|
131
|
-
}
|
|
212
|
+
},
|
|
213
|
+
parentContext
|
|
132
214
|
);
|
|
133
215
|
};
|
|
134
216
|
}
|
|
135
217
|
function createStreamWrapper(name, original) {
|
|
136
218
|
return function wrappedStreamFn(opts) {
|
|
137
|
-
const tracer =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
219
|
+
const tracer = getNeatlogsTracer(TRACER_NAME);
|
|
220
|
+
const parentContext = getParentContext();
|
|
221
|
+
const span = tracer.startSpan(
|
|
222
|
+
`ai.${name}`,
|
|
223
|
+
{ attributes: { "openinference.span.kind": rootSpanKind(name) } },
|
|
224
|
+
parentContext
|
|
225
|
+
);
|
|
226
|
+
return withNeatlogsSpan(
|
|
227
|
+
span,
|
|
228
|
+
() => {
|
|
229
|
+
let spanEnded = false;
|
|
230
|
+
const endOnce = () => {
|
|
231
|
+
if (spanEnded) return;
|
|
232
|
+
spanEnded = true;
|
|
233
|
+
span.end();
|
|
234
|
+
};
|
|
235
|
+
try {
|
|
236
|
+
setInputValue(span, opts);
|
|
237
|
+
const merged = mergeTelemetry(opts);
|
|
238
|
+
const userOnFinish = opts?.onFinish;
|
|
239
|
+
const userOnError = opts?.onError;
|
|
240
|
+
const wrappedOpts = {
|
|
241
|
+
...merged,
|
|
242
|
+
onFinish: async (event) => {
|
|
243
|
+
try {
|
|
244
|
+
setStreamOutputValue(span, event);
|
|
245
|
+
} finally {
|
|
246
|
+
endOnce();
|
|
247
|
+
}
|
|
248
|
+
if (typeof userOnFinish === "function") {
|
|
249
|
+
return userOnFinish(event);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
onError: (event) => {
|
|
253
|
+
recordSpanError(span, (event && event.error) ?? event);
|
|
156
254
|
endOnce();
|
|
255
|
+
if (typeof userOnError === "function") {
|
|
256
|
+
return userOnError(event);
|
|
257
|
+
}
|
|
157
258
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
return original(wrappedOpts);
|
|
171
|
-
} catch (err) {
|
|
172
|
-
recordSpanError(span, err);
|
|
173
|
-
endOnce();
|
|
174
|
-
throw err;
|
|
175
|
-
}
|
|
176
|
-
});
|
|
259
|
+
};
|
|
260
|
+
return original(wrappedOpts);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
recordSpanError(span, err);
|
|
263
|
+
endOnce();
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
parentContext
|
|
268
|
+
);
|
|
177
269
|
};
|
|
178
270
|
}
|
|
179
271
|
function mergeTelemetry(opts) {
|