@autter/runtime-node 0.1.0 → 1.1.0

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/dist/index.d.cts CHANGED
@@ -43,13 +43,52 @@ declare function createBrowserRelayHandler(opts: RelayOptions): (req: IncomingMe
43
43
  body?: unknown;
44
44
  }, res: ServerResponse) => void;
45
45
 
46
+ interface RedactOptions {
47
+ /**
48
+ * Extra patterns matched against lower-cased attribute KEYS; a match
49
+ * masks the whole value. Strings become case-insensitive substring
50
+ * patterns. Extends the built-in list.
51
+ */
52
+ additionalKeyPatterns?: (RegExp | string)[];
53
+ /**
54
+ * Extra patterns scrubbed inside string VALUES (same semantics as the
55
+ * built-in email/token patterns). Extends the built-in list.
56
+ */
57
+ additionalValuePatterns?: (RegExp | string)[];
58
+ /** Replacement token. Default "[redacted]". */
59
+ mask?: string;
60
+ /**
61
+ * Scrub email-shaped substrings from ALL string values, not just
62
+ * sensitive keys. Default true.
63
+ */
64
+ scrubEmailValues?: boolean;
65
+ }
66
+ /**
67
+ * Return a copy of `attributes` with PII/secrets masked. Never mutates the
68
+ * input; non-string primitives pass through untouched; `undefined` values
69
+ * are dropped (OpenTelemetry rejects them).
70
+ */
71
+ declare function redactAttributes(attributes?: Attributes | null, options?: RedactOptions): Attributes;
72
+
46
73
  /**
47
74
  * Curated OpenTelemetry setup for Autter Runtime — deliberately NOT the
48
75
  * auto-instrumentation metapackage. Default data-volume policy:
49
76
  *
50
77
  * errors / captured exceptions : 100% (dedicated always-on error tracer)
78
+ * traces containing an error : 100% (tail retention — the full trace is
79
+ * exported when any of its spans
80
+ * errors, so a retained error keeps
81
+ * the trace that explains it)
82
+ * named process spans : 100% (withProcessSpan, always-on tracer)
83
+ * LLM / GenAI call spans : 100% (withLlmCall/trackLlmCall/
84
+ * instrumentLlmClient + an LLM-aware
85
+ * sampler that keeps GenAI/Vercel-AI
86
+ * spans — cost tracking needs every
87
+ * call)
51
88
  * successful traces : 1% (head sampling, configurable)
52
- * request metrics : aggregated every 60 s
89
+ * request metrics : aggregated every 60 s, keyed by route
90
+ * template (Express routes detected
91
+ * out of the box — see captureExpressRoute)
53
92
  * logs : not collected
54
93
  *
55
94
  * Exports OTLP/HTTP JSON to the Autter ingester (`/v1/traces`,
@@ -66,14 +105,97 @@ interface AutterServerOptions {
66
105
  release?: string;
67
106
  /** Head-sampling ratio for regular traces. Default 0.01 (1%). */
68
107
  traceSampleRate?: number;
108
+ /**
109
+ * Keep the FULL trace whenever it contains an error: an ERROR-status
110
+ * span (e.g. a 5xx request), a recorded exception, or a
111
+ * `captureException` / error-severity `captureMessage` call made inside
112
+ * it. Head sampling still decides what is exported for healthy traffic;
113
+ * erroring traces are rescued from the unsampled pool by a bounded
114
+ * in-process buffer — without this, errors are retained at 100% while
115
+ * the trace explaining them survives only `traceSampleRate` of the time.
116
+ * Default true.
117
+ */
118
+ retainTracesOnError?: boolean;
69
119
  /** Metric export interval. Default 60_000 ms. */
70
120
  metricIntervalMs?: number;
71
121
  /** Capture crashing exceptions via process.uncaughtExceptionMonitor (default true). */
72
122
  captureGlobalErrors?: boolean;
