@anuma/sdk 6.33.1 → 6.34.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.
Files changed (45) hide show
  1. package/dist/design/index.cjs +0 -2
  2. package/dist/design/index.d.mts +0 -7
  3. package/dist/design/index.d.ts +0 -7
  4. package/dist/design/index.mjs +0 -2
  5. package/dist/expo/{chunk-XD6WOQ5I.mjs → chunk-GQBTAM6Y.mjs} +0 -70
  6. package/dist/expo/{consolidate-7DBUHQRK.mjs → consolidate-3H2WY3FU.mjs} +1 -8
  7. package/dist/expo/index.cjs +15947 -16025
  8. package/dist/expo/index.d.mts +5934 -6646
  9. package/dist/expo/index.d.ts +5934 -6646
  10. package/dist/expo/index.mjs +15996 -15997
  11. package/dist/index.cjs +2289 -2430
  12. package/dist/index.d.mts +914 -1054
  13. package/dist/index.d.ts +914 -1054
  14. package/dist/index.mjs +2290 -2429
  15. package/dist/memory/context.d.mts +0 -73
  16. package/dist/memory/context.d.ts +0 -73
  17. package/dist/next/index.cjs +1 -8
  18. package/dist/next/index.mjs +1 -8
  19. package/dist/pii/transformers.d.mts +0 -32
  20. package/dist/pii/transformers.d.ts +0 -32
  21. package/dist/react/{chunk-XNQRJBIH.mjs → chunk-UFO4W3JX.mjs} +0 -70
  22. package/dist/react/{consolidate-5DVINCUE.mjs → consolidate-RHIAUBT4.mjs} +1 -8
  23. package/dist/react/index.cjs +33267 -33441
  24. package/dist/react/index.d.mts +13598 -14550
  25. package/dist/react/index.d.ts +13598 -14550
  26. package/dist/react/index.mjs +32920 -33019
  27. package/dist/server/{chunk-KSYV7UO2.mjs → chunk-4RGGAOAJ.mjs} +835 -905
  28. package/dist/server/{consolidate-MCNBSUXD.mjs → consolidate-YEGACWLR.mjs} +1 -8
  29. package/dist/server/index.cjs +22357 -23132
  30. package/dist/server/index.d.mts +7895 -8768
  31. package/dist/server/index.d.ts +7895 -8768
  32. package/dist/server/index.mjs +20548 -21258
  33. package/dist/telemetry/index.cjs +0 -9
  34. package/dist/telemetry/index.d.mts +86 -197
  35. package/dist/telemetry/index.d.ts +86 -197
  36. package/dist/telemetry/index.mjs +0 -9
  37. package/dist/tools/index.cjs +13036 -13347
  38. package/dist/tools/index.d.mts +2434 -2729
  39. package/dist/tools/index.d.ts +2434 -2729
  40. package/dist/tools/index.mjs +13036 -13347
  41. package/dist/tools/selection/index.cjs +18 -122
  42. package/dist/tools/selection/index.d.mts +0 -159
  43. package/dist/tools/selection/index.d.ts +0 -159
  44. package/dist/tools/selection/index.mjs +18 -122
  45. package/package.json +1 -1
