neatlogs 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/dist/anthropic.cjs +68 -2
  3. package/dist/anthropic.cjs.map +1 -1
  4. package/dist/anthropic.mjs +68 -2
  5. package/dist/anthropic.mjs.map +1 -1
  6. package/dist/azure-openai.cjs +68 -2
  7. package/dist/azure-openai.cjs.map +1 -1
  8. package/dist/azure-openai.mjs +68 -2
  9. package/dist/azure-openai.mjs.map +1 -1
  10. package/dist/bedrock.cjs +68 -2
  11. package/dist/bedrock.cjs.map +1 -1
  12. package/dist/bedrock.mjs +68 -2
  13. package/dist/bedrock.mjs.map +1 -1
  14. package/dist/browser.cjs +19 -9
  15. package/dist/browser.cjs.map +1 -1
  16. package/dist/browser.d.ts +23 -5
  17. package/dist/browser.mjs +19 -9
  18. package/dist/browser.mjs.map +1 -1
  19. package/dist/index.cjs +225 -381
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.ts +38 -19
  22. package/dist/index.mjs +224 -381
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/langchain.cjs +68 -2
  25. package/dist/langchain.cjs.map +1 -1
  26. package/dist/langchain.mjs +68 -2
  27. package/dist/langchain.mjs.map +1 -1
  28. package/dist/openai.cjs +68 -2
  29. package/dist/openai.cjs.map +1 -1
  30. package/dist/openai.mjs +68 -2
  31. package/dist/openai.mjs.map +1 -1
  32. package/dist/opencode-plugin.cjs +2 -2
  33. package/dist/opencode-plugin.cjs.map +1 -1
  34. package/dist/opencode-plugin.d.ts +1 -1
  35. package/dist/opencode-plugin.mjs +2 -2
  36. package/dist/opencode-plugin.mjs.map +1 -1
  37. package/dist/openrouter-agent.cjs +68 -2
  38. package/dist/openrouter-agent.cjs.map +1 -1
  39. package/dist/openrouter-agent.mjs +68 -2
  40. package/dist/openrouter-agent.mjs.map +1 -1
  41. package/dist/vertex-ai.cjs +68 -2
  42. package/dist/vertex-ai.cjs.map +1 -1
  43. package/dist/vertex-ai.mjs +68 -2
  44. package/dist/vertex-ai.mjs.map +1 -1
  45. package/package.json +1 -1
package/dist/browser.d.ts CHANGED
@@ -78,6 +78,14 @@ interface NeatlogsSpan {
78
78
  endUser?: string;
79
79
  /** Arbitrary end-user fields for the trace root (overrides the client default). */
80
80
  endUserMetadata?: Record<string, unknown>;
81
+ /**
82
+ * SESSION this trace belongs to — the conversation/thread grouping many turns.
83
+ * Only meaningful on the ROOT of a trace (overrides the client default). Every
84
+ * trace sharing a `session` is grouped into one session in the dashboard; a new
85
+ * trace per turn, all with the same `session`, forms a multi-turn conversation.
86
+ * Ignored on child spans.
87
+ */
88
+ session?: string;
81
89
  }
82
90
  /** The root of a trace = a span node (its `name` becomes the workflow name). */
83
91
  type NeatlogsTrace = NeatlogsSpan;
@@ -109,6 +117,12 @@ interface NeatlogsOptions {
109
117
  endUser?: string;
110
118
  /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */
111
119
  endUserMetadata?: Record<string, unknown>;
120
+ /**
121
+ * Default SESSION for every trace this client sends — the conversation/thread
122
+ * these traces belong to. A per-call `session` (on trace()/trackAI()) overrides
123
+ * this. Set it once per conversation, e.g. `new Neatlogs({ apiKey, session: convId })`.
124
+ */
125
+ session?: string;
112
126
  }