123
+ /**
124
+ * Flush all exporters when the process exits (`beforeExit`, SIGINT,
125
+ * SIGTERM) so a forgotten `shutdown()` doesn't silently drop buffered
126
+ * telemetry. If your code also handles the signal, Autter only flushes
127
+ * alongside it and never changes your exit path. Default true.
128
+ */
129
+ autoFlush?: boolean;
130
+ /**
131
+ * Mask PII/secrets in custom attributes before they leave the process:
132
+ * emails, tokens (JWT, sk-, gh_, AWS, Slack), bearer headers,
133
+ * `scheme://user:pass@` URLs, and anything whose attribute KEY looks
134
+ * sensitive (password/token/secret/cookie/…). Mirrors the browser
135
+ * relay's whitelist sanitiser. `true`/undefined = defaults; pass a
136
+ * RedactOptions object to extend the patterns; `false` disables.
137
+ * Default true.
138
+ */
139
+ redactAttributes?: boolean | RedactOptions;
140
+ /**
141
+ * Verbose lifecycle logging to stderr: "exported N spans", flushes and
142
+ * auto-flush activity — handy while wiring the SDK up. Also enabled by
143
+ * the AUTTER_DEBUG=1 environment variable. Default false.
144
+ */
145
+ debug?: boolean;
146
+ /**
147
+ * Record LLM/GenAI spans (`gen_ai.*` semconv, Vercel AI SDK `ai.*`,
148
+ * `withLlmCall`) at 100% regardless of `traceSampleRate`, so every model
149
+ * call is tracked with tokens/cost (default true).
150
+ */
151
+ llmTracing?: boolean;
73
152
  /** Extra instrumentations (e.g. `new ExpressInstrumentation()`). */
74
153
  instrumentations?: unknown[];
75
154
  }
76
155
  type AutterSeverity = "fatal" | "error" | "warning" | "info";
