neatlogs 1.1.19 → 1.1.21
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 +34 -2
- package/dist/ai-sdk.cjs +186 -11
- package/dist/ai-sdk.cjs.map +1 -1
- package/dist/ai-sdk.d.ts +17 -7
- package/dist/ai-sdk.mjs +189 -12
- package/dist/ai-sdk.mjs.map +1 -1
- package/dist/anthropic.cjs +31 -14
- package/dist/anthropic.cjs.map +1 -1
- package/dist/anthropic.mjs +31 -14
- package/dist/anthropic.mjs.map +1 -1
- package/dist/azure-openai.cjs +31 -14
- package/dist/azure-openai.cjs.map +1 -1
- package/dist/azure-openai.mjs +31 -14
- package/dist/azure-openai.mjs.map +1 -1
- package/dist/bedrock.cjs +31 -14
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.mjs +31 -14
- package/dist/bedrock.mjs.map +1 -1
- package/dist/browser.cjs +26 -1
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.mjs +26 -1
- package/dist/browser.mjs.map +1 -1
- package/dist/claude-agent-sdk.cjs +2 -2
- package/dist/claude-agent-sdk.cjs.map +1 -1
- package/dist/claude-agent-sdk.mjs +2 -2
- package/dist/claude-agent-sdk.mjs.map +1 -1
- package/dist/cli.cjs +452 -200
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +427 -173
- package/dist/cli.mjs.map +1 -1
- package/dist/google-genai.cjs +31 -14
- package/dist/google-genai.cjs.map +1 -1
- package/dist/google-genai.mjs +31 -14
- package/dist/google-genai.mjs.map +1 -1
- package/dist/index.cjs +652 -333
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +15 -6
- package/dist/index.mjs +505 -184
- package/dist/index.mjs.map +1 -1
- package/dist/langchain.cjs +3 -3
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.mjs +3 -3
- package/dist/langchain.mjs.map +1 -1
- package/dist/mastra-wrap.cjs +2 -2
- package/dist/mastra-wrap.cjs.map +1 -1
- package/dist/mastra-wrap.mjs +2 -2
- package/dist/mastra-wrap.mjs.map +1 -1
- package/dist/openai-agents.cjs.map +1 -1
- package/dist/openai-agents.mjs.map +1 -1
- package/dist/openai.cjs +31 -14
- package/dist/openai.cjs.map +1 -1
- package/dist/openai.mjs +31 -14
- 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 +31 -14
- package/dist/openrouter-agent.cjs.map +1 -1
- package/dist/openrouter-agent.mjs +31 -14
- package/dist/openrouter-agent.mjs.map +1 -1
- package/dist/pi-agent.cjs.map +1 -1
- package/dist/pi-agent.mjs.map +1 -1
- package/dist/vertex-ai.cjs +31 -14
- package/dist/vertex-ai.cjs.map +1 -1
- package/dist/vertex-ai.mjs +31 -14
- package/dist/vertex-ai.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser.cjs
CHANGED
|
@@ -49,7 +49,11 @@ var Neatlogs = class {
|
|
|
49
49
|
this.apiKey = opts.apiKey;
|
|
50
50
|
this.project = opts.project;
|
|
51
51
|
const endpoint = opts.endpoint || DEFAULT_INGEST_ENDPOINT;
|
|
52
|
-
|
|
52
|
+
const origin = safeOrigin(endpoint);
|
|
53
|
+
if (origin === null) {
|
|
54
|
+
throw new Error(`Neatlogs: invalid endpoint ${JSON.stringify(endpoint)}`);
|
|
55
|
+
}
|
|
56
|
+
this.baseUrl = origin;
|
|
53
57
|
this.enabled = opts.enabled !== false;
|
|
54
58
|
this.endUserId = opts.endUserId;
|
|
55
59
|
this.endUserMetadata = opts.endUserMetadata;
|
|
@@ -117,6 +121,12 @@ var Neatlogs = class {
|
|
|
117
121
|
}
|
|
118
122
|
/** POST the trace JSON to the backend's /v1/trace endpoint. */
|
|
119
123
|
async post(body) {
|
|
124
|
+
const httpKindPath = findHttpKind(body);
|
|
125
|
+
if (httpKindPath) {
|
|
126
|
+
const error = `HTTP spans are not supported (${httpKindPath}). Trace the semantic AI operation instead.`;
|
|
127
|
+
this.onError(new Error(error));
|
|
128
|
+
return { ok: false, error };
|
|
129
|
+
}
|
|
120
130
|
if (!this.enabled) return { ok: true };
|
|
121
131
|
body = this.applyIdentity(body);
|
|
122
132
|
const payload = this.project && body.project === void 0 ? { ...body, project: this.project } : body;
|
|
@@ -155,6 +165,21 @@ function safeOrigin(endpoint) {
|
|
|
155
165
|
function nowIso() {
|
|
156
166
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
157
167
|
}
|
|
168
|
+
function findHttpKind(span, path = "root") {
|
|
169
|
+
const kindCandidates = [
|
|
170
|
+
span.kind,
|
|
171
|
+
span.attributes?.["neatlogs.span.kind"],
|
|
172
|
+
span.attributes?.["openinference.span.kind"]
|
|
173
|
+
];
|
|
174
|
+
if (kindCandidates.some((value) => String(value ?? "").trim().toUpperCase() === "HTTP")) {
|
|
175
|
+
return path;
|
|
176
|
+
}
|
|
177
|
+
for (let index = 0; index < (span.children?.length ?? 0); index += 1) {
|
|
178
|
+
const childPath = findHttpKind(span.children[index], `${path}.children[${index}]`);
|
|
179
|
+
if (childPath) return childPath;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
158
183
|
var browser_default = Neatlogs;
|
|
159
184
|
// Annotate the CommonJS export names for ESM import in node:
|
|
160
185
|
0 && (module.exports = {
|
package/dist/browser.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/constants.ts"],"sourcesContent":["/**\n * Neatlogs Browser SDK — `neatlogs/browser`\n *\n * A minimal, browser-safe client for sending traces to Neatlogs from web apps.\n * It has ZERO dependencies (no OpenTelemetry, no Node APIs) — only `fetch` — so\n * it bundles cleanly into front-end apps. It POSTs plain JSON to the backend's\n * simple trace endpoint (`/v1/trace`); the backend generates trace/span ids,\n * builds the hierarchy from nesting, infers cost from model+tokens, and pushes\n * through the normal pipeline. Nothing here streams OTLP.\n *\n * Usage:\n * import { Neatlogs } from 'neatlogs/browser';\n * const nl = new Neatlogs({ apiKey: 'nl_...' });\n *\n * // one-shot AI interaction\n * await nl.trackAI({ name: 'chat', model: 'gpt-4o', input, output,\n * tokens: { prompt: 10, completion: 5 } });\n *\n * // a full nested trace (same shape the backend's POST /v1/trace accepts)\n * await nl.trace({ name: 'support-chat', children: [\n * { name: 'retrieve', query, documents },\n * { name: 'answer', model: 'gpt-4o', input, output },\n * ]});\n *\n * // streaming: open, accumulate, finish\n * const t = nl.startTrace({ name: 'chat', model: 'gpt-4o', input });\n * t.finish({ output: full, tokens: { prompt, completion } });\n */\n\nimport { DEFAULT_INGEST_ENDPOINT } from './constants.js';\n\n// Canonical identity attribute keys (inlined — this file stays dependency-free).\nconst END_USER_ID_KEY = \"neatlogs.end_user.id\";\nconst END_USER_METADATA_KEY = \"neatlogs.end_user.metadata\";\nconst SESSION_ID_KEY = \"neatlogs.session.id\";\n\n// --- the simple trace shape the backend (/v1/trace) accepts --------------------\n// Mirrors the server-side SimpleSpan; kept local so this file has no imports.\n\nexport interface NeatlogsLog {\n level?: string;\n message: string;\n timestamp?: string;\n}\n\n/**\n * Span kinds the backend accepts (the canonical set). `kind` is optional — when\n * omitted the backend infers it from the fields present.\n */\nexport type NeatlogsKind =\n | \"WORKFLOW\" | \"AGENT\" | \"CHAIN\" | \"TOOL\" | \"RETRIEVER\" | \"RERANKER\"\n | \"EMBEDDING\" | \"LLM\" | \"GUARDRAIL\" | \"MCP_TOOL\" | \"TASK\"\n | \"VECTOR_STORE\" | \"EVALUATOR\" | \"HTTP\";\n\nexport interface NeatlogsSpan {\n name: string;\n /** Optional — the backend infers the kind from fields when omitted. */\n kind?: NeatlogsKind | string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n query?: unknown;\n documents?: unknown;\n tool_name?: string;\n passed?: boolean;\n score?: number;\n metadata?: Record<string, unknown>;\n status?: string;\n error?: string;\n start?: string;\n end?: string;\n /** Simplest way to record latency — the backend derives end from start + this. */\n duration_ms?: number;\n /**\n * Full canonical-attribute escape hatch. Send ANY neatlogs.* attribute the SDK\n * supports — e.g. { \"neatlogs.llm.temperature\": 0.7, \"neatlogs.agent.role\":\n * \"researcher\", \"neatlogs.tool.parameters\": {...} }. Non-canonical keys are\n * dropped server-side. The fields above (model/tokens/query/...) are shortcuts\n * for the common ones; explicit `attributes` win on conflict.\n */\n attributes?: Record<string, unknown>;\n children?: NeatlogsSpan[];\n logs?: NeatlogsLog[];\n /**\n * END-USER this trace belongs to — only meaningful on the ROOT of a trace\n * (overrides the client default). One end-user per trace; the backend rolls it\n * up to the trace and its session. Ignored on child spans.\n */\n endUserId?: string;\n /** Arbitrary end-user fields for the trace root (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * SESSION this trace belongs to — the conversation/thread grouping many turns.\n * Only meaningful on the ROOT of a trace (overrides the client default). Every\n * trace sharing a `sessionId` is grouped into one session in the dashboard; a new\n * trace per turn, all with the same `sessionId`, forms a multi-turn conversation.\n * Ignored on child spans.\n */\n sessionId?: string;\n}\n\n/** The root of a trace = a span node (its `name` becomes the workflow name). */\nexport type NeatlogsTrace = NeatlogsSpan;\n\nexport interface NeatlogsOptions {\n /**\n * Your Neatlogs WRITE key (`nlw_…`) — an ingest-only credential safe to embed in\n * browser code. (A full project key also works but should not be exposed client-side,\n * since it can read data.)\n */\n apiKey: string;\n /**\n * Project NAME to ingest into. REQUIRED when using a write key (the key identifies\n * you, not a project). Sent as the root `project` field on every trace. Ignored for\n * a full project key (already project-scoped).\n */\n project?: string;\n /** Backend base URL. Defaults to the same host the SDKs use. */\n endpoint?: string;\n /** Set false to validate calls without sending (default true). */\n enabled?: boolean;\n /** Called on transport errors instead of throwing (default: console.warn). */\n onError?: (err: unknown) => void;\n /**\n * Default END-USER identity for every trace this client sends — the user of\n * your app, not the operator. One end-user per trace; the backend rolls it up\n * to the trace and its session. A per-call `endUserId` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUserId })`.\n */\n endUserId?: string;\n /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * Default SESSION for every trace this client sends — the conversation/thread\n * these traces belong to. A per-call `sessionId` (on trace()/trackAI()) overrides\n * this. Set it once per conversation, e.g. `new Neatlogs({ apiKey, sessionId: convId })`.\n */\n sessionId?: string;\n}\n\nexport interface TrackResult {\n ok: boolean;\n trace_id?: string;\n spans?: number;\n error?: string;\n}\n\n/** Shorthand for a single AI interaction → a one-span trace. */\nexport interface TrackAIInput {\n name: string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n metadata?: Record<string, unknown>;\n duration_ms?: number;\n /** Any canonical neatlogs.* attributes (see NeatlogsSpan.attributes). */\n attributes?: Record<string, unknown>;\n /** Override the inferred kind (defaults to LLM for trackAI). */\n kind?: NeatlogsKind | string;\n /** END-USER for this trace (overrides the client default). One per trace. */\n endUserId?: string;\n /** Arbitrary end-user fields for this trace (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /** SESSION this trace belongs to (overrides the client default). */\n sessionId?: string;\n}\n\nexport class Neatlogs {\n private readonly apiKey: string;\n private readonly project?: string;\n private readonly baseUrl: string;\n private readonly enabled: boolean;\n private readonly onError: (err: unknown) => void;\n private readonly endUserId?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\n private readonly sessionId?: string;\n\n constructor(opts: NeatlogsOptions) {\n if (!opts || !opts.apiKey) {\n throw new Error(\"Neatlogs: apiKey is required\");\n }\n this.apiKey = opts.apiKey;\n this.project = opts.project;\n // Use the origin of the configured endpoint (same convention as the Node SDK),\n // so passing a full /v1/traces URL or a bare host both work.\n const endpoint = opts.endpoint || DEFAULT_INGEST_ENDPOINT;\n this.baseUrl = safeOrigin(endpoint) || DEFAULT_INGEST_ENDPOINT;\n this.enabled = opts.enabled !== false;\n this.endUserId = opts.endUserId;\n this.endUserMetadata = opts.endUserMetadata;\n this.sessionId = opts.sessionId;\n this.onError =\n opts.onError ||\n ((err) => {\n // eslint-disable-next-line no-console\n if (typeof console !== \"undefined\") console.warn(\"[neatlogs] send failed:\", err);\n });\n }\n\n /** Send a full (optionally nested) trace. Returns the backend's result. */\n async trace(trace: NeatlogsTrace): Promise<TrackResult> {\n return this.post(trace);\n }\n\n /** Send a single AI interaction as a one-span trace (kind defaults to LLM). */\n async trackAI(ai: TrackAIInput): Promise<TrackResult> {\n const { name, kind, ...rest } = ai;\n return this.post({ name, kind: kind ?? \"LLM\", ...rest });\n }\n\n /**\n * Begin a trace you'll complete later (e.g. streaming). Buffers the partial\n * input; call `.finish()` with the final output/tokens to send it. Nothing is\n * sent until `finish()`.\n */\n startTrace(initial: TrackAIInput): {\n finish: (final?: Partial<TrackAIInput>) => Promise<TrackResult>;\n } {\n const startedAt = nowIso();\n return {\n finish: (final?: Partial<TrackAIInput>) => {\n const merged: TrackAIInput = { ...initial, ...(final ?? {}) };\n const { name, kind, ...rest } = merged;\n return this.post({\n name,\n kind: kind ?? \"LLM\",\n start: startedAt,\n end: nowIso(),\n ...rest,\n });\n },\n };\n }\n\n /**\n * Fold identity (end-user + session) onto the trace ROOT as canonical\n * attributes, and strip the convenience `endUserId`/`endUserMetadata`/`sessionId`\n * fields so they aren't sent as raw root fields. Per-call values win over the\n * client defaults; explicit `attributes` win over both. Only the root carries\n * identity — one end-user per trace, one session per trace.\n */\n private applyIdentity(body: NeatlogsTrace): NeatlogsTrace {\n const { endUserId, endUserMetadata, sessionId, ...rest } = body as NeatlogsTrace & {\n endUserId?: string;\n endUserMetadata?: Record<string, unknown>;\n sessionId?: string;\n };\n const id = endUserId ?? this.endUserId;\n const meta = endUserMetadata ?? this.endUserMetadata;\n const session = sessionId ?? this.sessionId;\n if (id === undefined && meta === undefined && session === undefined) {\n return rest;\n }\n\n const attributes: Record<string, unknown> = { ...(rest.attributes ?? {}) };\n if (id !== undefined && attributes[END_USER_ID_KEY] === undefined) {\n attributes[END_USER_ID_KEY] = String(id);\n }\n if (meta !== undefined && attributes[END_USER_METADATA_KEY] === undefined) {\n attributes[END_USER_METADATA_KEY] =\n typeof meta === \"string\" ? meta : JSON.stringify(meta);\n }\n if (session !== undefined && attributes[SESSION_ID_KEY] === undefined) {\n attributes[SESSION_ID_KEY] = String(session);\n }\n return { ...rest, attributes };\n }\n\n /** POST the trace JSON to the backend's /v1/trace endpoint. */\n private async post(body: NeatlogsTrace): Promise<TrackResult> {\n if (!this.enabled) return { ok: true };\n // Fold identity (end-user + session) onto the root before anything else.\n body = this.applyIdentity(body);\n // Inject the configured project name into the root (required for write keys;\n // ignored server-side for full project keys). A `project` already on the body wins.\n const payload =\n this.project && (body as any).project === undefined\n ? { ...body, project: this.project }\n : body;\n try {\n const res = await fetch(`${this.baseUrl}/v1/trace`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n // keepalive lets the request survive a page unload (e.g. on navigation).\n keepalive: true,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const result = { ok: false, error: `HTTP ${res.status}${text ? `: ${text}` : \"\"}` };\n this.onError(new Error(result.error));\n return result;\n }\n const data = (await res.json().catch(() => ({}))) as Partial<TrackResult>;\n return { ok: true, trace_id: data.trace_id, spans: data.spans };\n } catch (err) {\n // Never throw into the host app over telemetry.\n this.onError(err);\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n// --- helpers (kept local, no imports) -----------------------------------------\n\nfunction safeOrigin(endpoint: string): string | null {\n try {\n return new URL(endpoint).origin;\n } catch {\n return null;\n }\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nexport default Neatlogs;\n","/** Stable SDK-wide defaults shared by every NeatLogs entry point. */\nexport const DEFAULT_INGEST_ENDPOINT = 'https://ingest.neatlogs.com' as const;\nexport const DEFAULT_MAX_QUEUE_ITEMS = 2048;\nexport const DEFAULT_MAX_SEMANTIC_STREAM_EVENTS = 128;\nexport const DEFAULT_MAX_STREAM_CAPTURE_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_STREAM_CAPTURE_ITEMS = 1024;\n\n/** UTF-8 byte length without allocating a second buffer for hostile stream chunks. */\nexport function utf8ByteLength(\n value: string,\n limit = Number.MAX_SAFE_INTEGER,\n): number {\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length) {\n const next = value.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n bytes += 4;\n i += 1;\n } else bytes += 3;\n } else bytes += 3;\n if (bytes > limit) return limit + 1;\n }\n return bytes;\n}\n\nexport function exportQueueCapacity(batchSize: number): number {\n return Math.max(DEFAULT_MAX_QUEUE_ITEMS, batchSize * 4);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/constants.ts"],"sourcesContent":["/**\n * Neatlogs Browser SDK — `neatlogs/browser`\n *\n * A minimal, browser-safe client for sending traces to Neatlogs from web apps.\n * It has ZERO dependencies (no OpenTelemetry, no Node APIs) — only `fetch` — so\n * it bundles cleanly into front-end apps. It POSTs plain JSON to the backend's\n * simple trace endpoint (`/v1/trace`); the backend generates trace/span ids,\n * builds the hierarchy from nesting, infers cost from model+tokens, and pushes\n * through the normal pipeline. Nothing here streams OTLP.\n *\n * Usage:\n * import { Neatlogs } from 'neatlogs/browser';\n * const nl = new Neatlogs({ apiKey: 'nl_...' });\n *\n * // one-shot AI interaction\n * await nl.trackAI({ name: 'chat', model: 'gpt-4o', input, output,\n * tokens: { prompt: 10, completion: 5 } });\n *\n * // a full nested trace (same shape the backend's POST /v1/trace accepts)\n * await nl.trace({ name: 'support-chat', children: [\n * { name: 'retrieve', query, documents },\n * { name: 'answer', model: 'gpt-4o', input, output },\n * ]});\n *\n * // streaming: open, accumulate, finish\n * const t = nl.startTrace({ name: 'chat', model: 'gpt-4o', input });\n * t.finish({ output: full, tokens: { prompt, completion } });\n */\n\nimport { DEFAULT_INGEST_ENDPOINT } from './constants.js';\n\n// Canonical identity attribute keys (inlined — this file stays dependency-free).\nconst END_USER_ID_KEY = \"neatlogs.end_user.id\";\nconst END_USER_METADATA_KEY = \"neatlogs.end_user.metadata\";\nconst SESSION_ID_KEY = \"neatlogs.session.id\";\n\n// --- the simple trace shape the backend (/v1/trace) accepts --------------------\n// Mirrors the server-side SimpleSpan; kept local so this file has no imports.\n\nexport interface NeatlogsLog {\n level?: string;\n message: string;\n timestamp?: string;\n}\n\n/**\n * Span kinds the backend accepts (the canonical set). `kind` is optional — when\n * omitted the backend infers it from the fields present.\n */\nexport type NeatlogsKind =\n | \"WORKFLOW\" | \"AGENT\" | \"CHAIN\" | \"TOOL\" | \"RETRIEVER\" | \"RERANKER\"\n | \"EMBEDDING\" | \"LLM\" | \"GUARDRAIL\" | \"MCP_TOOL\" | \"TASK\"\n | \"VECTOR_STORE\" | \"EVALUATOR\";\n\nexport interface NeatlogsSpan {\n name: string;\n /** Optional — the backend infers the kind from fields when omitted. */\n kind?: NeatlogsKind | string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n query?: unknown;\n documents?: unknown;\n tool_name?: string;\n passed?: boolean;\n score?: number;\n metadata?: Record<string, unknown>;\n status?: string;\n error?: string;\n start?: string;\n end?: string;\n /** Simplest way to record latency — the backend derives end from start + this. */\n duration_ms?: number;\n /**\n * Full canonical-attribute escape hatch. Send ANY neatlogs.* attribute the SDK\n * supports — e.g. { \"neatlogs.llm.temperature\": 0.7, \"neatlogs.agent.role\":\n * \"researcher\", \"neatlogs.tool.parameters\": {...} }. Non-canonical keys are\n * dropped server-side. The fields above (model/tokens/query/...) are shortcuts\n * for the common ones; explicit `attributes` win on conflict.\n */\n attributes?: Record<string, unknown>;\n children?: NeatlogsSpan[];\n logs?: NeatlogsLog[];\n /**\n * END-USER this trace belongs to — only meaningful on the ROOT of a trace\n * (overrides the client default). One end-user per trace; the backend rolls it\n * up to the trace and its session. Ignored on child spans.\n */\n endUserId?: string;\n /** Arbitrary end-user fields for the trace root (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * SESSION this trace belongs to — the conversation/thread grouping many turns.\n * Only meaningful on the ROOT of a trace (overrides the client default). Every\n * trace sharing a `sessionId` is grouped into one session in the dashboard; a new\n * trace per turn, all with the same `sessionId`, forms a multi-turn conversation.\n * Ignored on child spans.\n */\n sessionId?: string;\n}\n\n/** The root of a trace = a span node (its `name` becomes the workflow name). */\nexport type NeatlogsTrace = NeatlogsSpan;\n\nexport interface NeatlogsOptions {\n /**\n * Your Neatlogs WRITE key (`nlw_…`) — an ingest-only credential safe to embed in\n * browser code. (A full project key also works but should not be exposed client-side,\n * since it can read data.)\n */\n apiKey: string;\n /**\n * Project NAME to ingest into. REQUIRED when using a write key (the key identifies\n * you, not a project). Sent as the root `project` field on every trace. Ignored for\n * a full project key (already project-scoped).\n */\n project?: string;\n /** Backend base URL. Defaults to the same host the SDKs use. */\n endpoint?: string;\n /** Set false to validate calls without sending (default true). */\n enabled?: boolean;\n /** Called on transport errors instead of throwing (default: console.warn). */\n onError?: (err: unknown) => void;\n /**\n * Default END-USER identity for every trace this client sends — the user of\n * your app, not the operator. One end-user per trace; the backend rolls it up\n * to the trace and its session. A per-call `endUserId` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUserId })`.\n */\n endUserId?: string;\n /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * Default SESSION for every trace this client sends — the conversation/thread\n * these traces belong to. A per-call `sessionId` (on trace()/trackAI()) overrides\n * this. Set it once per conversation, e.g. `new Neatlogs({ apiKey, sessionId: convId })`.\n */\n sessionId?: string;\n}\n\nexport interface TrackResult {\n ok: boolean;\n trace_id?: string;\n spans?: number;\n error?: string;\n}\n\n/** Shorthand for a single AI interaction → a one-span trace. */\nexport interface TrackAIInput {\n name: string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n metadata?: Record<string, unknown>;\n duration_ms?: number;\n /** Any canonical neatlogs.* attributes (see NeatlogsSpan.attributes). */\n attributes?: Record<string, unknown>;\n /** Override the inferred kind (defaults to LLM for trackAI). */\n kind?: NeatlogsKind | string;\n /** END-USER for this trace (overrides the client default). One per trace. */\n endUserId?: string;\n /** Arbitrary end-user fields for this trace (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /** SESSION this trace belongs to (overrides the client default). */\n sessionId?: string;\n}\n\nexport class Neatlogs {\n private readonly apiKey: string;\n private readonly project?: string;\n private readonly baseUrl: string;\n private readonly enabled: boolean;\n private readonly onError: (err: unknown) => void;\n private readonly endUserId?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\n private readonly sessionId?: string;\n\n constructor(opts: NeatlogsOptions) {\n if (!opts || !opts.apiKey) {\n throw new Error(\"Neatlogs: apiKey is required\");\n }\n this.apiKey = opts.apiKey;\n this.project = opts.project;\n // Use the origin of the configured endpoint (same convention as the Node SDK),\n // so passing a full /v1/traces URL or a bare host both work. A malformed\n // endpoint throws instead of silently falling back to prod ingest, which\n // would misroute telemetry with no signal to the host app.\n const endpoint = opts.endpoint || DEFAULT_INGEST_ENDPOINT;\n const origin = safeOrigin(endpoint);\n if (origin === null) {\n throw new Error(`Neatlogs: invalid endpoint ${JSON.stringify(endpoint)}`);\n }\n this.baseUrl = origin;\n this.enabled = opts.enabled !== false;\n this.endUserId = opts.endUserId;\n this.endUserMetadata = opts.endUserMetadata;\n this.sessionId = opts.sessionId;\n this.onError =\n opts.onError ||\n ((err) => {\n // eslint-disable-next-line no-console\n if (typeof console !== \"undefined\") console.warn(\"[neatlogs] send failed:\", err);\n });\n }\n\n /** Send a full (optionally nested) trace. Returns the backend's result. */\n async trace(trace: NeatlogsTrace): Promise<TrackResult> {\n return this.post(trace);\n }\n\n /** Send a single AI interaction as a one-span trace (kind defaults to LLM). */\n async trackAI(ai: TrackAIInput): Promise<TrackResult> {\n const { name, kind, ...rest } = ai;\n return this.post({ name, kind: kind ?? \"LLM\", ...rest });\n }\n\n /**\n * Begin a trace you'll complete later (e.g. streaming). Buffers the partial\n * input; call `.finish()` with the final output/tokens to send it. Nothing is\n * sent until `finish()`.\n */\n startTrace(initial: TrackAIInput): {\n finish: (final?: Partial<TrackAIInput>) => Promise<TrackResult>;\n } {\n const startedAt = nowIso();\n return {\n finish: (final?: Partial<TrackAIInput>) => {\n const merged: TrackAIInput = { ...initial, ...(final ?? {}) };\n const { name, kind, ...rest } = merged;\n return this.post({\n name,\n kind: kind ?? \"LLM\",\n start: startedAt,\n end: nowIso(),\n ...rest,\n });\n },\n };\n }\n\n /**\n * Fold identity (end-user + session) onto the trace ROOT as canonical\n * attributes, and strip the convenience `endUserId`/`endUserMetadata`/`sessionId`\n * fields so they aren't sent as raw root fields. Per-call values win over the\n * client defaults; explicit `attributes` win over both. Only the root carries\n * identity — one end-user per trace, one session per trace.\n */\n private applyIdentity(body: NeatlogsTrace): NeatlogsTrace {\n const { endUserId, endUserMetadata, sessionId, ...rest } = body as NeatlogsTrace & {\n endUserId?: string;\n endUserMetadata?: Record<string, unknown>;\n sessionId?: string;\n };\n const id = endUserId ?? this.endUserId;\n const meta = endUserMetadata ?? this.endUserMetadata;\n const session = sessionId ?? this.sessionId;\n if (id === undefined && meta === undefined && session === undefined) {\n return rest;\n }\n\n const attributes: Record<string, unknown> = { ...(rest.attributes ?? {}) };\n if (id !== undefined && attributes[END_USER_ID_KEY] === undefined) {\n attributes[END_USER_ID_KEY] = String(id);\n }\n if (meta !== undefined && attributes[END_USER_METADATA_KEY] === undefined) {\n attributes[END_USER_METADATA_KEY] =\n typeof meta === \"string\" ? meta : JSON.stringify(meta);\n }\n if (session !== undefined && attributes[SESSION_ID_KEY] === undefined) {\n attributes[SESSION_ID_KEY] = String(session);\n }\n return { ...rest, attributes };\n }\n\n /** POST the trace JSON to the backend's /v1/trace endpoint. */\n private async post(body: NeatlogsTrace): Promise<TrackResult> {\n const httpKindPath = findHttpKind(body);\n if (httpKindPath) {\n const error = `HTTP spans are not supported (${httpKindPath}). Trace the semantic AI operation instead.`;\n this.onError(new Error(error));\n return { ok: false, error };\n }\n if (!this.enabled) return { ok: true };\n // Fold identity (end-user + session) onto the root before anything else.\n body = this.applyIdentity(body);\n // Inject the configured project name into the root (required for write keys;\n // ignored server-side for full project keys). A `project` already on the body wins.\n const payload =\n this.project && (body as any).project === undefined\n ? { ...body, project: this.project }\n : body;\n try {\n const res = await fetch(`${this.baseUrl}/v1/trace`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n // keepalive lets the request survive a page unload (e.g. on navigation).\n keepalive: true,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const result = { ok: false, error: `HTTP ${res.status}${text ? `: ${text}` : \"\"}` };\n this.onError(new Error(result.error));\n return result;\n }\n const data = (await res.json().catch(() => ({}))) as Partial<TrackResult>;\n return { ok: true, trace_id: data.trace_id, spans: data.spans };\n } catch (err) {\n // Never throw into the host app over telemetry.\n this.onError(err);\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n// --- helpers (kept local, no imports) -----------------------------------------\n\nfunction safeOrigin(endpoint: string): string | null {\n try {\n return new URL(endpoint).origin;\n } catch {\n return null;\n }\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction findHttpKind(span: NeatlogsSpan, path = 'root'): string | null {\n const kindCandidates = [\n span.kind,\n span.attributes?.['neatlogs.span.kind'],\n span.attributes?.['openinference.span.kind'],\n ];\n if (kindCandidates.some((value) => String(value ?? '').trim().toUpperCase() === 'HTTP')) {\n return path;\n }\n for (let index = 0; index < (span.children?.length ?? 0); index += 1) {\n const childPath = findHttpKind(span.children![index], `${path}.children[${index}]`);\n if (childPath) return childPath;\n }\n return null;\n}\n\nexport default Neatlogs;\n","/** Stable SDK-wide defaults shared by every NeatLogs entry point. */\nexport const DEFAULT_INGEST_ENDPOINT = 'https://ingest.neatlogs.com' as const;\nexport const DEFAULT_MAX_QUEUE_ITEMS = 2048;\nexport const DEFAULT_MAX_SEMANTIC_STREAM_EVENTS = 128;\nexport const DEFAULT_MAX_STREAM_CAPTURE_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_STREAM_CAPTURE_ITEMS = 1024;\n\n/** UTF-8 byte length without allocating a second buffer for hostile stream chunks. */\nexport function utf8ByteLength(\n value: string,\n limit = Number.MAX_SAFE_INTEGER,\n): number {\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length) {\n const next = value.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n bytes += 4;\n i += 1;\n } else bytes += 3;\n } else bytes += 3;\n if (bytes > limit) return limit + 1;\n }\n return bytes;\n}\n\nexport function exportQueueCapacity(batchSize: number): number {\n return Math.max(DEFAULT_MAX_QUEUE_ITEMS, batchSize * 4);\n}\n"],"mappings":"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","names":[]}
|
package/dist/browser.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ interface NeatlogsLog {
|
|
|
35
35
|
* Span kinds the backend accepts (the canonical set). `kind` is optional — when
|
|
36
36
|
* omitted the backend infers it from the fields present.
|
|
37
37
|
*/
|
|
38
|
-
type NeatlogsKind = "WORKFLOW" | "AGENT" | "CHAIN" | "TOOL" | "RETRIEVER" | "RERANKER" | "EMBEDDING" | "LLM" | "GUARDRAIL" | "MCP_TOOL" | "TASK" | "VECTOR_STORE" | "EVALUATOR"
|
|
38
|
+
type NeatlogsKind = "WORKFLOW" | "AGENT" | "CHAIN" | "TOOL" | "RETRIEVER" | "RERANKER" | "EMBEDDING" | "LLM" | "GUARDRAIL" | "MCP_TOOL" | "TASK" | "VECTOR_STORE" | "EVALUATOR";
|
|
39
39
|
interface NeatlogsSpan {
|
|
40
40
|
name: string;
|
|
41
41
|
/** Optional — the backend infers the kind from fields when omitted. */
|
package/dist/browser.mjs
CHANGED
|
@@ -22,7 +22,11 @@ var Neatlogs = class {
|
|
|
22
22
|
this.apiKey = opts.apiKey;
|
|
23
23
|
this.project = opts.project;
|
|
24
24
|
const endpoint = opts.endpoint || DEFAULT_INGEST_ENDPOINT;
|
|
25
|
-
|
|
25
|
+
const origin = safeOrigin(endpoint);
|
|
26
|
+
if (origin === null) {
|
|
27
|
+
throw new Error(`Neatlogs: invalid endpoint ${JSON.stringify(endpoint)}`);
|
|
28
|
+
}
|
|
29
|
+
this.baseUrl = origin;
|
|
26
30
|
this.enabled = opts.enabled !== false;
|
|
27
31
|
this.endUserId = opts.endUserId;
|
|
28
32
|
this.endUserMetadata = opts.endUserMetadata;
|
|
@@ -90,6 +94,12 @@ var Neatlogs = class {
|
|
|
90
94
|
}
|
|
91
95
|
/** POST the trace JSON to the backend's /v1/trace endpoint. */
|
|
92
96
|
async post(body) {
|
|
97
|
+
const httpKindPath = findHttpKind(body);
|
|
98
|
+
if (httpKindPath) {
|
|
99
|
+
const error = `HTTP spans are not supported (${httpKindPath}). Trace the semantic AI operation instead.`;
|
|
100
|
+
this.onError(new Error(error));
|
|
101
|
+
return { ok: false, error };
|
|
102
|
+
}
|
|
93
103
|
if (!this.enabled) return { ok: true };
|
|
94
104
|
body = this.applyIdentity(body);
|
|
95
105
|
const payload = this.project && body.project === void 0 ? { ...body, project: this.project } : body;
|
|
@@ -128,6 +138,21 @@ function safeOrigin(endpoint) {
|
|
|
128
138
|
function nowIso() {
|
|
129
139
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
130
140
|
}
|
|
141
|
+
function findHttpKind(span, path = "root") {
|
|
142
|
+
const kindCandidates = [
|
|
143
|
+
span.kind,
|
|
144
|
+
span.attributes?.["neatlogs.span.kind"],
|
|
145
|
+
span.attributes?.["openinference.span.kind"]
|
|
146
|
+
];
|
|
147
|
+
if (kindCandidates.some((value) => String(value ?? "").trim().toUpperCase() === "HTTP")) {
|
|
148
|
+
return path;
|
|
149
|
+
}
|
|
150
|
+
for (let index = 0; index < (span.children?.length ?? 0); index += 1) {
|
|
151
|
+
const childPath = findHttpKind(span.children[index], `${path}.children[${index}]`);
|
|
152
|
+
if (childPath) return childPath;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
131
156
|
var browser_default = Neatlogs;
|
|
132
157
|
export {
|
|
133
158
|
Neatlogs,
|
package/dist/browser.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/browser.ts"],"sourcesContent":["/** Stable SDK-wide defaults shared by every NeatLogs entry point. */\nexport const DEFAULT_INGEST_ENDPOINT = 'https://ingest.neatlogs.com' as const;\nexport const DEFAULT_MAX_QUEUE_ITEMS = 2048;\nexport const DEFAULT_MAX_SEMANTIC_STREAM_EVENTS = 128;\nexport const DEFAULT_MAX_STREAM_CAPTURE_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_STREAM_CAPTURE_ITEMS = 1024;\n\n/** UTF-8 byte length without allocating a second buffer for hostile stream chunks. */\nexport function utf8ByteLength(\n value: string,\n limit = Number.MAX_SAFE_INTEGER,\n): number {\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length) {\n const next = value.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n bytes += 4;\n i += 1;\n } else bytes += 3;\n } else bytes += 3;\n if (bytes > limit) return limit + 1;\n }\n return bytes;\n}\n\nexport function exportQueueCapacity(batchSize: number): number {\n return Math.max(DEFAULT_MAX_QUEUE_ITEMS, batchSize * 4);\n}\n","/**\n * Neatlogs Browser SDK — `neatlogs/browser`\n *\n * A minimal, browser-safe client for sending traces to Neatlogs from web apps.\n * It has ZERO dependencies (no OpenTelemetry, no Node APIs) — only `fetch` — so\n * it bundles cleanly into front-end apps. It POSTs plain JSON to the backend's\n * simple trace endpoint (`/v1/trace`); the backend generates trace/span ids,\n * builds the hierarchy from nesting, infers cost from model+tokens, and pushes\n * through the normal pipeline. Nothing here streams OTLP.\n *\n * Usage:\n * import { Neatlogs } from 'neatlogs/browser';\n * const nl = new Neatlogs({ apiKey: 'nl_...' });\n *\n * // one-shot AI interaction\n * await nl.trackAI({ name: 'chat', model: 'gpt-4o', input, output,\n * tokens: { prompt: 10, completion: 5 } });\n *\n * // a full nested trace (same shape the backend's POST /v1/trace accepts)\n * await nl.trace({ name: 'support-chat', children: [\n * { name: 'retrieve', query, documents },\n * { name: 'answer', model: 'gpt-4o', input, output },\n * ]});\n *\n * // streaming: open, accumulate, finish\n * const t = nl.startTrace({ name: 'chat', model: 'gpt-4o', input });\n * t.finish({ output: full, tokens: { prompt, completion } });\n */\n\nimport { DEFAULT_INGEST_ENDPOINT } from './constants.js';\n\n// Canonical identity attribute keys (inlined — this file stays dependency-free).\nconst END_USER_ID_KEY = \"neatlogs.end_user.id\";\nconst END_USER_METADATA_KEY = \"neatlogs.end_user.metadata\";\nconst SESSION_ID_KEY = \"neatlogs.session.id\";\n\n// --- the simple trace shape the backend (/v1/trace) accepts --------------------\n// Mirrors the server-side SimpleSpan; kept local so this file has no imports.\n\nexport interface NeatlogsLog {\n level?: string;\n message: string;\n timestamp?: string;\n}\n\n/**\n * Span kinds the backend accepts (the canonical set). `kind` is optional — when\n * omitted the backend infers it from the fields present.\n */\nexport type NeatlogsKind =\n | \"WORKFLOW\" | \"AGENT\" | \"CHAIN\" | \"TOOL\" | \"RETRIEVER\" | \"RERANKER\"\n | \"EMBEDDING\" | \"LLM\" | \"GUARDRAIL\" | \"MCP_TOOL\" | \"TASK\"\n | \"VECTOR_STORE\" | \"EVALUATOR\" | \"HTTP\";\n\nexport interface NeatlogsSpan {\n name: string;\n /** Optional — the backend infers the kind from fields when omitted. */\n kind?: NeatlogsKind | string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n query?: unknown;\n documents?: unknown;\n tool_name?: string;\n passed?: boolean;\n score?: number;\n metadata?: Record<string, unknown>;\n status?: string;\n error?: string;\n start?: string;\n end?: string;\n /** Simplest way to record latency — the backend derives end from start + this. */\n duration_ms?: number;\n /**\n * Full canonical-attribute escape hatch. Send ANY neatlogs.* attribute the SDK\n * supports — e.g. { \"neatlogs.llm.temperature\": 0.7, \"neatlogs.agent.role\":\n * \"researcher\", \"neatlogs.tool.parameters\": {...} }. Non-canonical keys are\n * dropped server-side. The fields above (model/tokens/query/...) are shortcuts\n * for the common ones; explicit `attributes` win on conflict.\n */\n attributes?: Record<string, unknown>;\n children?: NeatlogsSpan[];\n logs?: NeatlogsLog[];\n /**\n * END-USER this trace belongs to — only meaningful on the ROOT of a trace\n * (overrides the client default). One end-user per trace; the backend rolls it\n * up to the trace and its session. Ignored on child spans.\n */\n endUserId?: string;\n /** Arbitrary end-user fields for the trace root (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * SESSION this trace belongs to — the conversation/thread grouping many turns.\n * Only meaningful on the ROOT of a trace (overrides the client default). Every\n * trace sharing a `sessionId` is grouped into one session in the dashboard; a new\n * trace per turn, all with the same `sessionId`, forms a multi-turn conversation.\n * Ignored on child spans.\n */\n sessionId?: string;\n}\n\n/** The root of a trace = a span node (its `name` becomes the workflow name). */\nexport type NeatlogsTrace = NeatlogsSpan;\n\nexport interface NeatlogsOptions {\n /**\n * Your Neatlogs WRITE key (`nlw_…`) — an ingest-only credential safe to embed in\n * browser code. (A full project key also works but should not be exposed client-side,\n * since it can read data.)\n */\n apiKey: string;\n /**\n * Project NAME to ingest into. REQUIRED when using a write key (the key identifies\n * you, not a project). Sent as the root `project` field on every trace. Ignored for\n * a full project key (already project-scoped).\n */\n project?: string;\n /** Backend base URL. Defaults to the same host the SDKs use. */\n endpoint?: string;\n /** Set false to validate calls without sending (default true). */\n enabled?: boolean;\n /** Called on transport errors instead of throwing (default: console.warn). */\n onError?: (err: unknown) => void;\n /**\n * Default END-USER identity for every trace this client sends — the user of\n * your app, not the operator. One end-user per trace; the backend rolls it up\n * to the trace and its session. A per-call `endUserId` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUserId })`.\n */\n endUserId?: string;\n /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * Default SESSION for every trace this client sends — the conversation/thread\n * these traces belong to. A per-call `sessionId` (on trace()/trackAI()) overrides\n * this. Set it once per conversation, e.g. `new Neatlogs({ apiKey, sessionId: convId })`.\n */\n sessionId?: string;\n}\n\nexport interface TrackResult {\n ok: boolean;\n trace_id?: string;\n spans?: number;\n error?: string;\n}\n\n/** Shorthand for a single AI interaction → a one-span trace. */\nexport interface TrackAIInput {\n name: string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n metadata?: Record<string, unknown>;\n duration_ms?: number;\n /** Any canonical neatlogs.* attributes (see NeatlogsSpan.attributes). */\n attributes?: Record<string, unknown>;\n /** Override the inferred kind (defaults to LLM for trackAI). */\n kind?: NeatlogsKind | string;\n /** END-USER for this trace (overrides the client default). One per trace. */\n endUserId?: string;\n /** Arbitrary end-user fields for this trace (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /** SESSION this trace belongs to (overrides the client default). */\n sessionId?: string;\n}\n\nexport class Neatlogs {\n private readonly apiKey: string;\n private readonly project?: string;\n private readonly baseUrl: string;\n private readonly enabled: boolean;\n private readonly onError: (err: unknown) => void;\n private readonly endUserId?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\n private readonly sessionId?: string;\n\n constructor(opts: NeatlogsOptions) {\n if (!opts || !opts.apiKey) {\n throw new Error(\"Neatlogs: apiKey is required\");\n }\n this.apiKey = opts.apiKey;\n this.project = opts.project;\n // Use the origin of the configured endpoint (same convention as the Node SDK),\n // so passing a full /v1/traces URL or a bare host both work.\n const endpoint = opts.endpoint || DEFAULT_INGEST_ENDPOINT;\n this.baseUrl = safeOrigin(endpoint) || DEFAULT_INGEST_ENDPOINT;\n this.enabled = opts.enabled !== false;\n this.endUserId = opts.endUserId;\n this.endUserMetadata = opts.endUserMetadata;\n this.sessionId = opts.sessionId;\n this.onError =\n opts.onError ||\n ((err) => {\n // eslint-disable-next-line no-console\n if (typeof console !== \"undefined\") console.warn(\"[neatlogs] send failed:\", err);\n });\n }\n\n /** Send a full (optionally nested) trace. Returns the backend's result. */\n async trace(trace: NeatlogsTrace): Promise<TrackResult> {\n return this.post(trace);\n }\n\n /** Send a single AI interaction as a one-span trace (kind defaults to LLM). */\n async trackAI(ai: TrackAIInput): Promise<TrackResult> {\n const { name, kind, ...rest } = ai;\n return this.post({ name, kind: kind ?? \"LLM\", ...rest });\n }\n\n /**\n * Begin a trace you'll complete later (e.g. streaming). Buffers the partial\n * input; call `.finish()` with the final output/tokens to send it. Nothing is\n * sent until `finish()`.\n */\n startTrace(initial: TrackAIInput): {\n finish: (final?: Partial<TrackAIInput>) => Promise<TrackResult>;\n } {\n const startedAt = nowIso();\n return {\n finish: (final?: Partial<TrackAIInput>) => {\n const merged: TrackAIInput = { ...initial, ...(final ?? {}) };\n const { name, kind, ...rest } = merged;\n return this.post({\n name,\n kind: kind ?? \"LLM\",\n start: startedAt,\n end: nowIso(),\n ...rest,\n });\n },\n };\n }\n\n /**\n * Fold identity (end-user + session) onto the trace ROOT as canonical\n * attributes, and strip the convenience `endUserId`/`endUserMetadata`/`sessionId`\n * fields so they aren't sent as raw root fields. Per-call values win over the\n * client defaults; explicit `attributes` win over both. Only the root carries\n * identity — one end-user per trace, one session per trace.\n */\n private applyIdentity(body: NeatlogsTrace): NeatlogsTrace {\n const { endUserId, endUserMetadata, sessionId, ...rest } = body as NeatlogsTrace & {\n endUserId?: string;\n endUserMetadata?: Record<string, unknown>;\n sessionId?: string;\n };\n const id = endUserId ?? this.endUserId;\n const meta = endUserMetadata ?? this.endUserMetadata;\n const session = sessionId ?? this.sessionId;\n if (id === undefined && meta === undefined && session === undefined) {\n return rest;\n }\n\n const attributes: Record<string, unknown> = { ...(rest.attributes ?? {}) };\n if (id !== undefined && attributes[END_USER_ID_KEY] === undefined) {\n attributes[END_USER_ID_KEY] = String(id);\n }\n if (meta !== undefined && attributes[END_USER_METADATA_KEY] === undefined) {\n attributes[END_USER_METADATA_KEY] =\n typeof meta === \"string\" ? meta : JSON.stringify(meta);\n }\n if (session !== undefined && attributes[SESSION_ID_KEY] === undefined) {\n attributes[SESSION_ID_KEY] = String(session);\n }\n return { ...rest, attributes };\n }\n\n /** POST the trace JSON to the backend's /v1/trace endpoint. */\n private async post(body: NeatlogsTrace): Promise<TrackResult> {\n if (!this.enabled) return { ok: true };\n // Fold identity (end-user + session) onto the root before anything else.\n body = this.applyIdentity(body);\n // Inject the configured project name into the root (required for write keys;\n // ignored server-side for full project keys). A `project` already on the body wins.\n const payload =\n this.project && (body as any).project === undefined\n ? { ...body, project: this.project }\n : body;\n try {\n const res = await fetch(`${this.baseUrl}/v1/trace`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n // keepalive lets the request survive a page unload (e.g. on navigation).\n keepalive: true,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const result = { ok: false, error: `HTTP ${res.status}${text ? `: ${text}` : \"\"}` };\n this.onError(new Error(result.error));\n return result;\n }\n const data = (await res.json().catch(() => ({}))) as Partial<TrackResult>;\n return { ok: true, trace_id: data.trace_id, spans: data.spans };\n } catch (err) {\n // Never throw into the host app over telemetry.\n this.onError(err);\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n// --- helpers (kept local, no imports) -----------------------------------------\n\nfunction safeOrigin(endpoint: string): string | null {\n try {\n return new URL(endpoint).origin;\n } catch {\n return null;\n }\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nexport default Neatlogs;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/browser.ts"],"sourcesContent":["/** Stable SDK-wide defaults shared by every NeatLogs entry point. */\nexport const DEFAULT_INGEST_ENDPOINT = 'https://ingest.neatlogs.com' as const;\nexport const DEFAULT_MAX_QUEUE_ITEMS = 2048;\nexport const DEFAULT_MAX_SEMANTIC_STREAM_EVENTS = 128;\nexport const DEFAULT_MAX_STREAM_CAPTURE_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_STREAM_CAPTURE_ITEMS = 1024;\n\n/** UTF-8 byte length without allocating a second buffer for hostile stream chunks. */\nexport function utf8ByteLength(\n value: string,\n limit = Number.MAX_SAFE_INTEGER,\n): number {\n let bytes = 0;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length) {\n const next = value.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n bytes += 4;\n i += 1;\n } else bytes += 3;\n } else bytes += 3;\n if (bytes > limit) return limit + 1;\n }\n return bytes;\n}\n\nexport function exportQueueCapacity(batchSize: number): number {\n return Math.max(DEFAULT_MAX_QUEUE_ITEMS, batchSize * 4);\n}\n","/**\n * Neatlogs Browser SDK — `neatlogs/browser`\n *\n * A minimal, browser-safe client for sending traces to Neatlogs from web apps.\n * It has ZERO dependencies (no OpenTelemetry, no Node APIs) — only `fetch` — so\n * it bundles cleanly into front-end apps. It POSTs plain JSON to the backend's\n * simple trace endpoint (`/v1/trace`); the backend generates trace/span ids,\n * builds the hierarchy from nesting, infers cost from model+tokens, and pushes\n * through the normal pipeline. Nothing here streams OTLP.\n *\n * Usage:\n * import { Neatlogs } from 'neatlogs/browser';\n * const nl = new Neatlogs({ apiKey: 'nl_...' });\n *\n * // one-shot AI interaction\n * await nl.trackAI({ name: 'chat', model: 'gpt-4o', input, output,\n * tokens: { prompt: 10, completion: 5 } });\n *\n * // a full nested trace (same shape the backend's POST /v1/trace accepts)\n * await nl.trace({ name: 'support-chat', children: [\n * { name: 'retrieve', query, documents },\n * { name: 'answer', model: 'gpt-4o', input, output },\n * ]});\n *\n * // streaming: open, accumulate, finish\n * const t = nl.startTrace({ name: 'chat', model: 'gpt-4o', input });\n * t.finish({ output: full, tokens: { prompt, completion } });\n */\n\nimport { DEFAULT_INGEST_ENDPOINT } from './constants.js';\n\n// Canonical identity attribute keys (inlined — this file stays dependency-free).\nconst END_USER_ID_KEY = \"neatlogs.end_user.id\";\nconst END_USER_METADATA_KEY = \"neatlogs.end_user.metadata\";\nconst SESSION_ID_KEY = \"neatlogs.session.id\";\n\n// --- the simple trace shape the backend (/v1/trace) accepts --------------------\n// Mirrors the server-side SimpleSpan; kept local so this file has no imports.\n\nexport interface NeatlogsLog {\n level?: string;\n message: string;\n timestamp?: string;\n}\n\n/**\n * Span kinds the backend accepts (the canonical set). `kind` is optional — when\n * omitted the backend infers it from the fields present.\n */\nexport type NeatlogsKind =\n | \"WORKFLOW\" | \"AGENT\" | \"CHAIN\" | \"TOOL\" | \"RETRIEVER\" | \"RERANKER\"\n | \"EMBEDDING\" | \"LLM\" | \"GUARDRAIL\" | \"MCP_TOOL\" | \"TASK\"\n | \"VECTOR_STORE\" | \"EVALUATOR\";\n\nexport interface NeatlogsSpan {\n name: string;\n /** Optional — the backend infers the kind from fields when omitted. */\n kind?: NeatlogsKind | string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n query?: unknown;\n documents?: unknown;\n tool_name?: string;\n passed?: boolean;\n score?: number;\n metadata?: Record<string, unknown>;\n status?: string;\n error?: string;\n start?: string;\n end?: string;\n /** Simplest way to record latency — the backend derives end from start + this. */\n duration_ms?: number;\n /**\n * Full canonical-attribute escape hatch. Send ANY neatlogs.* attribute the SDK\n * supports — e.g. { \"neatlogs.llm.temperature\": 0.7, \"neatlogs.agent.role\":\n * \"researcher\", \"neatlogs.tool.parameters\": {...} }. Non-canonical keys are\n * dropped server-side. The fields above (model/tokens/query/...) are shortcuts\n * for the common ones; explicit `attributes` win on conflict.\n */\n attributes?: Record<string, unknown>;\n children?: NeatlogsSpan[];\n logs?: NeatlogsLog[];\n /**\n * END-USER this trace belongs to — only meaningful on the ROOT of a trace\n * (overrides the client default). One end-user per trace; the backend rolls it\n * up to the trace and its session. Ignored on child spans.\n */\n endUserId?: string;\n /** Arbitrary end-user fields for the trace root (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * SESSION this trace belongs to — the conversation/thread grouping many turns.\n * Only meaningful on the ROOT of a trace (overrides the client default). Every\n * trace sharing a `sessionId` is grouped into one session in the dashboard; a new\n * trace per turn, all with the same `sessionId`, forms a multi-turn conversation.\n * Ignored on child spans.\n */\n sessionId?: string;\n}\n\n/** The root of a trace = a span node (its `name` becomes the workflow name). */\nexport type NeatlogsTrace = NeatlogsSpan;\n\nexport interface NeatlogsOptions {\n /**\n * Your Neatlogs WRITE key (`nlw_…`) — an ingest-only credential safe to embed in\n * browser code. (A full project key also works but should not be exposed client-side,\n * since it can read data.)\n */\n apiKey: string;\n /**\n * Project NAME to ingest into. REQUIRED when using a write key (the key identifies\n * you, not a project). Sent as the root `project` field on every trace. Ignored for\n * a full project key (already project-scoped).\n */\n project?: string;\n /** Backend base URL. Defaults to the same host the SDKs use. */\n endpoint?: string;\n /** Set false to validate calls without sending (default true). */\n enabled?: boolean;\n /** Called on transport errors instead of throwing (default: console.warn). */\n onError?: (err: unknown) => void;\n /**\n * Default END-USER identity for every trace this client sends — the user of\n * your app, not the operator. One end-user per trace; the backend rolls it up\n * to the trace and its session. A per-call `endUserId` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUserId })`.\n */\n endUserId?: string;\n /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */\n endUserMetadata?: Record<string, unknown>;\n /**\n * Default SESSION for every trace this client sends — the conversation/thread\n * these traces belong to. A per-call `sessionId` (on trace()/trackAI()) overrides\n * this. Set it once per conversation, e.g. `new Neatlogs({ apiKey, sessionId: convId })`.\n */\n sessionId?: string;\n}\n\nexport interface TrackResult {\n ok: boolean;\n trace_id?: string;\n spans?: number;\n error?: string;\n}\n\n/** Shorthand for a single AI interaction → a one-span trace. */\nexport interface TrackAIInput {\n name: string;\n input?: unknown;\n output?: unknown;\n model?: string;\n tokens?: { prompt?: number; completion?: number; total?: number };\n metadata?: Record<string, unknown>;\n duration_ms?: number;\n /** Any canonical neatlogs.* attributes (see NeatlogsSpan.attributes). */\n attributes?: Record<string, unknown>;\n /** Override the inferred kind (defaults to LLM for trackAI). */\n kind?: NeatlogsKind | string;\n /** END-USER for this trace (overrides the client default). One per trace. */\n endUserId?: string;\n /** Arbitrary end-user fields for this trace (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\n /** SESSION this trace belongs to (overrides the client default). */\n sessionId?: string;\n}\n\nexport class Neatlogs {\n private readonly apiKey: string;\n private readonly project?: string;\n private readonly baseUrl: string;\n private readonly enabled: boolean;\n private readonly onError: (err: unknown) => void;\n private readonly endUserId?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\n private readonly sessionId?: string;\n\n constructor(opts: NeatlogsOptions) {\n if (!opts || !opts.apiKey) {\n throw new Error(\"Neatlogs: apiKey is required\");\n }\n this.apiKey = opts.apiKey;\n this.project = opts.project;\n // Use the origin of the configured endpoint (same convention as the Node SDK),\n // so passing a full /v1/traces URL or a bare host both work. A malformed\n // endpoint throws instead of silently falling back to prod ingest, which\n // would misroute telemetry with no signal to the host app.\n const endpoint = opts.endpoint || DEFAULT_INGEST_ENDPOINT;\n const origin = safeOrigin(endpoint);\n if (origin === null) {\n throw new Error(`Neatlogs: invalid endpoint ${JSON.stringify(endpoint)}`);\n }\n this.baseUrl = origin;\n this.enabled = opts.enabled !== false;\n this.endUserId = opts.endUserId;\n this.endUserMetadata = opts.endUserMetadata;\n this.sessionId = opts.sessionId;\n this.onError =\n opts.onError ||\n ((err) => {\n // eslint-disable-next-line no-console\n if (typeof console !== \"undefined\") console.warn(\"[neatlogs] send failed:\", err);\n });\n }\n\n /** Send a full (optionally nested) trace. Returns the backend's result. */\n async trace(trace: NeatlogsTrace): Promise<TrackResult> {\n return this.post(trace);\n }\n\n /** Send a single AI interaction as a one-span trace (kind defaults to LLM). */\n async trackAI(ai: TrackAIInput): Promise<TrackResult> {\n const { name, kind, ...rest } = ai;\n return this.post({ name, kind: kind ?? \"LLM\", ...rest });\n }\n\n /**\n * Begin a trace you'll complete later (e.g. streaming). Buffers the partial\n * input; call `.finish()` with the final output/tokens to send it. Nothing is\n * sent until `finish()`.\n */\n startTrace(initial: TrackAIInput): {\n finish: (final?: Partial<TrackAIInput>) => Promise<TrackResult>;\n } {\n const startedAt = nowIso();\n return {\n finish: (final?: Partial<TrackAIInput>) => {\n const merged: TrackAIInput = { ...initial, ...(final ?? {}) };\n const { name, kind, ...rest } = merged;\n return this.post({\n name,\n kind: kind ?? \"LLM\",\n start: startedAt,\n end: nowIso(),\n ...rest,\n });\n },\n };\n }\n\n /**\n * Fold identity (end-user + session) onto the trace ROOT as canonical\n * attributes, and strip the convenience `endUserId`/`endUserMetadata`/`sessionId`\n * fields so they aren't sent as raw root fields. Per-call values win over the\n * client defaults; explicit `attributes` win over both. Only the root carries\n * identity — one end-user per trace, one session per trace.\n */\n private applyIdentity(body: NeatlogsTrace): NeatlogsTrace {\n const { endUserId, endUserMetadata, sessionId, ...rest } = body as NeatlogsTrace & {\n endUserId?: string;\n endUserMetadata?: Record<string, unknown>;\n sessionId?: string;\n };\n const id = endUserId ?? this.endUserId;\n const meta = endUserMetadata ?? this.endUserMetadata;\n const session = sessionId ?? this.sessionId;\n if (id === undefined && meta === undefined && session === undefined) {\n return rest;\n }\n\n const attributes: Record<string, unknown> = { ...(rest.attributes ?? {}) };\n if (id !== undefined && attributes[END_USER_ID_KEY] === undefined) {\n attributes[END_USER_ID_KEY] = String(id);\n }\n if (meta !== undefined && attributes[END_USER_METADATA_KEY] === undefined) {\n attributes[END_USER_METADATA_KEY] =\n typeof meta === \"string\" ? meta : JSON.stringify(meta);\n }\n if (session !== undefined && attributes[SESSION_ID_KEY] === undefined) {\n attributes[SESSION_ID_KEY] = String(session);\n }\n return { ...rest, attributes };\n }\n\n /** POST the trace JSON to the backend's /v1/trace endpoint. */\n private async post(body: NeatlogsTrace): Promise<TrackResult> {\n const httpKindPath = findHttpKind(body);\n if (httpKindPath) {\n const error = `HTTP spans are not supported (${httpKindPath}). Trace the semantic AI operation instead.`;\n this.onError(new Error(error));\n return { ok: false, error };\n }\n if (!this.enabled) return { ok: true };\n // Fold identity (end-user + session) onto the root before anything else.\n body = this.applyIdentity(body);\n // Inject the configured project name into the root (required for write keys;\n // ignored server-side for full project keys). A `project` already on the body wins.\n const payload =\n this.project && (body as any).project === undefined\n ? { ...body, project: this.project }\n : body;\n try {\n const res = await fetch(`${this.baseUrl}/v1/trace`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(payload),\n // keepalive lets the request survive a page unload (e.g. on navigation).\n keepalive: true,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const result = { ok: false, error: `HTTP ${res.status}${text ? `: ${text}` : \"\"}` };\n this.onError(new Error(result.error));\n return result;\n }\n const data = (await res.json().catch(() => ({}))) as Partial<TrackResult>;\n return { ok: true, trace_id: data.trace_id, spans: data.spans };\n } catch (err) {\n // Never throw into the host app over telemetry.\n this.onError(err);\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n// --- helpers (kept local, no imports) -----------------------------------------\n\nfunction safeOrigin(endpoint: string): string | null {\n try {\n return new URL(endpoint).origin;\n } catch {\n return null;\n }\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction findHttpKind(span: NeatlogsSpan, path = 'root'): string | null {\n const kindCandidates = [\n span.kind,\n span.attributes?.['neatlogs.span.kind'],\n span.attributes?.['openinference.span.kind'],\n ];\n if (kindCandidates.some((value) => String(value ?? '').trim().toUpperCase() === 'HTTP')) {\n return path;\n }\n for (let index = 0; index < (span.children?.length ?? 0); index += 1) {\n const childPath = findHttpKind(span.children![index], `${path}.children[${index}]`);\n if (childPath) return childPath;\n }\n return null;\n}\n\nexport default Neatlogs;\n"],"mappings":"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","names":[]}
|
|
@@ -73,11 +73,11 @@ function getNeatlogsActiveContext() {
|
|
|
73
73
|
function getNeatlogsBaseContext(baseContext) {
|
|
74
74
|
return baseContext ?? getNeatlogsActiveContext();
|
|
75
75
|
}
|
|
76
|
-
function withNeatlogsSpan(span, fn, baseContext) {
|
|
76
|
+
function withNeatlogsSpan(span, fn, baseContext, rootSpan) {
|
|
77
77
|
const base = baseContext ?? getNeatlogsActiveContext();
|
|
78
78
|
let ctx = import_api.trace.setSpan(base, span);
|
|
79
79
|
if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === void 0) {
|
|
80
|
-
ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);
|
|
80
|
+
ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span);
|
|
81
81
|
}
|
|
82
82
|
return privateContextStorage.run(ctx, fn);
|
|
83
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/claude-agent-sdk.ts","../src/core/provider.ts","../src/core/active-client.ts"],"sourcesContent":["/**\n * Neatlogs Claude Agent SDK integration.\n *\n * Wraps Anthropic's `@anthropic-ai/claude-agent-sdk` so every `query()` run is\n * traced. The SDK's `query()` returns an async-iterable of SDKMessages\n * (system init → assistant (text + tool_use) → user (tool_result) → … →\n * result). Each message carries `parent_tool_use_id`: null for the main\n * (orchestrator) agent, or the id of the spawning Task tool call for a subagent.\n * We translate that into a neatlogs span tree:\n *\n * AGENT claude_agent.query (orchestrator = trace root)\n * ↳ LLM orchestrator turn (one per model turn; text + tool_calls)\n * ↳ TOOL Read / Edit / Bash …\n * ↳ TOOL Task (spawns a subagent)\n * ↳ AGENT claude_agent.subagent.<type> (each subagent, nested)\n * ↳ LLM subagent turn\n * ↳ TOOL subagent tool call\n *\n * The orchestrator is the single root AGENT span — there is NO redundant WORKFLOW\n * wrapper. Subagents (Task-tool invocations, e.g. the wizard's parallel per-file\n * edits) get their own AGENT spans nested under the Task TOOL span that spawned\n * them, so a multi-agent run is represented faithfully and each agent's I/O is\n * distinct.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { wrapClaudeAgentSDK } from 'neatlogs/claude-agent-sdk';\n * import * as claudeAgentSDK from '@anthropic-ai/claude-agent-sdk';\n *\n * await init({ apiKey, workflowName });\n * const { query } = wrapClaudeAgentSDK(claudeAgentSDK);\n * for await (const msg of query({ prompt: 'Hello', options: {...} })) { ... }\n *\n * Conversation tracking: Claude's `session_id` is captured on the root AGENT span\n * as `neatlogs.conversation.id`. Tool calls are traced only through the wrapped\n * `query` — calling the unwrapped SDK directly produces no tracing.\n */\n\nimport { trace, SpanStatusCode, type Span, type Context } from '@opentelemetry/api';\nimport { getNeatlogsTracer, getNeatlogsBaseContext, withNeatlogsSpan } from './core/provider.js';\n\nconst TRACER_NAME = 'neatlogs.claude_agent_sdk';\nconst ROOT_SCOPE = '__root__';\n\nexport interface WrapClaudeAgentSDKOptions {\n /** Logical grouping for traces (also settable globally via init({ workflowName })). */\n workflowName?: string;\n}\n\n/**\n * Wrap the Claude Agent SDK module. Returns a shallow copy of the module with an\n * instrumented `query`; all other exports (createSdkMcpServer, tool, the built-in\n * Tool helpers, etc.) are passed through unchanged.\n */\nexport function wrapClaudeAgentSDK<T extends Record<string, any>>(\n sdk: T,\n options: WrapClaudeAgentSDKOptions = {},\n): T {\n if (!sdk || typeof sdk.query !== 'function') return sdk;\n if ((sdk as any)._neatlogsWrapped) return sdk;\n\n const wrapped: Record<string, any> = { ...sdk };\n wrapped.query = wrapQuery(sdk.query.bind(sdk), options);\n\n try {\n Object.defineProperty(wrapped, '_neatlogsWrapped', {\n value: true,\n enumerable: false,\n configurable: true,\n });\n } catch {\n wrapped._neatlogsWrapped = true;\n }\n\n return wrapped as T;\n}\n\n// ---------------------------------------------------------------------------\n// query() wrapping\n// ---------------------------------------------------------------------------\n\nfunction wrapQuery(original: (...args: any[]) => any, options: WrapClaudeAgentSDKOptions) {\n return function (params: any, ...rest: any[]): any {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n const workflowName = options.workflowName ?? 'claude_agent.query';\n\n // Shared ref the input-tap fills with the first user-prompt text. In\n // streaming-input mode the prompt is an async generator fed into the SDK\n // subprocess and is NOT echoed back as a `user` output message before the\n // first assistant turn — so without tapping it, the first LLM span has no\n // input. The tap reads prompt text as the SDK pulls it.\n const promptRef: { text: string } = { text: '' };\n\n // The orchestrator AGENT is the trace ROOT (no WORKFLOW wrapper — a single\n // query() is one agent run; subagents nest below as their own AGENT spans).\n const agentSpan = tracer.startSpan(\n 'claude_agent.query',\n { attributes: { 'neatlogs.span.kind': 'AGENT', 'neatlogs.workflow.name': workflowName } },\n getNeatlogsBaseContext(),\n );\n\n // Input: a string prompt is captured directly. A streaming-input prompt (an\n // async iterable) is tapped — promptRef.text fills as the SDK pulls the\n // first user message — so the agent input and the first LLM span's input are\n // populated even before any `user` message echoes back through the output.\n const promptText = extractPromptText(params?.prompt);\n if (promptText) {\n promptRef.text = promptText;\n agentSpan.setAttribute('input.value', promptText);\n } else if (params && isAsyncIterable(params.prompt)) {\n params = { ...params, prompt: tapPromptStream(params.prompt, promptRef) };\n }\n\n const agentCtx = trace.setSpan(getNeatlogsBaseContext(), agentSpan);\n\n // Call the original query with the AGENT span active only under our private\n // context so a foreign provider's spans neither parent nor nest under ours.\n const queryObj = withNeatlogsSpan(agentSpan, () => original(params, ...rest));\n\n return instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRef);\n };\n}\n\n/** True for an async iterable (streaming-input prompt). */\nfunction isAsyncIterable(v: any): boolean {\n return Boolean(v) && typeof v[Symbol.asyncIterator] === 'function';\n}\n\n/**\n * Pass-through wrapper over a streaming-input prompt that records the first\n * user message's text into `ref` as the SDK consumes it. Never alters what the\n * SDK receives.\n */\nasync function* tapPromptStream(prompt: any, ref: { text: string }): AsyncGenerator<any> {\n // userMessageText is fully defensive (returns '' for any non-text shape), so\n // no try/catch is needed — the message is always yielded through untouched.\n for await (const message of prompt) {\n if (!ref.text) {\n const text = userMessageText(message);\n if (text) ref.text = text;\n }\n yield message;\n }\n}\n\ninterface ToolCallAccum {\n id: string;\n name: string;\n input: unknown;\n}\n\ninterface AssistantTurnBuffer {\n textParts: string[];\n thinkingParts: string[];\n toolCalls: ToolCallAccum[];\n usage: any;\n model?: string;\n stopReason?: string;\n}\n\n/**\n * One agent's tracing scope. There is always a root scope (the orchestrator,\n * keyed ROOT_SCOPE). Each subagent — identified by the `parent_tool_use_id` of\n * the Task tool call that spawned it — gets its own scope created lazily, with\n * its AGENT span nested under that Task TOOL span. Per-scope state keeps each\n * agent's conversation/turn buffer independent.\n */\ninterface AgentScope {\n /** The AGENT span for this scope (root = orchestrator; others = subagents). */\n span: Span;\n /** OTel context whose active span is this scope's AGENT span (children nest here). */\n ctx: Context;\n /** Running conversation (user/tool/assistant turns) — each LLM span's input. */\n inputMessages: Array<{ role: string; content: string }>;\n /** In-progress model turn, coalesced from multiple `assistant` messages. */\n assistantBuffer: AssistantTurnBuffer | null;\n /** Last assistant text seen (subagent output = its final text). */\n finalText: string;\n /** Whether input.value has been set on this scope's AGENT span. */\n inputCaptured: boolean;\n}\n\ninterface QueryState {\n /** TOOL spans keyed by tool_use_id, closed by the matching tool_result. */\n toolSpans: Map<string, Span>;\n /** Agent scopes keyed by parent_tool_use_id (ROOT_SCOPE for the orchestrator). */\n scopes: Map<string, AgentScope>;\n sessionId?: string;\n model?: string;\n finished: boolean;\n /** Lazily-filled prompt text from a streaming-input tap. */\n promptRef: { text: string };\n}\n\n/**\n * Wrap the Query object so iteration is instrumented while preserving its own\n * methods (interrupt, setPermissionMode, …). The SDK returns an async-iterable\n * object, not a bare generator.\n */\nfunction instrumentQueryIterable(\n queryObj: any,\n agentSpan: Span,\n agentCtx: Context,\n tracer: ReturnType<typeof trace.getTracer>,\n promptRef: { text: string },\n): any {\n const originalAsyncIterator = queryObj?.[Symbol.asyncIterator]?.bind(queryObj);\n if (!originalAsyncIterator) {\n // Not iterable — nothing to trace; close the root span immediately.\n agentSpan.setStatus({ code: SpanStatusCode.OK });\n agentSpan.end();\n return queryObj;\n }\n\n // The root scope is the orchestrator agent. Seed its conversation with the\n // prompt (known up front for string prompts; filled by the tap otherwise).\n const rootScope: AgentScope = {\n span: agentSpan,\n ctx: agentCtx,\n inputMessages: promptRef.text ? [{ role: 'user', content: promptRef.text }] : [],\n assistantBuffer: null,\n finalText: '',\n inputCaptured: Boolean(promptRef.text),\n };\n const state: QueryState = {\n toolSpans: new Map(),\n scopes: new Map([[ROOT_SCOPE, rootScope]]),\n finished: false,\n promptRef,\n };\n\n const finalizeAgent = (status: 'ok' | 'error', err?: unknown) => {\n if (state.finished) return;\n state.finished = true;\n // Flush every scope's in-progress turn, then close subagent AGENT spans\n // (deepest-first) and finally the root.\n for (const scope of state.scopes.values()) flushAssistantTurn(tracer, scope, state);\n // Close any tool spans that never got a matching result.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n // Close subagent scopes first (any still open), then the root.\n for (const [key, scope] of state.scopes) {\n if (key === ROOT_SCOPE) continue;\n closeScope(scope, 'ok');\n }\n if (rootScope.finalText) agentSpan.setAttribute('output.value', rootScope.finalText);\n if (status === 'error') {\n recordError(agentSpan, err);\n } else {\n agentSpan.setStatus({ code: SpanStatusCode.OK });\n agentSpan.end();\n }\n };\n\n const wrapped = Object.create(Object.getPrototypeOf(queryObj));\n Object.assign(wrapped, queryObj);\n\n wrapped[Symbol.asyncIterator] = function () {\n const iterator = originalAsyncIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await withNeatlogsSpan(agentSpan, () => iterator.next());\n if (result.done) {\n finalizeAgent('ok');\n return result;\n }\n try {\n handleMessage(tracer, state, result.value, finalizeAgent);\n } catch {\n /* never let tracing break the run */\n }\n return result;\n } catch (err) {\n finalizeAgent('error', err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalizeAgent('ok');\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(err?: any): Promise<IteratorResult<any>> {\n finalizeAgent('error', err);\n if (iterator.throw) return iterator.throw(err);\n throw err;\n },\n };\n };\n\n return wrapped;\n}\n\n/** End a subagent scope's AGENT span, setting its output to the subagent's final text. */\nfunction closeScope(scope: AgentScope, status: 'ok' | 'error'): void {\n try {\n if (scope.finalText) scope.span.setAttribute('output.value', scope.finalText);\n scope.span.setStatus({ code: status === 'ok' ? SpanStatusCode.OK : SpanStatusCode.ERROR });\n scope.span.end();\n } catch {\n /* ignore */\n }\n}\n\n// ---------------------------------------------------------------------------\n// Message handling\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the agent scope for a message. `parent_tool_use_id` is null for the\n * orchestrator (root scope) and the spawning Task tool_use_id for a subagent.\n * Subagent scopes are created lazily, with their AGENT span nested under the\n * Task TOOL span (looked up by that id) so the hierarchy is\n * orchestrator → Task TOOL → subagent AGENT.\n */\nfunction getScope(\n tracer: ReturnType<typeof trace.getTracer>,\n state: QueryState,\n msg: any,\n): AgentScope {\n const parentId = msg?.parent_tool_use_id ?? null;\n if (!parentId) return state.scopes.get(ROOT_SCOPE)!;\n\n const existing = state.scopes.get(parentId);\n if (existing) return existing;\n\n // New subagent. Its spawning Task tool_use may still be buffered in the root\n // turn (the SDK emits subagent messages before the Task's tool_result closes\n // the parent turn). Flush the root turn first so the Task TOOL span exists and\n // this subagent AGENT can nest under it.\n const root = state.scopes.get(ROOT_SCOPE)!;\n if (!state.toolSpans.has(parentId) && root.assistantBuffer) {\n flushAssistantTurn(tracer, root, state);\n }\n\n // Nest the subagent AGENT span under the spawning Task TOOL span if we have it;\n // otherwise under the root agent.\n const parentToolSpan = state.toolSpans.get(parentId);\n const parentCtx = parentToolSpan\n ? trace.setSpan(getNeatlogsBaseContext(), parentToolSpan)\n : state.scopes.get(ROOT_SCOPE)!.ctx;\n\n const subType = msg?.subagent_type ? String(msg.subagent_type) : 'subagent';\n const attrs: Record<string, any> = {\n 'neatlogs.span.kind': 'AGENT',\n 'neatlogs.agent.name': subType,\n };\n if (msg?.task_description) attrs['input.value'] = String(msg.task_description);\n const span = tracer.startSpan(`claude_agent.subagent.${subType}`, { attributes: attrs }, parentCtx);\n\n const scope: AgentScope = {\n span,\n ctx: trace.setSpan(getNeatlogsBaseContext(), span),\n inputMessages: msg?.task_description ? [{ role: 'user', content: String(msg.task_description) }] : [],\n assistantBuffer: null,\n finalText: '',\n inputCaptured: Boolean(msg?.task_description),\n };\n state.scopes.set(parentId, scope);\n return scope;\n}\n\nfunction handleMessage(\n tracer: ReturnType<typeof trace.getTracer>,\n state: QueryState,\n msg: any,\n finalizeAgent: (status: 'ok' | 'error', err?: unknown) => void,\n): void {\n if (!msg || typeof msg !== 'object') return;\n\n const rootScope = state.scopes.get(ROOT_SCOPE)!;\n\n // Backfill root input from the tapped streaming-input prompt as soon as it's\n // available (it fills before the first assistant turn).\n if (!rootScope.inputCaptured && state.promptRef.text) {\n rootScope.inputCaptured = true;\n rootScope.span.setAttribute('input.value', state.promptRef.text);\n }\n\n switch (msg.type) {\n case 'system': {\n // init message — carries session_id, model, available tools.\n if (msg.session_id) {\n state.sessionId = msg.session_id;\n rootScope.span.setAttribute('neatlogs.conversation.id', String(msg.session_id));\n }\n if (msg.model) {\n state.model = msg.model;\n rootScope.span.setAttribute('neatlogs.agent.model', String(msg.model));\n }\n break;\n }\n\n case 'user': {\n // A `user` message (prompt, or tool_result turns) is a turn boundary for\n // its scope: flush the buffered turn as ONE LLM span first.\n const scope = getScope(tracer, state, msg);\n flushAssistantTurn(tracer, scope, state);\n\n const userText = userMessageText(msg);\n if (userText) {\n if (!scope.inputCaptured) {\n scope.inputCaptured = true;\n scope.span.setAttribute('input.value', userText);\n }\n scope.inputMessages.push({ role: 'user', content: userText });\n }\n closeToolSpansFromUser(state, scope, msg);\n break;\n }\n\n case 'assistant': {\n // Don't emit yet — the SDK delivers one model turn as multiple `assistant`\n // messages (text block, then tool_use block, …). Buffer them per scope;\n // the next user/result boundary flushes the turn as a single LLM span.\n const scope = getScope(tracer, state, msg);\n bufferAssistantMessage(scope, msg);\n break;\n }\n\n case 'result': {\n // The run is complete. Flush the root turn (a final text answer may have no\n // trailing user message), then finalize.\n flushAssistantTurn(tracer, rootScope, state);\n\n const text = typeof msg.result === 'string' ? msg.result : '';\n if (text) rootScope.finalText = text;\n if (msg.session_id && !state.sessionId) {\n rootScope.span.setAttribute('neatlogs.conversation.id', String(msg.session_id));\n }\n const usage = msg.usage;\n if (usage) setUsage(rootScope.span, usage);\n if (msg.total_cost_usd != null) rootScope.span.setAttribute('neatlogs.agent.cost_usd', msg.total_cost_usd);\n if (msg.num_turns != null) rootScope.span.setAttribute('neatlogs.agent.num_turns', msg.num_turns);\n if (msg.is_error) rootScope.span.setAttribute('neatlogs.agent.is_error', true);\n\n finalizeAgent(msg.is_error ? 'error' : 'ok', msg.is_error ? new Error(String(text || 'agent run failed')) : undefined);\n break;\n }\n\n default:\n break;\n }\n}\n\n/**\n * Append one `assistant` SDK message to the in-progress turn buffer. The SDK\n * splits a single model turn into multiple assistant messages (a text block,\n * then tool_use blocks); they share token usage. We merge their text, thinking,\n * and tool_use blocks so the turn becomes ONE LLM span on flush.\n */\nfunction bufferAssistantMessage(scope: AgentScope, msg: any): void {\n const message = msg.message ?? msg;\n const content = message?.content ?? [];\n\n if (!scope.assistantBuffer) {\n scope.assistantBuffer = { textParts: [], thinkingParts: [], toolCalls: [], usage: undefined };\n }\n const buf = scope.assistantBuffer;\n if (message?.model) buf.model = message.model;\n if (message?.stop_reason) buf.stopReason = message.stop_reason;\n // Usage is reported per assistant message but is the SAME turn total — keep\n // the largest/last non-empty one rather than summing (summing double-counts).\n if (message?.usage) buf.usage = message.usage;\n\n for (const block of Array.isArray(content) ? content : []) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') buf.textParts.push(block.text);\n else if (block.type === 'thinking' && typeof block.thinking === 'string') buf.thinkingParts.push(block.thinking);\n else if (block.type === 'tool_use') {\n buf.toolCalls.push({ id: block.id ?? '', name: block.name ?? '', input: block.input });\n }\n }\n}\n\n/**\n * Emit the buffered model turn as a SINGLE LLM span, then open TOOL spans for\n * its tool calls. No-op if no turn is buffered. This is what makes one LLM span\n * per real model turn (not per SDK assistant message).\n */\nfunction flushAssistantTurn(\n tracer: ReturnType<typeof trace.getTracer>,\n scope: AgentScope,\n state: QueryState,\n): void {\n const buf = scope.assistantBuffer;\n if (!buf) return;\n scope.assistantBuffer = null;\n\n const model = buf.model ?? state.model ?? '';\n\n const attrs: Record<string, any> = {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': 'anthropic',\n 'neatlogs.llm.system': 'anthropic',\n };\n if (model) attrs['neatlogs.llm.model_name'] = String(model);\n\n // If this is the root scope's first turn and no user message was recorded yet\n // (streaming-input mode), seed input from the tapped prompt text.\n if (scope.inputMessages.length === 0 && state.promptRef.text) {\n scope.inputMessages.push({ role: 'user', content: state.promptRef.text });\n }\n\n // Input = the exact accumulated conversation up to this turn. Emitted BOTH as\n // structured indexed input_messages.* AND as the flat `input.value` blob —\n // per neatlogs/config/attribute-mapping.json, the main UI panel renders\n // `neatlogs.{span_kind}.input` (mapped from `input.value`); the indexed\n // messages alone do NOT populate it. Without input.value the LLM Input is blank.\n scope.inputMessages.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;\n });\n if (scope.inputMessages.length) {\n attrs['input.value'] = safeStringify({ messages: scope.inputMessages });\n }\n\n // Output = the turn's actual assistant content. Prefer the model's text; for a\n // tool-only turn (no text) the output IS the tool call(s), so render them as\n // the exact `name(arguments)` the model emitted (not a vague summary). The\n // structured tool_calls.* below still carry the same data for programmatic use.\n // `output.value` is the flat blob the UI maps to `neatlogs.{span_kind}.output`.\n const outText = buf.textParts.join('');\n const outValue =\n outText ||\n buf.toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.input ?? {})})`).join('\\n');\n if (outValue) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = outValue;\n attrs['output.value'] = outValue;\n }\n if (buf.thinkingParts.length) {\n attrs['neatlogs.llm.output_messages.0.thinking'] = buf.thinkingParts.join('');\n }\n buf.toolCalls.forEach((tc, j) => {\n attrs[`neatlogs.llm.tool_calls.${j}.id`] = tc.id;\n attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.input ?? {});\n });\n if (buf.stopReason) attrs['neatlogs.llm.finish_reason'] = String(buf.stopReason);\n\n // The LLM span nests under THIS scope's AGENT span.\n const span = tracer.startSpan(`claude_agent.llm.${model || 'model'}`, { attributes: attrs }, scope.ctx);\n if (buf.usage) setUsage(span, buf.usage);\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n\n // Track this scope's latest text as its output (the subagent/orchestrator\n // final answer is the last assistant text).\n if (outText) scope.finalText = outText;\n\n // Record this assistant turn so the NEXT turn's LLM span sees it as context.\n const turnParts: string[] = [];\n if (outText) turnParts.push(outText);\n for (const tc of buf.toolCalls) turnParts.push(`[tool_call ${tc.name} ${safeStringify(tc.input ?? {})}]`);\n if (turnParts.length) scope.inputMessages.push({ role: 'assistant', content: turnParts.join('\\n') });\n\n // Open TOOL spans for this turn's tool calls, nested under THIS scope's AGENT\n // span (closed by their tool_result). A Task tool's id becomes the key a\n // subagent's messages resolve to (see getScope).\n for (const tc of buf.toolCalls) {\n const toolSpan = tracer.startSpan(\n `claude_agent.tool.${tc.name || 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n 'neatlogs.tool.name': String(tc.name ?? ''),\n ...(tc.id ? { 'neatlogs.tool_call.id': String(tc.id) } : {}),\n 'input.value': safeStringify(tc.input ?? {}),\n },\n },\n scope.ctx,\n );\n if (tc.id) state.toolSpans.set(tc.id, toolSpan);\n }\n}\n\nfunction closeToolSpansFromUser(state: QueryState, scope: AgentScope, msg: any): void {\n const content = (msg.message ?? msg)?.content ?? [];\n for (const block of Array.isArray(content) ? content : []) {\n if (block?.type !== 'tool_result') continue;\n const id = block.tool_use_id ?? '';\n const out = block.content;\n const outText = (typeof out === 'string' ? out : safeStringify(out));\n // Feed the tool result into THIS scope's conversation so the next LLM span's\n // input reflects what the model actually saw.\n if (outText) scope.inputMessages.push({ role: 'tool', content: outText });\n const span = state.toolSpans.get(id);\n if (!span) continue;\n span.setAttribute('output.value', outText);\n if (block.is_error) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n state.toolSpans.delete(id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the human/text content of a `user` SDK message — the prompt, not\n * tool_result blocks (those are handled separately as tool outputs).\n */\nfunction userMessageText(msg: any): string {\n const content = (msg.message ?? msg)?.content;\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') {\n parts.push(block.text);\n }\n }\n return parts.join('\\n');\n}\n\nfunction setUsage(span: Span, usage: any): void {\n if (!usage) return;\n if (usage.input_tokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', usage.input_tokens);\n if (usage.output_tokens != null) span.setAttribute('neatlogs.llm.token_count.completion', usage.output_tokens);\n if (usage.input_tokens != null && usage.output_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.total', usage.input_tokens + usage.output_tokens);\n }\n if (usage.cache_read_input_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.cache_read', usage.cache_read_input_tokens);\n }\n if (usage.cache_creation_input_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.cache_write', usage.cache_creation_input_tokens);\n }\n}\n\nfunction extractPromptText(prompt: any): string {\n if (typeof prompt === 'string') return prompt;\n // Streaming-input mode passes an async iterable of messages; we can't read it\n // synchronously without consuming it, so leave input.value unset in that case.\n return '';\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction recordError(span: Span, err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n span.setAttribute(\n 'output.value',\n JSON.stringify({ status: 'error', error: message }),\n );\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 });\n }\n span.end();\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 createContextKey,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\nimport { getActiveClient } from './active-client.js';\n\n// Carries the trace-ROOT span down the private context so descendants can target\n// it (e.g. setTraceOutput). getActiveNeatlogsSpan() returns the innermost span,\n// which is not the root once nested; this key preserves the root reference.\nconst NEATLOGS_ROOT_SPAN_KEY = createContextKey('neatlogs.root_span');\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 const client = getActiveClient();\n if (client) return client.getTracer(name);\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/** A reusable tracer facade that resolves the active Client on every call. */\nexport function getRoutingNeatlogsTracer(name: string): Tracer {\n return {\n startSpan(spanName: string, options?: SpanOptions, context?: Context): Span {\n return isolateTracer(getNeatlogsTracer(name)).startSpan(\n spanName,\n options,\n context,\n );\n },\n startActiveSpan: ((...args: any[]) =>\n (isolateTracer(getNeatlogsTracer(name)).startActiveSpan as (...inner: any[]) => any)(\n ...args,\n )) as Tracer['startActiveSpan'],\n };\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 const client = getActiveClient();\n if (client) return client.tracerProvider;\n return providerState.provider;\n}\n\n/** Run a Client callback without inheriting another pipeline's private span. */\nexport function runWithFreshNeatlogsContext<T>(fn: () => T): T {\n return privateContextStorage.run(ROOT_CONTEXT, fn);\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 * Return the trace-ROOT Neatlogs span, or undefined when no trace is active.\n *\n * Unlike {@link getActiveNeatlogsSpan} (innermost), this is the outermost span\n * of the current trace — the one the backend derives trace-level output from\n * (`parent_span_id=''`). It is stashed on the private context the first time a\n * span becomes active, so nested calls still resolve to the root.\n */\nexport function getNeatlogsRootSpan(): Span | undefined {\n return getNeatlogsActiveContext().getValue(NEATLOGS_ROOT_SPAN_KEY) as\n | Span\n | undefined;\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 let ctx = otelTrace.setSpan(base, span);\n // The first span activated in a context with no root recorded IS the root of\n // this trace; remember it so descendants (setTraceOutput) can target it.\n if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === undefined) {\n ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);\n }\n return privateContextStorage.run(ctx, fn);\n}\n\n/**\n * @internal Run with a private Neatlogs parent without treating that parent as\n * a locally-recording trace root. This is used for an extracted remote parent:\n * the first local recording span should remain the target for local trace-level\n * output, while still inheriting the remote trace/span IDs.\n */\nexport function withNeatlogsRemoteParent<T>(span: Span, fn: () => T): T {\n return privateContextStorage.run(otelTrace.setSpan(ROOT_CONTEXT, span), fn);\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Tracer, TracerProvider } from '@opentelemetry/api';\n\nexport interface ActiveNeatlogsClient {\n readonly workflowName: string;\n readonly tracerProvider: TracerProvider;\n getTracer(scope: string): Tracer;\n getLogger(): any | null;\n}\n\nconst ACTIVE_CLIENT_STORAGE_KEY = Symbol.for(\n 'neatlogs.active_client_async_local_storage',\n);\ntype NeatlogsGlobal = typeof globalThis & {\n [ACTIVE_CLIENT_STORAGE_KEY]?: AsyncLocalStorage<ActiveNeatlogsClient>;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\nconst storage =\n neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] ??\n (neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] =\n new AsyncLocalStorage<ActiveNeatlogsClient>());\n\nexport function getActiveClient(): ActiveNeatlogsClient | undefined {\n return storage.getStore();\n}\n\nexport function runWithClient<T>(\n client: ActiveNeatlogsClient,\n fn: () => T,\n): T {\n return storage.run(client, fn);\n}\n"],"mappings":"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","names":["import_api","import_node_async_hooks","neatlogsGlobal","otelTrace","otelTrace"]}
|
|
1
|
+
{"version":3,"sources":["../src/claude-agent-sdk.ts","../src/core/provider.ts","../src/core/active-client.ts"],"sourcesContent":["/**\n * Neatlogs Claude Agent SDK integration.\n *\n * Wraps Anthropic's `@anthropic-ai/claude-agent-sdk` so every `query()` run is\n * traced. The SDK's `query()` returns an async-iterable of SDKMessages\n * (system init → assistant (text + tool_use) → user (tool_result) → … →\n * result). Each message carries `parent_tool_use_id`: null for the main\n * (orchestrator) agent, or the id of the spawning Task tool call for a subagent.\n * We translate that into a neatlogs span tree:\n *\n * AGENT claude_agent.query (orchestrator = trace root)\n * ↳ LLM orchestrator turn (one per model turn; text + tool_calls)\n * ↳ TOOL Read / Edit / Bash …\n * ↳ TOOL Task (spawns a subagent)\n * ↳ AGENT claude_agent.subagent.<type> (each subagent, nested)\n * ↳ LLM subagent turn\n * ↳ TOOL subagent tool call\n *\n * The orchestrator is the single root AGENT span — there is NO redundant WORKFLOW\n * wrapper. Subagents (Task-tool invocations, e.g. the wizard's parallel per-file\n * edits) get their own AGENT spans nested under the Task TOOL span that spawned\n * them, so a multi-agent run is represented faithfully and each agent's I/O is\n * distinct.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { wrapClaudeAgentSDK } from 'neatlogs/claude-agent-sdk';\n * import * as claudeAgentSDK from '@anthropic-ai/claude-agent-sdk';\n *\n * await init({ apiKey, workflowName });\n * const { query } = wrapClaudeAgentSDK(claudeAgentSDK);\n * for await (const msg of query({ prompt: 'Hello', options: {...} })) { ... }\n *\n * Conversation tracking: Claude's `session_id` is captured on the root AGENT span\n * as `neatlogs.conversation.id`. Tool calls are traced only through the wrapped\n * `query` — calling the unwrapped SDK directly produces no tracing.\n */\n\nimport { trace, SpanStatusCode, type Span, type Context } from '@opentelemetry/api';\nimport { getNeatlogsTracer, getNeatlogsBaseContext, withNeatlogsSpan } from './core/provider.js';\n\nconst TRACER_NAME = 'neatlogs.claude_agent_sdk';\nconst ROOT_SCOPE = '__root__';\n\nexport interface WrapClaudeAgentSDKOptions {\n /** Logical grouping for traces (also settable globally via init({ workflowName })). */\n workflowName?: string;\n}\n\n/**\n * Wrap the Claude Agent SDK module. Returns a shallow copy of the module with an\n * instrumented `query`; all other exports (createSdkMcpServer, tool, the built-in\n * Tool helpers, etc.) are passed through unchanged.\n */\nexport function wrapClaudeAgentSDK<T extends Record<string, any>>(\n sdk: T,\n options: WrapClaudeAgentSDKOptions = {},\n): T {\n if (!sdk || typeof sdk.query !== 'function') return sdk;\n if ((sdk as any)._neatlogsWrapped) return sdk;\n\n const wrapped: Record<string, any> = { ...sdk };\n wrapped.query = wrapQuery(sdk.query.bind(sdk), options);\n\n try {\n Object.defineProperty(wrapped, '_neatlogsWrapped', {\n value: true,\n enumerable: false,\n configurable: true,\n });\n } catch {\n wrapped._neatlogsWrapped = true;\n }\n\n return wrapped as T;\n}\n\n// ---------------------------------------------------------------------------\n// query() wrapping\n// ---------------------------------------------------------------------------\n\nfunction wrapQuery(original: (...args: any[]) => any, options: WrapClaudeAgentSDKOptions) {\n return function (params: any, ...rest: any[]): any {\n const tracer = getNeatlogsTracer(TRACER_NAME);\n const workflowName = options.workflowName ?? 'claude_agent.query';\n\n // Shared ref the input-tap fills with the first user-prompt text. In\n // streaming-input mode the prompt is an async generator fed into the SDK\n // subprocess and is NOT echoed back as a `user` output message before the\n // first assistant turn — so without tapping it, the first LLM span has no\n // input. The tap reads prompt text as the SDK pulls it.\n const promptRef: { text: string } = { text: '' };\n\n // The orchestrator AGENT is the trace ROOT (no WORKFLOW wrapper — a single\n // query() is one agent run; subagents nest below as their own AGENT spans).\n const agentSpan = tracer.startSpan(\n 'claude_agent.query',\n { attributes: { 'neatlogs.span.kind': 'AGENT', 'neatlogs.workflow.name': workflowName } },\n getNeatlogsBaseContext(),\n );\n\n // Input: a string prompt is captured directly. A streaming-input prompt (an\n // async iterable) is tapped — promptRef.text fills as the SDK pulls the\n // first user message — so the agent input and the first LLM span's input are\n // populated even before any `user` message echoes back through the output.\n const promptText = extractPromptText(params?.prompt);\n if (promptText) {\n promptRef.text = promptText;\n agentSpan.setAttribute('input.value', promptText);\n } else if (params && isAsyncIterable(params.prompt)) {\n params = { ...params, prompt: tapPromptStream(params.prompt, promptRef) };\n }\n\n const agentCtx = trace.setSpan(getNeatlogsBaseContext(), agentSpan);\n\n // Call the original query with the AGENT span active only under our private\n // context so a foreign provider's spans neither parent nor nest under ours.\n const queryObj = withNeatlogsSpan(agentSpan, () => original(params, ...rest));\n\n return instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRef);\n };\n}\n\n/** True for an async iterable (streaming-input prompt). */\nfunction isAsyncIterable(v: any): boolean {\n return Boolean(v) && typeof v[Symbol.asyncIterator] === 'function';\n}\n\n/**\n * Pass-through wrapper over a streaming-input prompt that records the first\n * user message's text into `ref` as the SDK consumes it. Never alters what the\n * SDK receives.\n */\nasync function* tapPromptStream(prompt: any, ref: { text: string }): AsyncGenerator<any> {\n // userMessageText is fully defensive (returns '' for any non-text shape), so\n // no try/catch is needed — the message is always yielded through untouched.\n for await (const message of prompt) {\n if (!ref.text) {\n const text = userMessageText(message);\n if (text) ref.text = text;\n }\n yield message;\n }\n}\n\ninterface ToolCallAccum {\n id: string;\n name: string;\n input: unknown;\n}\n\ninterface AssistantTurnBuffer {\n textParts: string[];\n thinkingParts: string[];\n toolCalls: ToolCallAccum[];\n usage: any;\n model?: string;\n stopReason?: string;\n}\n\n/**\n * One agent's tracing scope. There is always a root scope (the orchestrator,\n * keyed ROOT_SCOPE). Each subagent — identified by the `parent_tool_use_id` of\n * the Task tool call that spawned it — gets its own scope created lazily, with\n * its AGENT span nested under that Task TOOL span. Per-scope state keeps each\n * agent's conversation/turn buffer independent.\n */\ninterface AgentScope {\n /** The AGENT span for this scope (root = orchestrator; others = subagents). */\n span: Span;\n /** OTel context whose active span is this scope's AGENT span (children nest here). */\n ctx: Context;\n /** Running conversation (user/tool/assistant turns) — each LLM span's input. */\n inputMessages: Array<{ role: string; content: string }>;\n /** In-progress model turn, coalesced from multiple `assistant` messages. */\n assistantBuffer: AssistantTurnBuffer | null;\n /** Last assistant text seen (subagent output = its final text). */\n finalText: string;\n /** Whether input.value has been set on this scope's AGENT span. */\n inputCaptured: boolean;\n}\n\ninterface QueryState {\n /** TOOL spans keyed by tool_use_id, closed by the matching tool_result. */\n toolSpans: Map<string, Span>;\n /** Agent scopes keyed by parent_tool_use_id (ROOT_SCOPE for the orchestrator). */\n scopes: Map<string, AgentScope>;\n sessionId?: string;\n model?: string;\n finished: boolean;\n /** Lazily-filled prompt text from a streaming-input tap. */\n promptRef: { text: string };\n}\n\n/**\n * Wrap the Query object so iteration is instrumented while preserving its own\n * methods (interrupt, setPermissionMode, …). The SDK returns an async-iterable\n * object, not a bare generator.\n */\nfunction instrumentQueryIterable(\n queryObj: any,\n agentSpan: Span,\n agentCtx: Context,\n tracer: ReturnType<typeof trace.getTracer>,\n promptRef: { text: string },\n): any {\n const originalAsyncIterator = queryObj?.[Symbol.asyncIterator]?.bind(queryObj);\n if (!originalAsyncIterator) {\n // Not iterable — nothing to trace; close the root span immediately.\n agentSpan.setStatus({ code: SpanStatusCode.OK });\n agentSpan.end();\n return queryObj;\n }\n\n // The root scope is the orchestrator agent. Seed its conversation with the\n // prompt (known up front for string prompts; filled by the tap otherwise).\n const rootScope: AgentScope = {\n span: agentSpan,\n ctx: agentCtx,\n inputMessages: promptRef.text ? [{ role: 'user', content: promptRef.text }] : [],\n assistantBuffer: null,\n finalText: '',\n inputCaptured: Boolean(promptRef.text),\n };\n const state: QueryState = {\n toolSpans: new Map(),\n scopes: new Map([[ROOT_SCOPE, rootScope]]),\n finished: false,\n promptRef,\n };\n\n const finalizeAgent = (status: 'ok' | 'error', err?: unknown) => {\n if (state.finished) return;\n state.finished = true;\n // Flush every scope's in-progress turn, then close subagent AGENT spans\n // (deepest-first) and finally the root.\n for (const scope of state.scopes.values()) flushAssistantTurn(tracer, scope, state);\n // Close any tool spans that never got a matching result.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n // Close subagent scopes first (any still open), then the root.\n for (const [key, scope] of state.scopes) {\n if (key === ROOT_SCOPE) continue;\n closeScope(scope, 'ok');\n }\n if (rootScope.finalText) agentSpan.setAttribute('output.value', rootScope.finalText);\n if (status === 'error') {\n recordError(agentSpan, err);\n } else {\n agentSpan.setStatus({ code: SpanStatusCode.OK });\n agentSpan.end();\n }\n };\n\n const wrapped = Object.create(Object.getPrototypeOf(queryObj));\n Object.assign(wrapped, queryObj);\n\n wrapped[Symbol.asyncIterator] = function () {\n const iterator = originalAsyncIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await withNeatlogsSpan(agentSpan, () => iterator.next());\n if (result.done) {\n finalizeAgent('ok');\n return result;\n }\n try {\n handleMessage(tracer, state, result.value, finalizeAgent);\n } catch {\n /* never let tracing break the run */\n }\n return result;\n } catch (err) {\n finalizeAgent('error', err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalizeAgent('ok');\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(err?: any): Promise<IteratorResult<any>> {\n finalizeAgent('error', err);\n if (iterator.throw) return iterator.throw(err);\n throw err;\n },\n };\n };\n\n return wrapped;\n}\n\n/** End a subagent scope's AGENT span, setting its output to the subagent's final text. */\nfunction closeScope(scope: AgentScope, status: 'ok' | 'error'): void {\n try {\n if (scope.finalText) scope.span.setAttribute('output.value', scope.finalText);\n scope.span.setStatus({ code: status === 'ok' ? SpanStatusCode.OK : SpanStatusCode.ERROR });\n scope.span.end();\n } catch {\n /* ignore */\n }\n}\n\n// ---------------------------------------------------------------------------\n// Message handling\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the agent scope for a message. `parent_tool_use_id` is null for the\n * orchestrator (root scope) and the spawning Task tool_use_id for a subagent.\n * Subagent scopes are created lazily, with their AGENT span nested under the\n * Task TOOL span (looked up by that id) so the hierarchy is\n * orchestrator → Task TOOL → subagent AGENT.\n */\nfunction getScope(\n tracer: ReturnType<typeof trace.getTracer>,\n state: QueryState,\n msg: any,\n): AgentScope {\n const parentId = msg?.parent_tool_use_id ?? null;\n if (!parentId) return state.scopes.get(ROOT_SCOPE)!;\n\n const existing = state.scopes.get(parentId);\n if (existing) return existing;\n\n // New subagent. Its spawning Task tool_use may still be buffered in the root\n // turn (the SDK emits subagent messages before the Task's tool_result closes\n // the parent turn). Flush the root turn first so the Task TOOL span exists and\n // this subagent AGENT can nest under it.\n const root = state.scopes.get(ROOT_SCOPE)!;\n if (!state.toolSpans.has(parentId) && root.assistantBuffer) {\n flushAssistantTurn(tracer, root, state);\n }\n\n // Nest the subagent AGENT span under the spawning Task TOOL span if we have it;\n // otherwise under the root agent.\n const parentToolSpan = state.toolSpans.get(parentId);\n const parentCtx = parentToolSpan\n ? trace.setSpan(getNeatlogsBaseContext(), parentToolSpan)\n : state.scopes.get(ROOT_SCOPE)!.ctx;\n\n const subType = msg?.subagent_type ? String(msg.subagent_type) : 'subagent';\n const attrs: Record<string, any> = {\n 'neatlogs.span.kind': 'AGENT',\n 'neatlogs.agent.name': subType,\n };\n if (msg?.task_description) attrs['input.value'] = String(msg.task_description);\n const span = tracer.startSpan(`claude_agent.subagent.${subType}`, { attributes: attrs }, parentCtx);\n\n const scope: AgentScope = {\n span,\n ctx: trace.setSpan(getNeatlogsBaseContext(), span),\n inputMessages: msg?.task_description ? [{ role: 'user', content: String(msg.task_description) }] : [],\n assistantBuffer: null,\n finalText: '',\n inputCaptured: Boolean(msg?.task_description),\n };\n state.scopes.set(parentId, scope);\n return scope;\n}\n\nfunction handleMessage(\n tracer: ReturnType<typeof trace.getTracer>,\n state: QueryState,\n msg: any,\n finalizeAgent: (status: 'ok' | 'error', err?: unknown) => void,\n): void {\n if (!msg || typeof msg !== 'object') return;\n\n const rootScope = state.scopes.get(ROOT_SCOPE)!;\n\n // Backfill root input from the tapped streaming-input prompt as soon as it's\n // available (it fills before the first assistant turn).\n if (!rootScope.inputCaptured && state.promptRef.text) {\n rootScope.inputCaptured = true;\n rootScope.span.setAttribute('input.value', state.promptRef.text);\n }\n\n switch (msg.type) {\n case 'system': {\n // init message — carries session_id, model, available tools.\n if (msg.session_id) {\n state.sessionId = msg.session_id;\n rootScope.span.setAttribute('neatlogs.conversation.id', String(msg.session_id));\n }\n if (msg.model) {\n state.model = msg.model;\n rootScope.span.setAttribute('neatlogs.agent.model', String(msg.model));\n }\n break;\n }\n\n case 'user': {\n // A `user` message (prompt, or tool_result turns) is a turn boundary for\n // its scope: flush the buffered turn as ONE LLM span first.\n const scope = getScope(tracer, state, msg);\n flushAssistantTurn(tracer, scope, state);\n\n const userText = userMessageText(msg);\n if (userText) {\n if (!scope.inputCaptured) {\n scope.inputCaptured = true;\n scope.span.setAttribute('input.value', userText);\n }\n scope.inputMessages.push({ role: 'user', content: userText });\n }\n closeToolSpansFromUser(state, scope, msg);\n break;\n }\n\n case 'assistant': {\n // Don't emit yet — the SDK delivers one model turn as multiple `assistant`\n // messages (text block, then tool_use block, …). Buffer them per scope;\n // the next user/result boundary flushes the turn as a single LLM span.\n const scope = getScope(tracer, state, msg);\n bufferAssistantMessage(scope, msg);\n break;\n }\n\n case 'result': {\n // The run is complete. Flush the root turn (a final text answer may have no\n // trailing user message), then finalize.\n flushAssistantTurn(tracer, rootScope, state);\n\n const text = typeof msg.result === 'string' ? msg.result : '';\n if (text) rootScope.finalText = text;\n if (msg.session_id && !state.sessionId) {\n rootScope.span.setAttribute('neatlogs.conversation.id', String(msg.session_id));\n }\n const usage = msg.usage;\n if (usage) setUsage(rootScope.span, usage);\n if (msg.total_cost_usd != null) rootScope.span.setAttribute('neatlogs.agent.cost_usd', msg.total_cost_usd);\n if (msg.num_turns != null) rootScope.span.setAttribute('neatlogs.agent.num_turns', msg.num_turns);\n if (msg.is_error) rootScope.span.setAttribute('neatlogs.agent.is_error', true);\n\n finalizeAgent(msg.is_error ? 'error' : 'ok', msg.is_error ? new Error(String(text || 'agent run failed')) : undefined);\n break;\n }\n\n default:\n break;\n }\n}\n\n/**\n * Append one `assistant` SDK message to the in-progress turn buffer. The SDK\n * splits a single model turn into multiple assistant messages (a text block,\n * then tool_use blocks); they share token usage. We merge their text, thinking,\n * and tool_use blocks so the turn becomes ONE LLM span on flush.\n */\nfunction bufferAssistantMessage(scope: AgentScope, msg: any): void {\n const message = msg.message ?? msg;\n const content = message?.content ?? [];\n\n if (!scope.assistantBuffer) {\n scope.assistantBuffer = { textParts: [], thinkingParts: [], toolCalls: [], usage: undefined };\n }\n const buf = scope.assistantBuffer;\n if (message?.model) buf.model = message.model;\n if (message?.stop_reason) buf.stopReason = message.stop_reason;\n // Usage is reported per assistant message but is the SAME turn total — keep\n // the largest/last non-empty one rather than summing (summing double-counts).\n if (message?.usage) buf.usage = message.usage;\n\n for (const block of Array.isArray(content) ? content : []) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') buf.textParts.push(block.text);\n else if (block.type === 'thinking' && typeof block.thinking === 'string') buf.thinkingParts.push(block.thinking);\n else if (block.type === 'tool_use') {\n buf.toolCalls.push({ id: block.id ?? '', name: block.name ?? '', input: block.input });\n }\n }\n}\n\n/**\n * Emit the buffered model turn as a SINGLE LLM span, then open TOOL spans for\n * its tool calls. No-op if no turn is buffered. This is what makes one LLM span\n * per real model turn (not per SDK assistant message).\n */\nfunction flushAssistantTurn(\n tracer: ReturnType<typeof trace.getTracer>,\n scope: AgentScope,\n state: QueryState,\n): void {\n const buf = scope.assistantBuffer;\n if (!buf) return;\n scope.assistantBuffer = null;\n\n const model = buf.model ?? state.model ?? '';\n\n const attrs: Record<string, any> = {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': 'anthropic',\n 'neatlogs.llm.system': 'anthropic',\n };\n if (model) attrs['neatlogs.llm.model_name'] = String(model);\n\n // If this is the root scope's first turn and no user message was recorded yet\n // (streaming-input mode), seed input from the tapped prompt text.\n if (scope.inputMessages.length === 0 && state.promptRef.text) {\n scope.inputMessages.push({ role: 'user', content: state.promptRef.text });\n }\n\n // Input = the exact accumulated conversation up to this turn. Emitted BOTH as\n // structured indexed input_messages.* AND as the flat `input.value` blob —\n // per neatlogs/config/attribute-mapping.json, the main UI panel renders\n // `neatlogs.{span_kind}.input` (mapped from `input.value`); the indexed\n // messages alone do NOT populate it. Without input.value the LLM Input is blank.\n scope.inputMessages.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;\n });\n if (scope.inputMessages.length) {\n attrs['input.value'] = safeStringify({ messages: scope.inputMessages });\n }\n\n // Output = the turn's actual assistant content. Prefer the model's text; for a\n // tool-only turn (no text) the output IS the tool call(s), so render them as\n // the exact `name(arguments)` the model emitted (not a vague summary). The\n // structured tool_calls.* below still carry the same data for programmatic use.\n // `output.value` is the flat blob the UI maps to `neatlogs.{span_kind}.output`.\n const outText = buf.textParts.join('');\n const outValue =\n outText ||\n buf.toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.input ?? {})})`).join('\\n');\n if (outValue) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = outValue;\n attrs['output.value'] = outValue;\n }\n if (buf.thinkingParts.length) {\n attrs['neatlogs.llm.output_messages.0.thinking'] = buf.thinkingParts.join('');\n }\n buf.toolCalls.forEach((tc, j) => {\n attrs[`neatlogs.llm.tool_calls.${j}.id`] = tc.id;\n attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.input ?? {});\n });\n if (buf.stopReason) attrs['neatlogs.llm.finish_reason'] = String(buf.stopReason);\n\n // The LLM span nests under THIS scope's AGENT span.\n const span = tracer.startSpan(`claude_agent.llm.${model || 'model'}`, { attributes: attrs }, scope.ctx);\n if (buf.usage) setUsage(span, buf.usage);\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n\n // Track this scope's latest text as its output (the subagent/orchestrator\n // final answer is the last assistant text).\n if (outText) scope.finalText = outText;\n\n // Record this assistant turn so the NEXT turn's LLM span sees it as context.\n const turnParts: string[] = [];\n if (outText) turnParts.push(outText);\n for (const tc of buf.toolCalls) turnParts.push(`[tool_call ${tc.name} ${safeStringify(tc.input ?? {})}]`);\n if (turnParts.length) scope.inputMessages.push({ role: 'assistant', content: turnParts.join('\\n') });\n\n // Open TOOL spans for this turn's tool calls, nested under THIS scope's AGENT\n // span (closed by their tool_result). A Task tool's id becomes the key a\n // subagent's messages resolve to (see getScope).\n for (const tc of buf.toolCalls) {\n const toolSpan = tracer.startSpan(\n `claude_agent.tool.${tc.name || 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n 'neatlogs.tool.name': String(tc.name ?? ''),\n ...(tc.id ? { 'neatlogs.tool_call.id': String(tc.id) } : {}),\n 'input.value': safeStringify(tc.input ?? {}),\n },\n },\n scope.ctx,\n );\n if (tc.id) state.toolSpans.set(tc.id, toolSpan);\n }\n}\n\nfunction closeToolSpansFromUser(state: QueryState, scope: AgentScope, msg: any): void {\n const content = (msg.message ?? msg)?.content ?? [];\n for (const block of Array.isArray(content) ? content : []) {\n if (block?.type !== 'tool_result') continue;\n const id = block.tool_use_id ?? '';\n const out = block.content;\n const outText = (typeof out === 'string' ? out : safeStringify(out));\n // Feed the tool result into THIS scope's conversation so the next LLM span's\n // input reflects what the model actually saw.\n if (outText) scope.inputMessages.push({ role: 'tool', content: outText });\n const span = state.toolSpans.get(id);\n if (!span) continue;\n span.setAttribute('output.value', outText);\n if (block.is_error) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n state.toolSpans.delete(id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the human/text content of a `user` SDK message — the prompt, not\n * tool_result blocks (those are handled separately as tool outputs).\n */\nfunction userMessageText(msg: any): string {\n const content = (msg.message ?? msg)?.content;\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') {\n parts.push(block.text);\n }\n }\n return parts.join('\\n');\n}\n\nfunction setUsage(span: Span, usage: any): void {\n if (!usage) return;\n if (usage.input_tokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', usage.input_tokens);\n if (usage.output_tokens != null) span.setAttribute('neatlogs.llm.token_count.completion', usage.output_tokens);\n if (usage.input_tokens != null && usage.output_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.total', usage.input_tokens + usage.output_tokens);\n }\n if (usage.cache_read_input_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.cache_read', usage.cache_read_input_tokens);\n }\n if (usage.cache_creation_input_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.cache_write', usage.cache_creation_input_tokens);\n }\n}\n\nfunction extractPromptText(prompt: any): string {\n if (typeof prompt === 'string') return prompt;\n // Streaming-input mode passes an async iterable of messages; we can't read it\n // synchronously without consuming it, so leave input.value unset in that case.\n return '';\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction recordError(span: Span, err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n span.setAttribute(\n 'output.value',\n JSON.stringify({ status: 'error', error: message }),\n );\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 });\n }\n span.end();\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 createContextKey,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\nimport { getActiveClient } from './active-client.js';\n\n// Carries the trace-ROOT span down the private context so descendants can target\n// it (e.g. setTraceOutput). getActiveNeatlogsSpan() returns the innermost span,\n// which is not the root once nested; this key preserves the root reference.\nconst NEATLOGS_ROOT_SPAN_KEY = createContextKey('neatlogs.root_span');\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 const client = getActiveClient();\n if (client) return client.getTracer(name);\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/** A reusable tracer facade that resolves the active Client on every call. */\nexport function getRoutingNeatlogsTracer(name: string): Tracer {\n return {\n startSpan(spanName: string, options?: SpanOptions, context?: Context): Span {\n return isolateTracer(getNeatlogsTracer(name)).startSpan(\n spanName,\n options,\n context,\n );\n },\n startActiveSpan: ((...args: any[]) =>\n (isolateTracer(getNeatlogsTracer(name)).startActiveSpan as (...inner: any[]) => any)(\n ...args,\n )) as Tracer['startActiveSpan'],\n };\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 const client = getActiveClient();\n if (client) return client.tracerProvider;\n return providerState.provider;\n}\n\n/** Run a Client callback without inheriting another pipeline's private span. */\nexport function runWithFreshNeatlogsContext<T>(fn: () => T): T {\n return privateContextStorage.run(ROOT_CONTEXT, fn);\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 * Return the trace-ROOT Neatlogs span, or undefined when no trace is active.\n *\n * Unlike {@link getActiveNeatlogsSpan} (innermost), this is the outermost span\n * of the current trace — the one the backend derives trace-level output from\n * (`parent_span_id=''`). It is stashed on the private context the first time a\n * span becomes active, so nested calls still resolve to the root.\n */\nexport function getNeatlogsRootSpan(): Span | undefined {\n return getNeatlogsActiveContext().getValue(NEATLOGS_ROOT_SPAN_KEY) as\n | Span\n | undefined;\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 rootSpan?: Span,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n let ctx = otelTrace.setSpan(base, span);\n // The first span activated in a context with no root recorded IS the root of\n // this trace; remember it so descendants (setTraceOutput) can target it.\n if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === undefined) {\n ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span);\n }\n return privateContextStorage.run(ctx, fn);\n}\n\n/**\n * @internal Run with a private Neatlogs parent without treating that parent as\n * a locally-recording trace root. This is used for an extracted remote parent:\n * the first local recording span should remain the target for local trace-level\n * output, while still inheriting the remote trace/span IDs.\n */\nexport function withNeatlogsRemoteParent<T>(span: Span, fn: () => T): T {\n return privateContextStorage.run(otelTrace.setSpan(ROOT_CONTEXT, span), fn);\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Tracer, TracerProvider } from '@opentelemetry/api';\n\nexport interface ActiveNeatlogsClient {\n readonly workflowName: string;\n readonly tracerProvider: TracerProvider;\n getTracer(scope: string): Tracer;\n getLogger(): any | null;\n}\n\nconst ACTIVE_CLIENT_STORAGE_KEY = Symbol.for(\n 'neatlogs.active_client_async_local_storage',\n);\ntype NeatlogsGlobal = typeof globalThis & {\n [ACTIVE_CLIENT_STORAGE_KEY]?: AsyncLocalStorage<ActiveNeatlogsClient>;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\nconst storage =\n neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] ??\n (neatlogsGlobal[ACTIVE_CLIENT_STORAGE_KEY] =\n new AsyncLocalStorage<ActiveNeatlogsClient>());\n\nexport function getActiveClient(): ActiveNeatlogsClient | undefined {\n return storage.getStore();\n}\n\nexport function runWithClient<T>(\n client: ActiveNeatlogsClient,\n fn: () => T,\n): T {\n return storage.run(client, fn);\n}\n"],"mappings":"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","names":["import_api","import_node_async_hooks","neatlogsGlobal","otelTrace","otelTrace"]}
|
|
@@ -54,11 +54,11 @@ function getNeatlogsActiveContext() {
|
|
|
54
54
|
function getNeatlogsBaseContext(baseContext) {
|
|
55
55
|
return baseContext ?? getNeatlogsActiveContext();
|
|
56
56
|
}
|
|
57
|
-
function withNeatlogsSpan(span, fn, baseContext) {
|
|
57
|
+
function withNeatlogsSpan(span, fn, baseContext, rootSpan) {
|
|
58
58
|
const base = baseContext ?? getNeatlogsActiveContext();
|
|
59
59
|
let ctx = otelTrace.setSpan(base, span);
|
|
60
60
|
if (base.getValue(NEATLOGS_ROOT_SPAN_KEY) === void 0) {
|
|
61
|
-
ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, span);
|
|
61
|
+
ctx = ctx.setValue(NEATLOGS_ROOT_SPAN_KEY, rootSpan ?? span);
|
|
62
62
|
}
|
|
63
63
|
return privateContextStorage.run(ctx, fn);
|
|
64
64
|
}
|