113
127
  interface TrackResult {
114
128
  ok: boolean;
@@ -137,6 +151,8 @@ interface TrackAIInput {
137
151
  endUser?: string;
138
152
  /** Arbitrary end-user fields for this trace (overrides the client default). */
139
153
  endUserMetadata?: Record<string, unknown>;
154
+ /** SESSION this trace belongs to (overrides the client default). */
155
+ session?: string;
140
156
  }
141
157
  declare class Neatlogs {
142
158
  private readonly apiKey;
@@ -146,6 +162,7 @@ declare class Neatlogs {
146
162
  private readonly onError;
147
163
  private readonly endUser?;
148
164
  private readonly endUserMetadata?;
165
+ private readonly session?;
149
166
  constructor(opts: NeatlogsOptions);
150
167
  /** Send a full (optionally nested) trace. Returns the backend's result. */
151
168
  trace(trace: NeatlogsTrace): Promise<TrackResult>;
@@ -160,12 +177,13 @@ declare class Neatlogs {
160
177
  finish: (final?: Partial<TrackAIInput>) => Promise<TrackResult>;
161
178
  };
162
179
  /**
163
- * Fold end-user identity onto the trace ROOT as canonical attributes, and
164
- * strip the convenience `endUser`/`endUserMetadata` fields so they aren't sent
165
- * as raw root fields. Per-call values win over the client defaults; explicit
166
- * `attributes` win over both. One end-user per trace — only the root carries it.
180
+ * Fold identity (end-user + session) onto the trace ROOT as canonical
181
+ * attributes, and strip the convenience `endUser`/`endUserMetadata`/`session`
182
+ * fields so they aren't sent as raw root fields. Per-call values win over the
183
+ * client defaults; explicit `attributes` win over both. Only the root carries
184
+ * identity — one end-user per trace, one session per trace.
167
185
  */
168
- private applyEndUser;
186
+ private applyIdentity;
169
187
  /** POST the trace JSON to the backend's /v1/trace endpoint. */
170
188
  private post;
171
189
  }
package/dist/browser.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  // src/browser.ts
2
- var DEFAULT_ENDPOINT = "https://staging-cloud.neatlogs.com";
2
+ var DEFAULT_ENDPOINT = "https://ingest.neatlogs.com";
3
3
  var END_USER_ID_KEY = "neatlogs.end_user.id";
4
4
  var END_USER_METADATA_KEY = "neatlogs.end_user.metadata";
5
+ var SESSION_ID_KEY = "neatlogs.session.id";
5
6
  var Neatlogs = class {
6
7
  apiKey;
7
8
  project;
@@ -10,6 +11,7 @@ var Neatlogs = class {
10
11
  onError;
11
12
  endUser;
12
13
  endUserMetadata;
14
+ session;
13
15
  constructor(opts) {
14
16
  if (!opts || !opts.apiKey) {
15
17
  throw new Error("Neatlogs: apiKey is required");
@@ -21,6 +23,7 @@ var Neatlogs = class {
21
23
  this.enabled = opts.enabled !== false;
22
24
  this.endUser = opts.endUser;
23
25
  this.endUserMetadata = opts.endUserMetadata;
26
+ this.session = opts.session;
24
27
  this.onError = opts.onError || ((err) => {
25
28
  if (typeof console !== "undefined") console.warn("[neatlogs] send failed:", err);
26
29
  });
@@ -56,16 +59,20 @@ var Neatlogs = class {
56
59
  };
57
60
  }
58
61
  /**
59
- * Fold end-user identity onto the trace ROOT as canonical attributes, and
60
- * strip the convenience `endUser`/`endUserMetadata` fields so they aren't sent
61
- * as raw root fields. Per-call values win over the client defaults; explicit
62
- * `attributes` win over both. One end-user per trace — only the root carries it.
62
+ * Fold identity (end-user + session) onto the trace ROOT as canonical
63
+ * attributes, and strip the convenience `endUser`/`endUserMetadata`/`session`
64
+ * fields so they aren't sent as raw root fields. Per-call values win over the
65
+ * client defaults; explicit `attributes` win over both. Only the root carries
66
+ * identity — one end-user per trace, one session per trace.
63
67
  */
64
- applyEndUser(body) {
65
- const { endUser, endUserMetadata, ...rest } = body;
68
+ applyIdentity(body) {
69
+ const { endUser, endUserMetadata, session, ...rest } = body;
66
70
  const id = endUser ?? this.endUser;
67
71
  const meta = endUserMetadata ?? this.endUserMetadata;
68
- if (id === void 0 && meta === void 0) return rest;
72
+ const sessionId = session ?? this.session;
73
+ if (id === void 0 && meta === void 0 && sessionId === void 0) {
74
+ return rest;
75
+ }
69
76
  const attributes = { ...rest.attributes ?? {} };
70
77
  if (id !== void 0 && attributes[END_USER_ID_KEY] === void 0) {
71
78
  attributes[END_USER_ID_KEY] = String(id);
@@ -73,12 +80,15 @@ var Neatlogs = class {
73
80
  if (meta !== void 0 && attributes[END_USER_METADATA_KEY] === void 0) {
74
81
  attributes[END_USER_METADATA_KEY] = typeof meta === "string" ? meta : JSON.stringify(meta);
75
82
  }
83
+ if (sessionId !== void 0 && attributes[SESSION_ID_KEY] === void 0) {
84
+ attributes[SESSION_ID_KEY] = String(sessionId);
85
+ }
76
86
  return { ...rest, attributes };
77
87
  }
78
88
  /** POST the trace JSON to the backend's /v1/trace endpoint. */
79
89
  async post(body) {
80
90
  if (!this.enabled) return { ok: true };
81
- body = this.applyEndUser(body);
91
+ body = this.applyIdentity(body);
82
92
  const payload = this.project && body.project === void 0 ? { ...body, project: this.project } : body;
83
93
  try {
84
94
  const res = await fetch(`${this.baseUrl}/v1/trace`, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/browser.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\nconst DEFAULT_ENDPOINT = \"https://staging-cloud.neatlogs.com\";\n\n// Canonical end-user 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\";\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 endUser?: string;\n /** Arbitrary end-user fields for the trace root (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\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 `endUser` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUser })`.\n */\n endUser?: string;\n /** Default arbitrary end-user fields stored as JSON (e.g. { plan: 'pro' }). */\n endUserMetadata?: Record<string, unknown>;\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 endUser?: string;\n /** Arbitrary end-user fields for this trace (overrides the client default). */\n endUserMetadata?: Record<string, unknown>;\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 endUser?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\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_ENDPOINT;\n this.baseUrl = safeOrigin(endpoint) || DEFAULT_ENDPOINT;\n this.enabled = opts.enabled !== false;\n this.endUser = opts.endUser;\n this.endUserMetadata = opts.endUserMetadata;\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 end-user identity onto the trace ROOT as canonical attributes, and\n * strip the convenience `endUser`/`endUserMetadata` fields so they aren't sent\n * as raw root fields. Per-call values win over the client defaults; explicit\n * `attributes` win over both. One end-user per trace — only the root carries it.\n */\n private applyEndUser(body: NeatlogsTrace): NeatlogsTrace {\n const { endUser, endUserMetadata, ...rest } = body as NeatlogsTrace & {\n endUser?: string;\n endUserMetadata?: Record<string, unknown>;\n };\n const id = endUser ?? this.endUser;\n const meta = endUserMetadata ?? this.endUserMetadata;\n if (id === undefined && meta === undefined) return rest;\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 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 end-user identity onto the root before anything else.\n body = this.applyEndUser(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/browser.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\nconst DEFAULT_ENDPOINT = \"https://ingest.neatlogs.com\";\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 endUser?: 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 `session` is grouped into one session in the dashboard; a new\n * trace per turn, all with the same `session`, forms a multi-turn conversation.\n * Ignored on child spans.\n */\n session?: 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 `endUser` (on trace()/trackAI())\n * overrides this. Set it once after login, e.g. `new Neatlogs({ apiKey, endUser })`.\n */\n endUser?: 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 `session` (on trace()/trackAI()) overrides\n * this. Set it once per conversation, e.g. `new Neatlogs({ apiKey, session: convId })`.\n */\n session?: 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 endUser?: 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 session?: 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 endUser?: string;\n private readonly endUserMetadata?: Record<string, unknown>;\n private readonly session?: 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_ENDPOINT;\n this.baseUrl = safeOrigin(endpoint) || DEFAULT_ENDPOINT;\n this.enabled = opts.enabled !== false;\n this.endUser = opts.endUser;\n this.endUserMetadata = opts.endUserMetadata;\n this.session = opts.session;\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 `endUser`/`endUserMetadata`/`session`\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 { endUser, endUserMetadata, session, ...rest } = body as NeatlogsTrace & {\n endUser?: string;\n endUserMetadata?: Record<string, unknown>;\n session?: string;\n };\n const id = endUser ?? this.endUser;\n const meta = endUserMetadata ?? this.endUserMetadata;\n const sessionId = session ?? this.session;\n if (id === undefined && meta === undefined && sessionId === 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 (sessionId !== undefined && attributes[SESSION_ID_KEY] === undefined) {\n attributes[SESSION_ID_KEY] = String(sessionId);\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":[]}