156
+ /** Token usage (and optionally exact cost) of one LLM call. */
157
+ interface LlmUsage {
158
+ inputTokens?: number;
159
+ outputTokens?: number;
160
+ /**
161
+ * Exact cost in USD, if the provider reports it. Overrides Autter's
162
+ * built-in per-model price estimate.
163
+ */
164
+ costUsd?: number;
165
+ }
166
+ interface LlmCallInfo {
167
+ /** Provider system: "openai", "anthropic", "google", "mistral_ai", … */
168
+ provider: string;
169
+ /** Requested model id, e.g. "gpt-5-mini" or "claude-sonnet-4". */
170
+ model: string;
171
+ /** "chat" (default), "embeddings", "text_completion", … */
172
+ operation?: string;
173
+ /** Opaque end-user id for per-user cost attribution — never an email. */
174
+ userId?: string;
175
+ sessionId?: string;
176
+ /** Extra span attributes (kept under the gen_ai./autter.llm. families). */
177
+ attributes?: Attributes;
178
+ }
179
+ /** Reporting surface handed to the `withLlmCall` callback. */
180
+ interface LlmCallHandle {
181
+ /** Report token usage / exact cost from the provider response. */
182
+ setUsage(usage: LlmUsage): void;
183
+ /** Exact cost in USD; without it the ingester estimates from its price table. */
184
+ setCost(usd: number): void;
185
+ /** Record the model the provider actually served (gen_ai.response.model). */
186
+ setResponseModel(model: string): void;
187
+ /** Alias of setResponseModel. */
188
+ setModel(model: string): void;
189
+ /** Extra span attributes reported mid-call. */
190
+ setAttributes(attributes: Attributes): void;
191
+ }
192
+ /** A completed LLM call reported after the fact via `trackLlmCall`. */
193
+ interface TrackedLlmCall extends LlmCallInfo, LlmUsage {
194
+ /** Wall-clock duration of the call, if measured. */
195
+ durationMs?: number;
196
+ /** Pass the thrown error to record the call as failed. */
197
+ error?: unknown;
198
+ }
77
199
  interface AutterServer {
78
200
  /** Report a handled exception. Always recorded (never sampled out). */
79
201
  captureException(error: unknown, attributes?: Attributes): void;
@@ -83,6 +205,41 @@ interface AutterServer {
83
205
  * errors with a lower severity so aggregations can slice one dataset.
84
206
  */
85
207
  captureMessage(message: string, severity?: AutterSeverity, attributes?: Attributes): void;
208
+ /**
209
+ * Wrap a named unit of work — a background job, queue consumer, cron
210
+ * tick, or DB-heavy call — in an always-recorded span. Unlike regular
211
+ * traces these are never head-sampled, so Autter's slow-process monitor
212
+ * sees accurate run counts and durations for non-HTTP work. Nested
213
+ * instrumented calls run inside the span's context, so slow traces show
214
+ * where the time went. Rethrows whatever `fn` throws.
215
+ */
216
+ withProcessSpan<T>(name: string, fn: () => T | Promise<T>, attributes?: Attributes): Promise<T>;
217
+ /**
218
+ * Wrap one LLM provider call. Always recorded (never head-sampled), so
219
+ * Autter's LLM usage/cost dashboards and anomaly monitor see every call.
220
+ * Report tokens via the handle once the response arrives; cost is
221
+ * estimated from the model id unless `setCost(usd)` (or
222
+ * `setUsage({ costUsd })`) reports the exact figure. Rethrows whatever
223
+ * `fn` throws (failed calls are recorded with the error attached).
224
+ *
225
+ * const reply = await autter.withLlmCall(
226
+ * { provider: "openai", model: "gpt-5-mini", userId: user.id },
227
+ * async (llm) => {
228
+ * const res = await openai.chat.completions.create({ ... });
229
+ * llm.setUsage({
230
+ * inputTokens: res.usage?.prompt_tokens,
231
+ * outputTokens: res.usage?.completion_tokens,
232
+ * });
233
+ * return res;
234
+ * },
235
+ * );
236
+ */
237
+ withLlmCall<T>(info: LlmCallInfo, fn: (llm: LlmCallHandle) => T | Promise<T>): Promise<T>;
238
+ /**
239
+ * Report an already-completed LLM call — for codepaths where wrapping is
240
+ * awkward (queues, callbacks, batch results). Fire-and-forget.
241
+ */
242
+ trackLlmCall(call: TrackedLlmCall): void;
86
243
  /** Flush and stop all exporters. Call on graceful shutdown. */
87
244
  shutdown(): Promise<void>;
88
245
  }
@@ -90,7 +247,131 @@ declare function initAutterServer(options: AutterServerOptions): AutterServer;
90
247
  /** Module-level convenience — routes to the active server, or degrades to a
91
248
  * span on the global tracer so libraries can call it unconditionally. */
92
249
  declare function captureException(error: unknown, attributes?: Attributes): void;
250
+ /**
251
+ * Module-level convenience — see AutterServer.withProcessSpan. Before
252
+ * initAutterServer runs it degrades to the global tracer, where the span is
253
+ * subject to that provider's own sampling instead of the always-on pipe.
254
+ */
255
+ declare function withProcessSpan<T>(name: string, fn: () => T | Promise<T>, attributes?: Attributes): Promise<T>;
256
+ /**
257
+ * Module-level convenience — see AutterServer.withLlmCall. Before
258
+ * initAutterServer runs it degrades to the global tracer, where the span is
259
+ * subject to that provider's own sampling instead of the always-on pipe.
260
+ */
261
+ declare function withLlmCall<T>(info: LlmCallInfo, fn: (llm: LlmCallHandle) => T | Promise<T>): Promise<T>;
262
+ /** Module-level convenience — see AutterServer.trackLlmCall. */
263
+ declare function trackLlmCall(call: TrackedLlmCall): void;
93
264
  /** Module-level convenience for warnings/info — see AutterServer.captureMessage. */
94
265
  declare function captureMessage(message: string, severity?: AutterSeverity, attributes?: Attributes): void;
266
+ /** Capture surface with guaranteed redaction, regardless of how the host app
267
+ * configured initAutterServer — for library authors whose calls must never
268
+ * forward PII even when the host disabled it. Routes to the active server
269
+ * (or degrades to the global tracer exactly like captureException). */
270
+ interface SafeCapture {
271
+ captureException(error: unknown, attributes?: Attributes): void;
272
+ captureMessage(message: string, severity?: AutterSeverity, attributes?: Attributes): void;
273
+ }
274
+ /**
275
+ * Build a redacting capture wrapper:
276
+ *
277
+ * const safe = makeSafeCapture();
278
+ * safe.captureException(err, { "user.email": email }); // masked before export
279
+ *
280
+ * The returned functions have the same signatures as their plain twins;
281
+ * every attribute passes through redactAttributes() first. Note that when
282
+ * the host has NOT disabled redaction this is belt-and-braces (already on by
283
+ * default) — its value is enforcing privacy inside libraries and wrappers.
284
+ */
285
+ declare function makeSafeCapture(options?: RedactOptions): SafeCapture;
286
+ /**
287
+ * Emit one clearly-named fake LLM call (provider/model "autter-selftest",
288
+ * 1 input + 1 output token, cost 0) and force-flush it, returning the
289
+ * traceId — proves LLM traces reach the ingester without calling any real
290
+ * model. For setup verification only; requires initAutterServer.
291
+ */
292
+ declare function emitLlmSelftestTrace(): Promise<{
293
+ traceId: string;
294
+ }>;
295
+
296
+ interface AutterLogger {
297
+ log: (...args: unknown[]) => void;
298
+ warn: (...args: unknown[]) => void;
299
+ error: (...args: unknown[]) => void;
300
+ }
301
+ interface FlushTarget {
302
+ forceFlush(): Promise<unknown> | unknown;
303
+ }
304
+ interface AutoFlushOptions {
305
+ /**
306
+ * Targets to flush. Defaults to whatever initAutterServer has registered;
307
+ * pass your own for custom pipelines.
308
+ */
309
+ targets?: FlushTarget[];
310
+ /** Where lifecycle messages go. Default console. */
311
+ logger?: AutterLogger;
312
+ /** Log each successful flush ("flushed telemetry (SIGTERM)"). Default true. */
313
+ log?: boolean;
314
+ /**
315
+ * Warn on stderr when the process exits with captures that were never
316
+ * confirmed exported. Default true.
317
+ */
318
+ warnOnUnflushedExit?: boolean;
319
+ /**
320
+ * Max wall-clock time a single flush may take before giving up (and, on
321
+ * signals, proceeding to exit anyway). Default 3000 ms.
322
+ */
323
+ timeoutMs?: number;
324
+ /** Signals handled besides the defaults. Default ["SIGINT", "SIGTERM"]. */
325
+ signals?: string[];
326
+ }
327
+ interface AutoFlushHandle {
328
+ /** Force-flush now. Safe to call repeatedly; concurrent calls coalesce. */
329
+ flush(reason?: string): Promise<boolean>;
330
+ /** Remove every listener this install added. */
331
+ dispose(): void;
332
+ }
333
+ /**
334
+ * Flush all exporters when the process is going away:
335
+ *
336
+ * - `beforeExit` — event-loop drained naturally: force-flush, then hand the
337
+ * loop back (a kept-alive timer makes sure the async flush finishes
338
+ * inside the beforeExit window).
339
+ * - SIGINT / SIGTERM — flush, then exit with the conventional code (130 /
340
+ * 143). If YOUR code also listens to the signal, we only flush
341
+ * concurrently and let your handler decide the exit — installing this
342
+ * never changes an existing graceful-shutdown path. A second signal
343
+ * always exits immediately.
344
+ *
345
+ * Idempotent per options object; `initAutterServer` installs it by default
346
+ * (`autoFlush: false` opts out).
347
+ */
348
+ declare function installAutterAutoFlush(options?: AutoFlushOptions): AutoFlushHandle;
349
+
350
+ /**
351
+ * One-line LLM client auto-instrumentation:
352
+ *
353
+ * const openai = instrumentLlmClient(new OpenAI());
354
+ *
355
+ * Wraps a provider SDK instance (OpenAI, Anthropic, Google GenAI, or any
356
+ * client with the same call shapes) in a deep proxy. Every model invocation —
357
+ * a method like `create`/`stream`/`generateContent` called with an object
358
+ * argument carrying a `model` — is recorded as a gen_ai span on the
359
+ * always-recorded pipe: model, tokens, latency, errors, and (ingester-side)
360
+ * USD cost. Streaming responses are supported: the span stays open until the
361
+ * stream is consumed, picking up usage from the final chunks.
362
+ *
363
+ * Nothing about the client's behaviour changes; methods run with the
364
+ * original `this` (SDK private fields keep working) and results pass
365
+ * through untouched apart from the stream wrapper.
366
+ */
367
+ interface InstrumentLlmOptions {
368
+ /** Provider id ("openai", "anthropic", …). Default: detected from the client. */
369
+ provider?: string;
370
+ /** Opaque end-user id (never an email) — static or resolved per call. */
371
+ userId?: string | (() => string | undefined);
372
+ /** Extra attributes for every span from this client. */
373
+ attributes?: Attributes;
374
+ }
375
+ declare function instrumentLlmClient<T extends object>(client: T, options?: InstrumentLlmOptions): T;
95
376
 
96
- export { type AutterServer, type AutterServerOptions, type AutterSeverity, type RelayOptions, captureException, captureMessage, createBrowserRelayFetchHandler, createBrowserRelayHandler, initAutterServer, sanitizeBrowserPayload };
377
+ export { type AutoFlushHandle, type AutoFlushOptions, type AutterServer, type AutterServerOptions, type AutterSeverity, type FlushTarget, type InstrumentLlmOptions, type LlmCallHandle, type LlmCallInfo, type LlmUsage, type RedactOptions, type RelayOptions, type SafeCapture, type TrackedLlmCall, captureException, captureMessage, createBrowserRelayFetchHandler, createBrowserRelayHandler, emitLlmSelftestTrace, initAutterServer, installAutterAutoFlush, instrumentLlmClient, makeSafeCapture, redactAttributes, sanitizeBrowserPayload, trackLlmCall, withLlmCall, withProcessSpan };
package/dist/index.d.ts CHANGED
@@ -43,13 +43,52 @@ declare function createBrowserRelayHandler(opts: RelayOptions): (req: IncomingMe
43
43
  body?: unknown;
44
44
  }, res: ServerResponse) => void;