@@ -213,16 +213,7 @@ function createRecallDiagnosticsHandler(sink) {
213
213
  usedBudget: diagnostics.usedBudget,
214
214
  reranked: diagnostics.reranked,
215
215
  candidateCount: diagnostics.candidateCount,
216
- // What the caller actually GOT. `candidateCount` is what was considered,
217
- // and the two are routinely far apart — the fact lane pulls `limit * 2`
218
- // when fusing — so a consumer reading candidateCount as "memories this
219
- // turn received" overcounts.
220
216
  admittedCount: diagnostics.admittedCount,
221
- // Both ends of the admitted range, and the lane counts, ride the EVENT as
222
- // well as the metrics: a track-only sink is a supported configuration
223
- // (the module's own PostHog example is exactly that shape), and it could
224
- // otherwise see neither the bottom of the score range nor which side lane
225
- // contributed.
226
217
  topScore: diagnostics.topScore,
227
218
  lowestAdmittedScore: diagnostics.lowestAdmittedScore,
228
219
  minScoreApplied: diagnostics.minScoreApplied,
@@ -96,17 +96,6 @@ type LlmapiChatCompletionTool = {
96
96
  [key: string]: unknown;
97
97
  };
98
98
 
99
- /**
100
- * Lifecycle hooks for runToolLoop. All hooks are optional and observer
101
- * errors are swallowed (sync throws and async rejections both) so a buggy
102
- * hook can't crash the loop. Hook names match the Claude Agent SDK shape
103
- * (camelCase).
104
- *
105
- * Hooks are awaited synchronously at each fire site, so slow hooks
106
- * serialize into loop latency — keep them fast and do any heavy work
107
- * (network exports, file IO) asynchronously inside your own queue.
108
- */
109
-
110
99
  type LlmTokenUsage = {
111
100
  inputTokens?: number;
112
101
  outputTokens?: number;
@@ -228,156 +217,6 @@ type RunHooks = {
228
217
  afterToolUse?: (e: ToolUseEndEvent) => Promise<void> | void;
229
218
  };
230
219
 
231
- /**
232
- * Telemetry sink contract for the observability adapter.
233
- *
234
- * The SDK never talks to a vendor directly — it emits structured events and
235
- * metrics through this interface, and the app adapts it to PostHog, Datadog,
236
- * OpenTelemetry, etc. Both methods are optional so a sink that only cares
237
- * about events (or only about metrics) can implement half the surface.
238
- *
239
- * Implementations must be fast and must not throw: hooks are awaited
240
- * synchronously inside the tool loop (see `runHooks.ts`), so a slow or
241
- * throwing sink serializes into run latency or — worse — surfaces as a run
242
- * error. Queue network exports asynchronously inside your sink.
243
- *
244
- * @example
245
- * ```ts
246
- * import { createMetricsHooks, type TelemetrySink } from "@anuma/sdk/telemetry";
247
- *
248
- * // posthog-js: capture(event, properties)
249
- * const sink: TelemetrySink = {
250
- * track: (event, properties) => posthog.capture(event, properties),
251
- * metric: (name, value, tags) => statsd.histogram(name, value, tags),
252
- * };
253
- *
254
- * // posthog-node takes ONE object, so the arguments do not line up:
255
- * // track: (event, properties) => posthog.capture({ distinctId, event, properties })
256
- *
257
- * const hooks = createMetricsHooks(sink);
258
- * // pass `hooks` wherever RunHooks are accepted, or compose with your own:
259
- * // composeHooks([hooks, myTracingHooks])
260
- * ```
261
- */
262
- interface TelemetrySink {
263
- /**
264
- * Record a discrete event (run started, tool failed, recall degraded…).
265
- * `properties` are flat, JSON-serializable, and free of message contents —
266
- * only identifiers, counts, and timings.
267
- */
268
- track?: (event: string, properties: Record<string, unknown>) => void;
269
- /**
270
- * Record a numeric measurement (latency, duration, token count). `tags`
271
- * are low-cardinality string dimensions (model, toolName, outcome, lane).
272
- */
273
- metric?: (name: string, value: number, tags: Record<string, string>) => void;
274
- }
275
- /**
276
- * Default sink that discards everything. Useful as a fallback when telemetry
277
- * is configured off, and as the base for sinks that override one method.
278
- *
279
- * Frozen. It is a published singleton, and `createMetricsHooks` closes over the
280
- * object rather than its methods, so one module assigning `noopTelemetrySink.track`
281
- * would reroute every already-constructed adapter that shares the reference — a
282
- * test stub that forgets to restore changes behaviour for the rest of the suite.
283
- * Freezing now rather than later: adding it later is the breaking direction,
284
- * because it turns a silent assignment into a strict-mode TypeError.
285
- */
286
- declare const noopTelemetrySink: TelemetrySink;
287
-
288
- /**
289
- * {@link RunHooks} adapter that turns tool-loop lifecycle hooks into
290
- * telemetry events and metrics on a {@link TelemetrySink}.
291
- *
292
- * Events emitted (all with `runId`; no message contents or tool arguments):
293
- *
294
- * - `run.started` — `{ runId, model }`
295
- * - `run.completed` — `{ runId, totalSteps, durationMs }`
296
- * - `run.failed` — `{ runId, durationMs, errorType, stage }`
297
- * - `model.call.completed` — `{ runId, stepIndex, latencyMs, model?, inputTokens?, outputTokens?, finishReason? }`
298
- * - `model.call.failed` — `{ runId, stepIndex, latencyMs, model? }`
299
- * - `tool.call.completed` — `{ runId, stepIndex, toolCallId, toolName, durationMs }`
300
- * - `tool.call.failed` — `{ runId, stepIndex, toolCallId, toolName, durationMs, errorType }`
301
- *
302
- * The raw `error` message string is NOT reported unless the caller opts in
303
- * with {@link MetricsHooksOptions.includeErrorMessages} — see that option for
304
- * why. `errorType` is always safe and always reported where the source event
305
- * carries one.
306
- *
307
- * Metrics emitted:
308
- *
309
- * - `run.duration` (ms, tags: model?, outcome)
310
- * - `model.call.latency` (ms, tags: model?, outcome)
311
- * - `model.call.tokens` (count, tags: direction: input|output) when usage is present
312
- * - `tool.call.duration` (ms, tags: toolName, outcome, errorType?)
313
- *
314
- * Pairing notes (mirrors the contract documented in `runHooks.ts`):
315
- *
316
- * - `onRunEnd` / `onRunError` are mutually exclusive and fire exactly once per
317
- * run, so every started run resolves to exactly one terminal event.
318
- * - Server-side tools routed via `onToolCall` get `beforeToolUse` with no
319
- * `afterToolUse`. The adapter therefore never emits a completion for a tool
320
- * it saw only start; the pending timer is dropped when the run terminates.
321
- * - `afterToolUse` without a matching `beforeToolUse` (defensive — not a
322
- * documented case) is still reported, with `durationMs` omitted.
323
- * - Per-run state is keyed by `runId` and cleared on the terminal hook, so
324
- * concurrent runs and long-lived adapter instances do not leak timers. The
325
- * `after*` hooks read that state without creating it, so a hook arriving
326
- * after the terminal one cannot resurrect an entry nothing would delete
327
- * again.
328
- */
329
-
330
- interface MetricsHooksOptions {
331
- /**
332
- * Override the clock used for every duration this adapter reports.
333
- *
334
- * The default is monotonic where the runtime has `performance.now()`
335
- * (browser / RN / Node), falling back to `Date.now()`. That is about
336
- * monotonicity, not precision: `Date.now()` steps BACKWARD on an NTP
337
- * correction, a VM resume from snapshot, or a container host clock sync, and
338
- * a step landing mid-run makes `now() - startedAt` negative. dogstatsd
339
- * accepts a negative histogram sample, so it is ingested and corrupts the
340
- * min/p50/p95 for that bucket — silently wrong data, which is the one thing
341
- * this module exists to prevent.
342
- *
343
- * Supply your own only to make durations deterministic in a test.
344
- */
345
- now?: () => number;
346
- /**
347
- * Report the raw `error` message string on `run.failed`,
348
- * `model.call.failed`, and `tool.call.failed`. Default `false`.
349
- *
350
- * Off by default because those strings are free text and routinely quote
351
- * the input that caused the failure. `JSON.parse` echoes the offending tool
352
- * arguments verbatim (`Unexpected token 'h', "hunter2pass"... is not valid
353
- * JSON`), `executeToolCall` wraps whatever a host executor threw, and an
354
- * executor with `deAnonymizeArgs` runs on de-redacted PII. Sending them to
355
- * a sink contradicts {@link TelemetrySink}, which states that properties
356
- * carry only identifiers, counts, and timings.
357
- *
358
- * Turn it on when the sink is infrastructure you own and you accept the
359
- * exposure. Prefer `errorType`, which is always reported and always safe.
360
- */
361
- includeErrorMessages?: boolean;
362
- }
363
- /**
364
- * Build {@link RunHooks} that report run lifecycle, model calls, and tool
365
- * calls to `sink`. The returned hooks are synchronous and never throw — a
366
- * throwing sink method is swallowed with a console-free `noop` (the tool loop
367
- * already swallows hook errors; we avoid double-logging here).
368
- */
369
- declare function createMetricsHooks(sink: TelemetrySink, opts?: MetricsHooksOptions): RunHooks;
370
-
371
- /**
372
- * Unified memory layer types — the Recall / Retain API.
373
- *
374
- * This module defines the public surface for the new unified memory layer
375
- * that sits above `memoryVault` (curated facts) and `memoryEngine` (raw
376
- * conversation chunks). Callers should prefer these types over the legacy
377
- * `VaultSearchResult` / `ChunkSearchResult` shapes; the underlying stores
378
- * may converge later, the API will not.
379
- */
380
-
381
220
  /**
382
221
  * Budget controls retrieval depth/cost. Higher budgets enable more
383
222
  * candidate sources and the cross-encoder reranker.
@@ -594,51 +433,101 @@ interface RecallDiagnostics {
594
433
  }
595
434
 
596
435
  /**
597
- * Adapter that turns {@link RecallDiagnostics} (the `onDiagnostics` hook on
598
- * {@link RecallOptions}) into telemetry events and metrics on a
599
- * {@link TelemetrySink}.
436
+ * Telemetry sink contract for the observability adapter.
600
437
  *
601
- * Events emitted:
438
+ * The SDK never talks to a vendor directly — it emits structured events and
439
+ * metrics through this interface, and the app adapts it to PostHog, Datadog,
440
+ * OpenTelemetry, etc. Both methods are optional so a sink that only cares
441
+ * about events (or only about metrics) can implement half the surface.
602
442
  *
603
- * - `recall.completed` — once per `recall()` call with budget, lane counts,
604
- * and whether the cross-encoder reranked.
605
- * - `recall.degraded` — once per entry in `diagnostics.degraded`, with the
606
- * degradation reason. A clean recall emits none.
443
+ * Implementations must be fast and must not throw: hooks are awaited
444
+ * synchronously inside the tool loop (see `runHooks.ts`), so a slow or
445
+ * throwing sink serializes into run latency or — worse — surfaces as a run
446
+ * error. Queue network exports asynchronously inside your sink.
607
447
  *
608
- * Metrics emitted (all tagged `lane`):
448
+ * @example
449
+ * ```ts
450
+ * import { createMetricsHooks, type TelemetrySink } from "@anuma/sdk/telemetry";
609
451
  *
610
- * - `recall.duration` (ms) for each timing lane: `total`, `prep`, `factLane`,
611
- * `rerank`, `queryEmbed`, `chunkLane`, `fuse`. `rerank` and `queryEmbed`
612
- * are SUBSETS of `factLane` — do not sum the lanes.
613
- * - `recall.candidates` (count), `recall.facts` (count), `recall.chunks` (count)
614
- * - `recall.vault.size`, `recall.vault.rows_decrypted`,
615
- * `recall.vault.rows_embedded` when the fact lane reported them.
452
+ * // posthog-js: capture(event, properties)
453
+ * const sink: TelemetrySink = {
454
+ * track: (event, properties) => posthog.capture(event, properties),
455
+ * metric: (name, value, tags) => statsd.histogram(name, value, tags),
456
+ * };
616
457
  *
617
- * COVERAGE — this handler only sees recalls the host starts itself: a direct
618
- * `recall()` call, or `reflect()` (`ReflectOptions extends RecallOptions`, and
619
- * reflect forwards the options object). It does NOT see recalls that happen
620
- * inside a tool-loop run. `RecallOptions.onDiagnostics` is a single slot, not a
621
- * composable hook list, and both in-run paths already occupy it to read
622
- * `degraded` for themselves — the recall tool (`lib/memory/recallTool.ts`) and
623
- * the vault search tool (`lib/memoryVault/searchTool.ts`). Neither forwards a
624
- * caller's callback, and neither exposes the slot on its own options.
458
+ * // posthog-node takes ONE object, so the arguments do not line up:
459
+ * // track: (event, properties) => posthog.capture({ distinctId, event, properties })
625
460
  *
626
- * Two consequences to plan for:
461
+ * const hooks = createMetricsHooks(sink);
462
+ * // pass `hooks` wherever RunHooks are accepted, or compose with your own:
463
+ * // composeHooks([hooks, myTracingHooks])
464
+ * ```
465
+ */
466
+ interface TelemetrySink {
467
+ /**
468
+ * Record a discrete event (run started, tool failed, recall degraded…).
469
+ * `properties` are flat, JSON-serializable, and free of message contents —
470
+ * only identifiers, counts, and timings.
471
+ */
472
+ track?: (event: string, properties: Record<string, unknown>) => void;
473
+ /**
474
+ * Record a numeric measurement (latency, duration, token count). `tags`
475
+ * are low-cardinality string dimensions (model, toolName, outcome, lane).
476
+ */
477
+ metric?: (name: string, value: number, tags: Record<string, string>) => void;
478
+ }
479
+ /**
480
+ * Default sink that discards everything. Useful as a fallback when telemetry
481
+ * is configured off, and as the base for sinks that override one method.
627
482
  *
628
- * - Event volume reports host-initiated recall only. A near-empty
629
- * `recall.completed` stream means the app recalls through the tools. It does
630
- * not mean recall is broken or that the sink is misconfigured.
631
- * - These events carry no `runId`, so you cannot join them to `run.failed` from
632
- * `createMetricsHooks`. That join needs two changes in the memory layer, not
633
- * here: make `onDiagnostics` forwardable through both tools, and thread the
634
- * loop's `runId` (minted in `lib/chat/toolLoop.ts`) across the executor
635
- * boundary onto `RecallDiagnostics`.
483
+ * Frozen. It is a published singleton, and `createMetricsHooks` closes over the
484
+ * object rather than its methods, so one module assigning `noopTelemetrySink.track`
485
+ * would reroute every already-constructed adapter that shares the reference — a
486
+ * test stub that forgets to restore changes behaviour for the rest of the suite.
487
+ * Freezing now rather than later: adding it later is the breaking direction,
488
+ * because it turns a silent assignment into a strict-mode TypeError.
489
+ */
490
+ declare const noopTelemetrySink: TelemetrySink;
491
+
492
+ interface MetricsHooksOptions {
493
+ /**
494
+ * Override the clock used for every duration this adapter reports.
495
+ *
496
+ * The default is monotonic where the runtime has `performance.now()`
497
+ * (browser / RN / Node), falling back to `Date.now()`. That is about
498
+ * monotonicity, not precision: `Date.now()` steps BACKWARD on an NTP
499
+ * correction, a VM resume from snapshot, or a container host clock sync, and
500
+ * a step landing mid-run makes `now() - startedAt` negative. dogstatsd
501
+ * accepts a negative histogram sample, so it is ingested and corrupts the
502
+ * min/p50/p95 for that bucket — silently wrong data, which is the one thing
503
+ * this module exists to prevent.
504
+ *
505
+ * Supply your own only to make durations deterministic in a test.
506
+ */
507
+ now?: () => number;
508
+ /**
509
+ * Report the raw `error` message string on `run.failed`,
510
+ * `model.call.failed`, and `tool.call.failed`. Default `false`.
511
+ *
512
+ * Off by default because those strings are free text and routinely quote
513
+ * the input that caused the failure. `JSON.parse` echoes the offending tool
514
+ * arguments verbatim (`Unexpected token 'h', "hunter2pass"... is not valid
515
+ * JSON`), `executeToolCall` wraps whatever a host executor threw, and an
516
+ * executor with `deAnonymizeArgs` runs on de-redacted PII. Sending them to
517
+ * a sink contradicts {@link TelemetrySink}, which states that properties
518
+ * carry only identifiers, counts, and timings.
519
+ *
520
+ * Turn it on when the sink is infrastructure you own and you accept the
521
+ * exposure. Prefer `errorType`, which is always reported and always safe.
522
+ */
523
+ includeErrorMessages?: boolean;
524
+ }
525
+ /**
526
+ * Build {@link RunHooks} that report run lifecycle, model calls, and tool calls to `sink`; every run ends in exactly one `run.completed` or `run.failed`, and no message contents or tool arguments are emitted.
636
527
  *
637
- * A host that already holds a `runId` can stamp it today with no signature
638
- * change: wrap the sink rather than the handler, and put it on `track`
639
- * properties only. A metric tag of that cardinality is a problem for the
640
- * backend — see {@link TelemetrySink} on keeping tags low-cardinality.
528
+ * @see TELEMETRY.md for event payloads and metric names.
641
529
  */
530
+ declare function createMetricsHooks(sink: TelemetrySink, opts?: MetricsHooksOptions): RunHooks;
642
531
 
643
532
  /**
644
533
  * Build an `onDiagnostics` callback that forwards recall diagnostics to
@@ -96,17 +96,6 @@ type LlmapiChatCompletionTool = {
96
96
  [key: string]: unknown;
97
97
  };
98
98
 
99
- /**
100
- * Lifecycle hooks for runToolLoop. All hooks are optional and observer
101
- * errors are swallowed (sync throws and async rejections both) so a buggy
102
- * hook can't crash the loop. Hook names match the Claude Agent SDK shape
103
- * (camelCase).
104
- *
105
- * Hooks are awaited synchronously at each fire site, so slow hooks
106
- * serialize into loop latency — keep them fast and do any heavy work
107
- * (network exports, file IO) asynchronously inside your own queue.
108
- */
109
-
110
99
  type LlmTokenUsage = {
111
100
  inputTokens?: number;
112
101
  outputTokens?: number;
@@ -228,156 +217,6 @@ type RunHooks = {
228
217
  afterToolUse?: (e: ToolUseEndEvent) => Promise<void> | void;
229
218
  };
230
219
 
231
- /**
232
- * Telemetry sink contract for the observability adapter.
233
- *
234
- * The SDK never talks to a vendor directly — it emits structured events and
235
- * metrics through this interface, and the app adapts it to PostHog, Datadog,
236
- * OpenTelemetry, etc. Both methods are optional so a sink that only cares
237
- * about events (or only about metrics) can implement half the surface.
238
- *
239
- * Implementations must be fast and must not throw: hooks are awaited
240
- * synchronously inside the tool loop (see `runHooks.ts`), so a slow or
241
- * throwing sink serializes into run latency or — worse — surfaces as a run
242
- * error. Queue network exports asynchronously inside your sink.
243
- *
244
- * @example
245
- * ```ts
246
- * import { createMetricsHooks, type TelemetrySink } from "@anuma/sdk/telemetry";
247
- *
248
- * // posthog-js: capture(event, properties)
249
- * const sink: TelemetrySink = {
250
- * track: (event, properties) => posthog.capture(event, properties),
251
- * metric: (name, value, tags) => statsd.histogram(name, value, tags),
252
- * };
253
- *
254
- * // posthog-node takes ONE object, so the arguments do not line up:
255
- * // track: (event, properties) => posthog.capture({ distinctId, event, properties })
256
- *
257
- * const hooks = createMetricsHooks(sink);
258
- * // pass `hooks` wherever RunHooks are accepted, or compose with your own:
259
- * // composeHooks([hooks, myTracingHooks])
260
- * ```
261
- */
262
- interface TelemetrySink {
263
- /**
264
- * Record a discrete event (run started, tool failed, recall degraded…).
265
- * `properties` are flat, JSON-serializable, and free of message contents —
266
- * only identifiers, counts, and timings.
267
- */
268
- track?: (event: string, properties: Record<string, unknown>) => void;
269
- /**
270
- * Record a numeric measurement (latency, duration, token count). `tags`
271
- * are low-cardinality string dimensions (model, toolName, outcome, lane).
272
- */
273
- metric?: (name: string, value: number, tags: Record<string, string>) => void;
274
- }
275
- /**
276
- * Default sink that discards everything. Useful as a fallback when telemetry
277
- * is configured off, and as the base for sinks that override one method.
278
- *
279
- * Frozen. It is a published singleton, and `createMetricsHooks` closes over the
280
- * object rather than its methods, so one module assigning `noopTelemetrySink.track`
281
- * would reroute every already-constructed adapter that shares the reference — a
282
- * test stub that forgets to restore changes behaviour for the rest of the suite.
283
- * Freezing now rather than later: adding it later is the breaking direction,
284
- * because it turns a silent assignment into a strict-mode TypeError.
285
- */
286
- declare const noopTelemetrySink: TelemetrySink;
287
-
288
- /**
289
- * {@link RunHooks} adapter that turns tool-loop lifecycle hooks into
290
- * telemetry events and metrics on a {@link TelemetrySink}.
291
- *
292
- * Events emitted (all with `runId`; no message contents or tool arguments):
293
- *
294
- * - `run.started` — `{ runId, model }`
295
- * - `run.completed` — `{ runId, totalSteps, durationMs }`
296
- * - `run.failed` — `{ runId, durationMs, errorType, stage }`
297
- * - `model.call.completed` — `{ runId, stepIndex, latencyMs, model?, inputTokens?, outputTokens?, finishReason? }`
298
- * - `model.call.failed` — `{ runId, stepIndex, latencyMs, model? }`
299
- * - `tool.call.completed` — `{ runId, stepIndex, toolCallId, toolName, durationMs }`
300
- * - `tool.call.failed` — `{ runId, stepIndex, toolCallId, toolName, durationMs, errorType }`
301
- *
302
- * The raw `error` message string is NOT reported unless the caller opts in
303
- * with {@link MetricsHooksOptions.includeErrorMessages} — see that option for
304
- * why. `errorType` is always safe and always reported where the source event
305
- * carries one.
306
- *
307
- * Metrics emitted:
308
- *
309
- * - `run.duration` (ms, tags: model?, outcome)
310
- * - `model.call.latency` (ms, tags: model?, outcome)
311
- * - `model.call.tokens` (count, tags: direction: input|output) when usage is present
312
- * - `tool.call.duration` (ms, tags: toolName, outcome, errorType?)
313
- *
314
- * Pairing notes (mirrors the contract documented in `runHooks.ts`):
315
- *
316
- * - `onRunEnd` / `onRunError` are mutually exclusive and fire exactly once per
317
- * run, so every started run resolves to exactly one terminal event.
318
- * - Server-side tools routed via `onToolCall` get `beforeToolUse` with no
319
- * `afterToolUse`. The adapter therefore never emits a completion for a tool
320
- * it saw only start; the pending timer is dropped when the run terminates.
321
- * - `afterToolUse` without a matching `beforeToolUse` (defensive — not a
322
- * documented case) is still reported, with `durationMs` omitted.
323
- * - Per-run state is keyed by `runId` and cleared on the terminal hook, so
324
- * concurrent runs and long-lived adapter instances do not leak timers. The
325
- * `after*` hooks read that state without creating it, so a hook arriving
326
- * after the terminal one cannot resurrect an entry nothing would delete
327
- * again.
328
- */
329
-
330
- interface MetricsHooksOptions {
331
- /**
332
- * Override the clock used for every duration this adapter reports.
333
- *
334
- * The default is monotonic where the runtime has `performance.now()`
335
- * (browser / RN / Node), falling back to `Date.now()`. That is about
336
- * monotonicity, not precision: `Date.now()` steps BACKWARD on an NTP
337
- * correction, a VM resume from snapshot, or a container host clock sync, and
338
- * a step landing mid-run makes `now() - startedAt` negative. dogstatsd
339
- * accepts a negative histogram sample, so it is ingested and corrupts the
340
- * min/p50/p95 for that bucket — silently wrong data, which is the one thing
341
- * this module exists to prevent.
342
- *
343
- * Supply your own only to make durations deterministic in a test.
344
- */
345
- now?: () => number;
346
- /**
347
- * Report the raw `error` message string on `run.failed`,
348
- * `model.call.failed`, and `tool.call.failed`. Default `false`.
349
- *
350
- * Off by default because those strings are free text and routinely quote
351
- * the input that caused the failure. `JSON.parse` echoes the offending tool
352
- * arguments verbatim (`Unexpected token 'h', "hunter2pass"... is not valid
353
- * JSON`), `executeToolCall` wraps whatever a host executor threw, and an
354
- * executor with `deAnonymizeArgs` runs on de-redacted PII. Sending them to
355
- * a sink contradicts {@link TelemetrySink}, which states that properties
356
- * carry only identifiers, counts, and timings.
357
- *
358
- * Turn it on when the sink is infrastructure you own and you accept the
359
- * exposure. Prefer `errorType`, which is always reported and always safe.
360
- */
361
- includeErrorMessages?: boolean;
362
- }
363
- /**
364
- * Build {@link RunHooks} that report run lifecycle, model calls, and tool
365
- * calls to `sink`. The returned hooks are synchronous and never throw — a
366
- * throwing sink method is swallowed with a console-free `noop` (the tool loop
367
- * already swallows hook errors; we avoid double-logging here).
368
- */
369
- declare function createMetricsHooks(sink: TelemetrySink, opts?: MetricsHooksOptions): RunHooks;
370
-
371
- /**
372
- * Unified memory layer types — the Recall / Retain API.
373
- *
374
- * This module defines the public surface for the new unified memory layer
375
- * that sits above `memoryVault` (curated facts) and `memoryEngine` (raw
376
- * conversation chunks). Callers should prefer these types over the legacy
377
- * `VaultSearchResult` / `ChunkSearchResult` shapes; the underlying stores
378
- * may converge later, the API will not.
379
- */
380
-
381
220
  /**
382
221
  * Budget controls retrieval depth/cost. Higher budgets enable more
383
222
  * candidate sources and the cross-encoder reranker.
@@ -594,51 +433,101 @@ interface RecallDiagnostics {
594
433
  }
595
434
 
596
435
  /**
597
- * Adapter that turns {@link RecallDiagnostics} (the `onDiagnostics` hook on
598
- * {@link RecallOptions}) into telemetry events and metrics on a
599
- * {@link TelemetrySink}.
436
+ * Telemetry sink contract for the observability adapter.
600
437
  *
601
- * Events emitted:
438
+ * The SDK never talks to a vendor directly — it emits structured events and
439
+ * metrics through this interface, and the app adapts it to PostHog, Datadog,
440
+ * OpenTelemetry, etc. Both methods are optional so a sink that only cares
441
+ * about events (or only about metrics) can implement half the surface.
602
442
  *
603
- * - `recall.completed` — once per `recall()` call with budget, lane counts,
604
- * and whether the cross-encoder reranked.
605
- * - `recall.degraded` — once per entry in `diagnostics.degraded`, with the
606
- * degradation reason. A clean recall emits none.
443
+ * Implementations must be fast and must not throw: hooks are awaited
444
+ * synchronously inside the tool loop (see `runHooks.ts`), so a slow or
445
+ * throwing sink serializes into run latency or — worse — surfaces as a run
446
+ * error. Queue network exports asynchronously inside your sink.
607
447
  *
608
- * Metrics emitted (all tagged `lane`):
448
+ * @example
449
+ * ```ts
450
+ * import { createMetricsHooks, type TelemetrySink } from "@anuma/sdk/telemetry";
609
451
  *
610
- * - `recall.duration` (ms) for each timing lane: `total`, `prep`, `factLane`,
611
- * `rerank`, `queryEmbed`, `chunkLane`, `fuse`. `rerank` and `queryEmbed`
612
- * are SUBSETS of `factLane` — do not sum the lanes.
613
- * - `recall.candidates` (count), `recall.facts` (count), `recall.chunks` (count)
614
- * - `recall.vault.size`, `recall.vault.rows_decrypted`,
615
- * `recall.vault.rows_embedded` when the fact lane reported them.
452
+ * // posthog-js: capture(event, properties)
453
+ * const sink: TelemetrySink = {
454
+ * track: (event, properties) => posthog.capture(event, properties),
455
+ * metric: (name, value, tags) => statsd.histogram(name, value, tags),
456
+ * };
616
457
  *
617
- * COVERAGE — this handler only sees recalls the host starts itself: a direct
618
- * `recall()` call, or `reflect()` (`ReflectOptions extends RecallOptions`, and
619
- * reflect forwards the options object). It does NOT see recalls that happen
620
- * inside a tool-loop run. `RecallOptions.onDiagnostics` is a single slot, not a
621
- * composable hook list, and both in-run paths already occupy it to read
622
- * `degraded` for themselves — the recall tool (`lib/memory/recallTool.ts`) and
623
- * the vault search tool (`lib/memoryVault/searchTool.ts`). Neither forwards a
624
- * caller's callback, and neither exposes the slot on its own options.
458
+ * // posthog-node takes ONE object, so the arguments do not line up:
459
+ * // track: (event, properties) => posthog.capture({ distinctId, event, properties })
625
460
  *
626
- * Two consequences to plan for:
461
+ * const hooks = createMetricsHooks(sink);
462
+ * // pass `hooks` wherever RunHooks are accepted, or compose with your own:
463
+ * // composeHooks([hooks, myTracingHooks])
464
+ * ```
465
+ */
466
+ interface TelemetrySink {
467
+ /**
468
+ * Record a discrete event (run started, tool failed, recall degraded…).
469
+ * `properties` are flat, JSON-serializable, and free of message contents —
470
+ * only identifiers, counts, and timings.
471
+ */
472
+ track?: (event: string, properties: Record<string, unknown>) => void;
473
+ /**
474
+ * Record a numeric measurement (latency, duration, token count). `tags`
475
+ * are low-cardinality string dimensions (model, toolName, outcome, lane).
476
+ */
477
+ metric?: (name: string, value: number, tags: Record<string, string>) => void;
478
+ }
479
+ /**
480
+ * Default sink that discards everything. Useful as a fallback when telemetry
481
+ * is configured off, and as the base for sinks that override one method.
627
482
  *
628
- * - Event volume reports host-initiated recall only. A near-empty
629
- * `recall.completed` stream means the app recalls through the tools. It does
630
- * not mean recall is broken or that the sink is misconfigured.
631
- * - These events carry no `runId`, so you cannot join them to `run.failed` from
632
- * `createMetricsHooks`. That join needs two changes in the memory layer, not
633
- * here: make `onDiagnostics` forwardable through both tools, and thread the
634
- * loop's `runId` (minted in `lib/chat/toolLoop.ts`) across the executor
635
- * boundary onto `RecallDiagnostics`.
483
+ * Frozen. It is a published singleton, and `createMetricsHooks` closes over the
484
+ * object rather than its methods, so one module assigning `noopTelemetrySink.track`
485
+ * would reroute every already-constructed adapter that shares the reference — a
486
+ * test stub that forgets to restore changes behaviour for the rest of the suite.
487
+ * Freezing now rather than later: adding it later is the breaking direction,
488
+ * because it turns a silent assignment into a strict-mode TypeError.
489
+ */
490
+ declare const noopTelemetrySink: TelemetrySink;
491
+
492
+ interface MetricsHooksOptions {
493
+ /**
494
+ * Override the clock used for every duration this adapter reports.
495
+ *
496
+ * The default is monotonic where the runtime has `performance.now()`
497
+ * (browser / RN / Node), falling back to `Date.now()`. That is about
498
+ * monotonicity, not precision: `Date.now()` steps BACKWARD on an NTP
499
+ * correction, a VM resume from snapshot, or a container host clock sync, and
500
+ * a step landing mid-run makes `now() - startedAt` negative. dogstatsd
501
+ * accepts a negative histogram sample, so it is ingested and corrupts the
502
+ * min/p50/p95 for that bucket — silently wrong data, which is the one thing
503
+ * this module exists to prevent.
504
+ *
505
+ * Supply your own only to make durations deterministic in a test.
506
+ */
507
+ now?: () => number;
508
+ /**
509
+ * Report the raw `error` message string on `run.failed`,
510
+ * `model.call.failed`, and `tool.call.failed`. Default `false`.
511
+ *
512
+ * Off by default because those strings are free text and routinely quote
513
+ * the input that caused the failure. `JSON.parse` echoes the offending tool
514
+ * arguments verbatim (`Unexpected token 'h', "hunter2pass"... is not valid
515
+ * JSON`), `executeToolCall` wraps whatever a host executor threw, and an
516
+ * executor with `deAnonymizeArgs` runs on de-redacted PII. Sending them to
517
+ * a sink contradicts {@link TelemetrySink}, which states that properties
518
+ * carry only identifiers, counts, and timings.
519
+ *
520
+ * Turn it on when the sink is infrastructure you own and you accept the
521
+ * exposure. Prefer `errorType`, which is always reported and always safe.
522
+ */
523
+ includeErrorMessages?: boolean;
524
+ }
525
+ /**
526
+ * Build {@link RunHooks} that report run lifecycle, model calls, and tool calls to `sink`; every run ends in exactly one `run.completed` or `run.failed`, and no message contents or tool arguments are emitted.
636
527
  *
637
- * A host that already holds a `runId` can stamp it today with no signature
638
- * change: wrap the sink rather than the handler, and put it on `track`
639
- * properties only. A metric tag of that cardinality is a problem for the
640
- * backend — see {@link TelemetrySink} on keeping tags low-cardinality.
528
+ * @see TELEMETRY.md for event payloads and metric names.
641
529
  */
530
+ declare function createMetricsHooks(sink: TelemetrySink, opts?: MetricsHooksOptions): RunHooks;
642
531
 
643
532
  /**
644
533
  * Build an `onDiagnostics` callback that forwards recall diagnostics to