45
45
 
46
+ interface RedactOptions {
47
+ /**
48
+ * Extra patterns matched against lower-cased attribute KEYS; a match
49
+ * masks the whole value. Strings become case-insensitive substring
50
+ * patterns. Extends the built-in list.
51
+ */
52
+ additionalKeyPatterns?: (RegExp | string)[];
53
+ /**
54
+ * Extra patterns scrubbed inside string VALUES (same semantics as the
55
+ * built-in email/token patterns). Extends the built-in list.
56
+ */
57
+ additionalValuePatterns?: (RegExp | string)[];
58
+ /** Replacement token. Default "[redacted]". */
59
+ mask?: string;
60
+ /**
61
+ * Scrub email-shaped substrings from ALL string values, not just
62
+ * sensitive keys. Default true.
63
+ */
64
+ scrubEmailValues?: boolean;
65
+ }
66
+ /**
67
+ * Return a copy of `attributes` with PII/secrets masked. Never mutates the
68
+ * input; non-string primitives pass through untouched; `undefined` values
69
+ * are dropped (OpenTelemetry rejects them).
70
+ */
71
+ declare function redactAttributes(attributes?: Attributes | null, options?: RedactOptions): Attributes;
72
+
46
73
  /**
47
74
  * Curated OpenTelemetry setup for Autter Runtime — deliberately NOT the
48
75
  * auto-instrumentation metapackage. Default data-volume policy:
49
76
  *
50
77
  * errors / captured exceptions : 100% (dedicated always-on error tracer)
78
+ * traces containing an error : 100% (tail retention — the full trace is
79
+ * exported when any of its spans
80
+ * errors, so a retained error keeps
81
+ * the trace that explains it)
82
+ * named process spans : 100% (withProcessSpan, always-on tracer)
83
+ * LLM / GenAI call spans : 100% (withLlmCall/trackLlmCall/
84
+ * instrumentLlmClient + an LLM-aware
85
+ * sampler that keeps GenAI/Vercel-AI
86
+ * spans — cost tracking needs every
87
+ * call)
51
88
  * successful traces : 1% (head sampling, configurable)
52
- * request metrics : aggregated every 60 s
89
+ * request metrics : aggregated every 60 s, keyed by route
90
+ * template (Express routes detected
91
+ * out of the box — see captureExpressRoute)
53
92
  * logs : not collected
54
93
  *
55
94
  * Exports OTLP/HTTP JSON to the Autter ingester (`/v1/traces`,
@@ -66,14 +105,97 @@ interface AutterServerOptions {
66
105
  release?: string;
67
106
  /** Head-sampling ratio for regular traces. Default 0.01 (1%). */
68
107
  traceSampleRate?: number;
108
+ /**
109
+ * Keep the FULL trace whenever it contains an error: an ERROR-status
110
+ * span (e.g. a 5xx request), a recorded exception, or a
111
+ * `captureException` / error-severity `captureMessage` call made inside
112
+ * it. Head sampling still decides what is exported for healthy traffic;
113
+ * erroring traces are rescued from the unsampled pool by a bounded
114
+ * in-process buffer — without this, errors are retained at 100% while
115
+ * the trace explaining them survives only `traceSampleRate` of the time.
116
+ * Default true.
117
+ */
118
+ retainTracesOnError?: boolean;
69
119
  /** Metric export interval. Default 60_000 ms. */
70
120
  metricIntervalMs?: number;
71
121
  /** Capture crashing exceptions via process.uncaughtExceptionMonitor (default true). */
72
122
  captureGlobalErrors?: boolean;
123
+ /**
124
+ * Flush all exporters when the process exits (`beforeExit`, SIGINT,
125
+ * SIGTERM) so a forgotten `shutdown()` doesn't silently drop buffered
126
+ * telemetry. If your code also handles the signal, Autter only flushes
127
+ * alongside it and never changes your exit path. Default true.
128
+ */
129
+ autoFlush?: boolean;
130
+ /**
131
+ * Mask PII/secrets in custom attributes before they leave the process:
132
+ * emails, tokens (JWT, sk-, gh_, AWS, Slack), bearer headers,
133
+ * `scheme://user:pass@` URLs, and anything whose attribute KEY looks
134
+ * sensitive (password/token/secret/cookie/…). Mirrors the browser
135
+ * relay's whitelist sanitiser. `true`/undefined = defaults; pass a
136
+ * RedactOptions object to extend the patterns; `false` disables.
137
+ * Default true.
138
+ */
139
+ redactAttributes?: boolean | RedactOptions;
140
+ /**
141
+ * Verbose lifecycle logging to stderr: "exported N spans", flushes and
142
+ * auto-flush activity — handy while wiring the SDK up. Also enabled by
143
+ * the AUTTER_DEBUG=1 environment variable. Default false.
144
+ */
145
+ debug?: boolean;
146
+ /**
147
+ * Record LLM/GenAI spans (`gen_ai.*` semconv, Vercel AI SDK `ai.*`,
148
+ * `withLlmCall`) at 100% regardless of `traceSampleRate`, so every model
149
+ * call is tracked with tokens/cost (default true).
150
+ */
151
+ llmTracing?: boolean;
73
152
  /** Extra instrumentations (e.g. `new ExpressInstrumentation()`). */
74
153
  instrumentations?: unknown[];
75
154
  }
76
155
  type AutterSeverity = "fatal" | "error" | "warning" | "info";
156
+ /** Token usage (and optionally exact cost) of one LLM call. */
157
+ interface LlmUsage {
158
+ inputTokens?: number;
159
+ outputTokens?: number;
160
+ /**
161
+ * Exact cost in USD, if the provider reports it. Overrides Autter's
162
+ * built-in per-model price estimate.
163
+ */
164
+ costUsd?: number;
165
+ }
166
+ interface LlmCallInfo {
167
+ /** Provider system: "openai", "anthropic", "google", "mistral_ai", … */
168
+ provider: string;
169
+ /** Requested model id, e.g. "gpt-5-mini" or "claude-sonnet-4". */
170
+ model: string;
171
+ /** "chat" (default), "embeddings", "text_completion", … */
172
+ operation?: string;
173
+ /** Opaque end-user id for per-user cost attribution — never an email. */
174
+ userId?: string;
175
+ sessionId?: string;
176
+ /** Extra span attributes (kept under the gen_ai./autter.llm. families). */
177
+ attributes?: Attributes;
178
+ }
179
+ /** Reporting surface handed to the `withLlmCall` callback. */
180
+ interface LlmCallHandle {
181
+ /** Report token usage / exact cost from the provider response. */
182
+ setUsage(usage: LlmUsage): void;
183
+ /** Exact cost in USD; without it the ingester estimates from its price table. */
184
+ setCost(usd: number): void;
185
+ /** Record the model the provider actually served (gen_ai.response.model). */
186
+ setResponseModel(model: string): void;
187
+ /** Alias of setResponseModel. */
188
+ setModel(model: string): void;
189
+ /** Extra span attributes reported mid-call. */
190
+ setAttributes(attributes: Attributes): void;
191
+ }
192
+ /** A completed LLM call reported after the fact via `trackLlmCall`. */
193
+ interface TrackedLlmCall extends LlmCallInfo, LlmUsage {
194
+ /** Wall-clock duration of the call, if measured. */
195
+ durationMs?: number;
196
+ /** Pass the thrown error to record the call as failed. */
197
+ error?: unknown;
198
+ }
77
199
  interface AutterServer {
78
200
  /** Report a handled exception. Always recorded (never sampled out). */
79
201
  captureException(error: unknown, attributes?: Attributes): void;
@@ -83,6 +205,41 @@ interface AutterServer {
83
205
  * errors with a lower severity so aggregations can slice one dataset.
84
206
  */
85
207
  captureMessage(message: string, severity?: AutterSeverity, attributes?: Attributes): void;
208
+ /**
209
+ * Wrap a named unit of work — a background job, queue consumer, cron
210
+ * tick, or DB-heavy call — in an always-recorded span. Unlike regular
211
+ * traces these are never head-sampled, so Autter's slow-process monitor
212
+ * sees accurate run counts and durations for non-HTTP work. Nested
213
+ * instrumented calls run inside the span's context, so slow traces show
214
+ * where the time went. Rethrows whatever `fn` throws.
215
+ */
216
+ withProcessSpan<T>(name: string, fn: () => T | Promise<T>, attributes?: Attributes): Promise<T>;
217
+ /**
218
+ * Wrap one LLM provider call. Always recorded (never head-sampled), so
219
+ * Autter's LLM usage/cost dashboards and anomaly monitor see every call.
220
+ * Report tokens via the handle once the response arrives; cost is
221
+ * estimated from the model id unless `setCost(usd)` (or
222
+ * `setUsage({ costUsd })`) reports the exact figure. Rethrows whatever
223
+ * `fn` throws (failed calls are recorded with the error attached).
224
+ *
225
+ * const reply = await autter.withLlmCall(
226
+ * { provider: "openai", model: "gpt-5-mini", userId: user.id },
227
+ * async (llm) => {
228
+ * const res = await openai.chat.completions.create({ ... });
229
+ * llm.setUsage({
230
+ * inputTokens: res.usage?.prompt_tokens,
231
+ * outputTokens: res.usage?.completion_tokens,
232
+ * });
233
+ * return res;
234
+ * },
235
+ * );
236
+ */
237
+ withLlmCall<T>(info: LlmCallInfo, fn: (llm: LlmCallHandle) => T | Promise<T>): Promise<T>;
238
+ /**
239
+ * Report an already-completed LLM call — for codepaths where wrapping is
240
+ * awkward (queues, callbacks, batch results). Fire-and-forget.
241
+ */
242
+ trackLlmCall(call: TrackedLlmCall): void;
86
243
  /** Flush and stop all exporters. Call on graceful shutdown. */
87
244
  shutdown(): Promise<void>;
88
245
  }
@@ -90,7 +247,131 @@ declare function initAutterServer(options: AutterServerOptions): AutterServer;
90
247
  /** Module-level convenience — routes to the active server, or degrades to a
91
248
  * span on the global tracer so libraries can call it unconditionally. */
92
249
  declare function captureException(error: unknown, attributes?: Attributes): void;
250
+ /**
251
+ * Module-level convenience — see AutterServer.withProcessSpan. Before
252
+ * initAutterServer runs it degrades to the global tracer, where the span is
253
+ * subject to that provider's own sampling instead of the always-on pipe.
254
+ */
255
+ declare function withProcessSpan<T>(name: string, fn: () => T | Promise<T>, attributes?: Attributes): Promise<T>;
256
+ /**
257
+ * Module-level convenience — see AutterServer.withLlmCall. Before
258
+ * initAutterServer runs it degrades to the global tracer, where the span is
259
+ * subject to that provider's own sampling instead of the always-on pipe.
260
+ */
261
+ declare function withLlmCall<T>(info: LlmCallInfo, fn: (llm: LlmCallHandle) => T | Promise<T>): Promise<T>;
262
+ /** Module-level convenience — see AutterServer.trackLlmCall. */
263
+ declare function trackLlmCall(call: TrackedLlmCall): void;
93
264
  /** Module-level convenience for warnings/info — see AutterServer.captureMessage. */
94
265
  declare function captureMessage(message: string, severity?: AutterSeverity, attributes?: Attributes): void;
266
+ /** Capture surface with guaranteed redaction, regardless of how the host app
267
+ * configured initAutterServer — for library authors whose calls must never
268
+ * forward PII even when the host disabled it. Routes to the active server
269
+ * (or degrades to the global tracer exactly like captureException). */
270
+ interface SafeCapture {
271
+ captureException(error: unknown, attributes?: Attributes): void;
272
+ captureMessage(message: string, severity?: AutterSeverity, attributes?: Attributes): void;
273
+ }
274
+ /**
275
+ * Build a redacting capture wrapper:
276
+ *
277
+ * const safe = makeSafeCapture();
278
+ * safe.captureException(err, { "user.email": email }); // masked before export
279
+ *
280
+ * The returned functions have the same signatures as their plain twins;
281
+ * every attribute passes through redactAttributes() first. Note that when
282
+ * the host has NOT disabled redaction this is belt-and-braces (already on by
283
+ * default) — its value is enforcing privacy inside libraries and wrappers.
284
+ */
285
+ declare function makeSafeCapture(options?: RedactOptions): SafeCapture;
286
+ /**
287
+ * Emit one clearly-named fake LLM call (provider/model "autter-selftest",
288
+ * 1 input + 1 output token, cost 0) and force-flush it, returning the
289
+ * traceId — proves LLM traces reach the ingester without calling any real
290
+ * model. For setup verification only; requires initAutterServer.
291
+ */
292
+ declare function emitLlmSelftestTrace(): Promise<{
293
+ traceId: string;
294
+ }>;
295
+
296
+ interface AutterLogger {
297
+ log: (...args: unknown[]) => void;
298
+ warn: (...args: unknown[]) => void;
299
+ error: (...args: unknown[]) => void;
300
+ }
301
+ interface FlushTarget {
302
+ forceFlush(): Promise<unknown> | unknown;
303
+ }
304
+ interface AutoFlushOptions {
305
+ /**
306
+ * Targets to flush. Defaults to whatever initAutterServer has registered;
307
+ * pass your own for custom pipelines.
308
+ */
309
+ targets?: FlushTarget[];
310
+ /** Where lifecycle messages go. Default console. */
311
+ logger?: AutterLogger;
312
+ /** Log each successful flush ("flushed telemetry (SIGTERM)"). Default true. */
313
+ log?: boolean;
314
+ /**
315
+ * Warn on stderr when the process exits with captures that were never
316
+ * confirmed exported. Default true.
317
+ */
318
+ warnOnUnflushedExit?: boolean;
319
+ /**
320
+ * Max wall-clock time a single flush may take before giving up (and, on
321
+ * signals, proceeding to exit anyway). Default 3000 ms.
322
+ */
323
+ timeoutMs?: number;
324
+ /** Signals handled besides the defaults. Default ["SIGINT", "SIGTERM"]. */
325
+ signals?: string[];
326
+ }
327
+ interface AutoFlushHandle {
328
+ /** Force-flush now. Safe to call repeatedly; concurrent calls coalesce. */
329
+ flush(reason?: string): Promise<boolean>;
330
+ /** Remove every listener this install added. */
331
+ dispose(): void;
332
+ }
333
+ /**
334
+ * Flush all exporters when the process is going away:
335
+ *
336
+ * - `beforeExit` — event-loop drained naturally: force-flush, then hand the
337
+ * loop back (a kept-alive timer makes sure the async flush finishes
338
+ * inside the beforeExit window).
339
+ * - SIGINT / SIGTERM — flush, then exit with the conventional code (130 /
340
+ * 143). If YOUR code also listens to the signal, we only flush
341
+ * concurrently and let your handler decide the exit — installing this
342
+ * never changes an existing graceful-shutdown path. A second signal
343
+ * always exits immediately.
344
+ *
345
+ * Idempotent per options object; `initAutterServer` installs it by default
346
+ * (`autoFlush: false` opts out).
347
+ */
348
+ declare function installAutterAutoFlush(options?: AutoFlushOptions): AutoFlushHandle;
349
+
350
+ /**
351
+ * One-line LLM client auto-instrumentation:
352
+ *
353
+ * const openai = instrumentLlmClient(new OpenAI());
354
+ *
355
+ * Wraps a provider SDK instance (OpenAI, Anthropic, Google GenAI, or any
356
+ * client with the same call shapes) in a deep proxy. Every model invocation —
357
+ * a method like `create`/`stream`/`generateContent` called with an object
358
+ * argument carrying a `model` — is recorded as a gen_ai span on the
359
+ * always-recorded pipe: model, tokens, latency, errors, and (ingester-side)
360
+ * USD cost. Streaming responses are supported: the span stays open until the
361
+ * stream is consumed, picking up usage from the final chunks.
362
+ *
363
+ * Nothing about the client's behaviour changes; methods run with the
364
+ * original `this` (SDK private fields keep working) and results pass
365
+ * through untouched apart from the stream wrapper.
366
+ */
367
+ interface InstrumentLlmOptions {
368
+ /** Provider id ("openai", "anthropic", …). Default: detected from the client. */
369
+ provider?: string;
370
+ /** Opaque end-user id (never an email) — static or resolved per call. */
371
+ userId?: string | (() => string | undefined);
372
+ /** Extra attributes for every span from this client. */
373
+ attributes?: Attributes;
374
+ }
375
+ declare function instrumentLlmClient<T extends object>(client: T, options?: InstrumentLlmOptions): T;
95
376
 
96
- export { type AutterServer, type AutterServerOptions, type AutterSeverity, type RelayOptions, captureException, captureMessage, createBrowserRelayFetchHandler, createBrowserRelayHandler, initAutterServer, sanitizeBrowserPayload };
377
+ export { type AutoFlushHandle, type AutoFlushOptions, type AutterServer, type AutterServerOptions, type AutterSeverity, type FlushTarget, type InstrumentLlmOptions, type LlmCallHandle, type LlmCallInfo, type LlmUsage, type RedactOptions, type RelayOptions, type SafeCapture, type TrackedLlmCall, captureException, captureMessage, createBrowserRelayFetchHandler, createBrowserRelayHandler, emitLlmSelftestTrace, initAutterServer, installAutterAutoFlush, instrumentLlmClient, makeSafeCapture, redactAttributes, sanitizeBrowserPayload, trackLlmCall, withLlmCall, withProcessSpan };