@warlock.js/ai-panoptic 5.30.0 → 5.32.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 (161) hide show
  1. package/CHANGELOG.md +25 -3
  2. package/README.md +2 -2
  3. package/cjs/index.cjs +38 -84
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/collector/collector.d.mts +3 -5
  6. package/esm/collector/collector.d.mts.map +1 -1
  7. package/esm/collector/content-capture.type.d.mts +4 -5
  8. package/esm/collector/content-capture.type.d.mts.map +1 -1
  9. package/esm/collector/extract-span-attributes.d.mts +1 -3
  10. package/esm/collector/extract-span-attributes.d.mts.map +1 -1
  11. package/esm/collector/extract-span-attributes.mjs +2 -4
  12. package/esm/collector/extract-span-attributes.mjs.map +1 -1
  13. package/esm/collector/index.d.mts +2 -1
  14. package/esm/collector/index.mjs +1 -1
  15. package/esm/collector/normalize-error.d.mts +1 -3
  16. package/esm/collector/normalize-error.d.mts.map +1 -1
  17. package/esm/collector/report-to-span.d.mts +1 -3
  18. package/esm/collector/report-to-span.d.mts.map +1 -1
  19. package/esm/collector/report-to-span.mjs.map +1 -1
  20. package/esm/collector/report-to-trace.d.mts +1 -3
  21. package/esm/collector/report-to-trace.d.mts.map +1 -1
  22. package/esm/config/apply-panoptic-config.d.mts +2 -3
  23. package/esm/config/apply-panoptic-config.d.mts.map +1 -1
  24. package/esm/config/apply-panoptic-config.mjs.map +1 -1
  25. package/esm/config/index.d.mts +2 -1
  26. package/esm/config/index.mjs +1 -1
  27. package/esm/config/panoptic-config.type.d.mts +2 -4
  28. package/esm/config/panoptic-config.type.d.mts.map +1 -1
  29. package/esm/contracts/collector.contract.d.mts +1 -3
  30. package/esm/contracts/collector.contract.d.mts.map +1 -1
  31. package/esm/contracts/exporter.contract.d.mts +1 -3
  32. package/esm/contracts/exporter.contract.d.mts.map +1 -1
  33. package/esm/contracts/index.d.mts +2 -1
  34. package/esm/contracts/trace.type.d.mts +35 -20
  35. package/esm/contracts/trace.type.d.mts.map +1 -1
  36. package/esm/dashboard/dashboard.d.mts +1 -3
  37. package/esm/dashboard/dashboard.d.mts.map +1 -1
  38. package/esm/dashboard/dashboard.mjs +3 -2
  39. package/esm/dashboard/dashboard.mjs.map +1 -1
  40. package/esm/dashboard/dashboard.type.d.mts +9 -7
  41. package/esm/dashboard/dashboard.type.d.mts.map +1 -1
  42. package/esm/dashboard/index.d.mts +4 -1
  43. package/esm/dashboard/index.mjs +1 -1
  44. package/esm/dashboard/parse-query.d.mts +1 -1
  45. package/esm/dashboard/parse-query.mjs +1 -0
  46. package/esm/dashboard/parse-query.mjs.map +1 -1
  47. package/esm/dashboard/serve.d.mts +2 -1
  48. package/esm/dashboard/serve.mjs +1 -1
  49. package/esm/dashboard/serve.mjs.map +1 -1
  50. package/esm/dashboard/trace-filter.d.mts +44 -32
  51. package/esm/dashboard/trace-filter.d.mts.map +1 -1
  52. package/esm/dashboard/trace-filter.mjs +1 -1
  53. package/esm/dashboard/trace-filter.mjs.map +1 -1
  54. package/esm/dashboard/ui.html.mjs +5 -4
  55. package/esm/dashboard/ui.html.mjs.map +1 -1
  56. package/esm/evaluate/evaluate-system-prompt.d.mts +1 -3
  57. package/esm/evaluate/evaluate-system-prompt.d.mts.map +1 -1
  58. package/esm/evaluate/evaluate-system-prompt.mjs +3 -1
  59. package/esm/evaluate/evaluate-system-prompt.mjs.map +1 -1
  60. package/esm/evaluate/evaluate.type.d.mts +2 -4
  61. package/esm/evaluate/evaluate.type.d.mts.map +1 -1
  62. package/esm/evaluate/extract-last-system-prompt.d.mts +1 -3
  63. package/esm/evaluate/extract-last-system-prompt.d.mts.map +1 -1
  64. package/esm/evaluate/find-span-by-id.d.mts +1 -3
  65. package/esm/evaluate/find-span-by-id.d.mts.map +1 -1
  66. package/esm/evaluate/index.d.mts +2 -1
  67. package/esm/evaluate/index.mjs +1 -1
  68. package/esm/exporters/console/console-exporter.d.mts +2 -3
  69. package/esm/exporters/console/console-exporter.d.mts.map +1 -1
  70. package/esm/exporters/console/console-exporter.mjs +4 -3
  71. package/esm/exporters/console/console-exporter.mjs.map +1 -1
  72. package/esm/exporters/console/console-exporter.type.d.mts +2 -3
  73. package/esm/exporters/console/console-exporter.type.d.mts.map +1 -1
  74. package/esm/exporters/console/format-span-io.d.mts +2 -2
  75. package/esm/exporters/console/format-span-io.d.mts.map +1 -1
  76. package/esm/exporters/console/format-span-io.mjs.map +1 -1
  77. package/esm/exporters/console/format-span-line.d.mts +2 -2
  78. package/esm/exporters/console/format-span-line.d.mts.map +1 -1
  79. package/esm/exporters/console/format-span-line.mjs +2 -0
  80. package/esm/exporters/console/format-span-line.mjs.map +1 -1
  81. package/esm/exporters/console/index.d.mts +2 -1
  82. package/esm/exporters/console/index.mjs +5 -0
  83. package/esm/exporters/file/file-exporter.d.mts +2 -3
  84. package/esm/exporters/file/file-exporter.d.mts.map +1 -1
  85. package/esm/exporters/file/file-exporter.mjs.map +1 -1
  86. package/esm/exporters/file/file-exporter.type.d.mts +8 -5
  87. package/esm/exporters/file/file-exporter.type.d.mts.map +1 -1
  88. package/esm/exporters/file/index.d.mts +2 -1
  89. package/esm/exporters/index.d.mts +7 -1
  90. package/esm/exporters/index.mjs +3 -1
  91. package/esm/exporters/langfuse/index.d.mts +2 -1
  92. package/esm/exporters/langfuse/index.mjs +1 -1
  93. package/esm/exporters/langfuse/langfuse-exporter.d.mts +2 -3
  94. package/esm/exporters/langfuse/langfuse-exporter.d.mts.map +1 -1
  95. package/esm/exporters/langfuse/langfuse-exporter.mjs +4 -3
  96. package/esm/exporters/langfuse/langfuse-exporter.mjs.map +1 -1
  97. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts +23 -17
  98. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts.map +1 -1
  99. package/esm/exporters/otel/index.d.mts +2 -1
  100. package/esm/exporters/otel/index.mjs +1 -1
  101. package/esm/exporters/otel/otel-api.shim.type.d.mts +6 -7
  102. package/esm/exporters/otel/otel-api.shim.type.d.mts.map +1 -1
  103. package/esm/exporters/otel/otel-exporter.d.mts +2 -3
  104. package/esm/exporters/otel/otel-exporter.d.mts.map +1 -1
  105. package/esm/exporters/otel/otel-exporter.mjs +2 -1
  106. package/esm/exporters/otel/otel-exporter.mjs.map +1 -1
  107. package/esm/exporters/otel/otel-exporter.type.d.mts +1 -3
  108. package/esm/exporters/otel/otel-exporter.type.d.mts.map +1 -1
  109. package/esm/exporters/utils/gen-ai-attributes.d.mts +9 -7
  110. package/esm/exporters/utils/gen-ai-attributes.d.mts.map +1 -1
  111. package/esm/exporters/utils/index.d.mts +2 -1
  112. package/esm/exporters/utils/index.mjs +5 -0
  113. package/esm/exporters/utils/total-cost.d.mts +3 -7
  114. package/esm/exporters/utils/total-cost.d.mts.map +1 -1
  115. package/esm/exporters/utils/total-cost.mjs +5 -7
  116. package/esm/exporters/utils/total-cost.mjs.map +1 -1
  117. package/esm/exporters/utils/walk-spans.d.mts +2 -2
  118. package/esm/exporters/utils/walk-spans.d.mts.map +1 -1
  119. package/esm/index.d.mts +8 -0
  120. package/esm/panoptic/index.d.mts +2 -1
  121. package/esm/panoptic/index.mjs +1 -1
  122. package/esm/panoptic/panoptic-middleware.d.mts +1 -3
  123. package/esm/panoptic/panoptic-middleware.d.mts.map +1 -1
  124. package/esm/panoptic/panoptic-middleware.mjs.map +1 -1
  125. package/esm/panoptic/panoptic-target.type.d.mts +4 -5
  126. package/esm/panoptic/panoptic-target.type.d.mts.map +1 -1
  127. package/esm/panoptic/panoptic.d.mts +1 -3
  128. package/esm/panoptic/panoptic.d.mts.map +1 -1
  129. package/esm/panoptic/panoptic.mjs.map +1 -1
  130. package/esm/panoptic/panoptic.type.d.mts +8 -7
  131. package/esm/panoptic/panoptic.type.d.mts.map +1 -1
  132. package/esm/store/cache-trace-store.d.mts +4 -6
  133. package/esm/store/cache-trace-store.d.mts.map +1 -1
  134. package/esm/store/cache-trace-store.mjs +2 -4
  135. package/esm/store/cache-trace-store.mjs.map +1 -1
  136. package/esm/store/in-memory-trace-store.d.mts +2 -4
  137. package/esm/store/in-memory-trace-store.d.mts.map +1 -1
  138. package/esm/store/in-memory-trace-store.mjs +4 -5
  139. package/esm/store/in-memory-trace-store.mjs.map +1 -1
  140. package/esm/store/index.d.mts +2 -1
  141. package/esm/store/index.mjs +1 -1
  142. package/esm/store/match-trace.d.mts +1 -3
  143. package/esm/store/match-trace.d.mts.map +1 -1
  144. package/esm/store/sum-usage.d.mts +4 -30
  145. package/esm/store/sum-usage.d.mts.map +1 -1
  146. package/esm/store/sum-usage.mjs +6 -49
  147. package/esm/store/sum-usage.mjs.map +1 -1
  148. package/esm/store/trace-aggregate.type.d.mts +13 -14
  149. package/esm/store/trace-aggregate.type.d.mts.map +1 -1
  150. package/esm/store/trace-query.type.d.mts +5 -5
  151. package/esm/store/trace-query.type.d.mts.map +1 -1
  152. package/esm/store/trace-store.contract.d.mts +1 -3
  153. package/esm/store/trace-store.contract.d.mts.map +1 -1
  154. package/llms-full.txt +148 -117
  155. package/package.json +4 -4
  156. package/skills/evaluate-system-prompt/SKILL.md +21 -17
  157. package/skills/export-traces/SKILL.md +32 -24
  158. package/skills/index.md +3 -0
  159. package/skills/observe-with-panoptic/SKILL.md +18 -18
  160. package/skills/query-traces/SKILL.md +45 -33
  161. package/skills/use-local-dashboard/SKILL.md +33 -26
@@ -1,7 +1,6 @@
1
1
  import { ExporterContract } from "../contracts/exporter.contract.mjs";
2
2
  import { TraceStoreContract } from "./trace-store.contract.mjs";
3
3
  import { CacheDriver } from "@warlock.js/cache";
4
-
5
4
  //#region ../../packages/ai-panoptic/src/store/cache-trace-store.d.ts
6
5
  /**
7
6
  * A {@link CacheDriver} instance, or a (possibly async) factory that
@@ -14,11 +13,11 @@ import { CacheDriver } from "@warlock.js/cache";
14
13
  * driver's `get` / `set` / `remove` surface and is agnostic to the
15
14
  * concrete client + options of whichever driver backs it.
16
15
  */
17
- type CacheDriverInput = CacheDriver<any, any> | (() => CacheDriver<any, any> | Promise<CacheDriver<any, any>>);
16
+ export type CacheDriverInput = CacheDriver<any, any> | (() => CacheDriver<any, any> | Promise<CacheDriver<any, any>>);
18
17
  /**
19
18
  * Options for {@link createCacheTraceStore}.
20
19
  */
21
- type CacheTraceStoreOptions = {
20
+ export type CacheTraceStoreOptions = {
22
21
  /**
23
22
  * Key prefix every cache entry this store writes is namespaced under.
24
23
  * Per-trace keys are `${prefix}:trace:${traceId}`; the newest-first
@@ -38,7 +37,7 @@ type CacheTraceStoreOptions = {
38
37
  * plus a `ready()` to hydrate the in-memory mirror from the cache on
39
38
  * startup so traces survive a process restart.
40
39
  */
41
- type CacheTraceStoreHandle = TraceStoreContract & ExporterContract & {
40
+ export type CacheTraceStoreHandle = TraceStoreContract & ExporterContract & {
42
41
  /**
43
42
  * Hydrate the in-memory mirror from the cache. Await once at startup
44
43
  * (the dashboard wiring does this for you) so previously-persisted
@@ -73,9 +72,8 @@ type CacheTraceStoreHandle = TraceStoreContract & ExporterContract & {
73
72
  * collector.use(store); // fills as traces complete
74
73
  * const failed = store.query({ status: "failed" });
75
74
  */
76
- declare function createCacheTraceStore(cache: CacheDriverInput, options?: CacheTraceStoreOptions & {
75
+ export declare function createCacheTraceStore(cache: CacheDriverInput, options?: CacheTraceStoreOptions & {
77
76
  onError?: (error: unknown) => void;
78
77
  }): CacheTraceStoreHandle;
79
78
  //#endregion
80
- export { CacheDriverInput, CacheTraceStoreHandle, CacheTraceStoreOptions, createCacheTraceStore };
81
79
  //# sourceMappingURL=cache-trace-store.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache-trace-store.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/cache-trace-store.ts"],"mappings":";;;;;;;AAoBA;;;;;;;;;KAAY,gBAAA,GAIR,WAAA,oBAEO,WAAA,aAAwB,OAAA,CAAQ,WAAA;;;;KAK/B,sBAAA;EAL0C;AAKtD;;;;EAME,MAAA;EA0YU;;;;;EApYV,QAAQ;AAAA;;;;;;;KAoYE,qBAAA,GAAwB,kBAAA,GAClC,gBAAA;EAoCc;;;;;EA9BZ,KAAA,IAAS,OAAA;AAAA;;;;;;;;;;AAiCW;;;;;;;;;;;;;;;;;;iBAHR,qBAAA,CACd,KAAA,EAAO,gBAAA,EACP,OAAA,GAAS,sBAAA;EAA2B,OAAA,IAAW,KAAA;AAAA,IAC9C,qBAAA"}
1
+ {"version":3,"file":"cache-trace-store.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/cache-trace-store.ts"],"mappings":";;;;;;;;;;;;;;;YAoBY,mBAIR,+BAEO,wBAAwB,QAAQ;;;;YAK/B;;;;;;EAMV;;;;;;EAMA;;;;;;;;YAwYU,wBAAwB,qBAClC;;;;;;EAME,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA8BG,sBACd,OAAO,kBACP,UAAS;EAA2B,WAAW;IAC9C"}
@@ -221,12 +221,10 @@ var CacheTraceStore = class {
221
221
  aggregate.completed += 1;
222
222
  break;
223
223
  case "failed":
224
+ case "incomplete":
224
225
  aggregate.failed += 1;
225
226
  break;
226
- case "cancelled":
227
- aggregate.cancelled += 1;
228
- break;
229
- default: break;
227
+ case "cancelled": aggregate.cancelled += 1;
230
228
  }
231
229
  }
232
230
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cache-trace-store.mjs","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/cache-trace-store.ts"],"sourcesContent":["import type { CacheDriver } from \"@warlock.js/cache\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { matchTrace } from \"./match-trace\";\nimport { emptyUsage, sumUsage } from \"./sum-usage\";\nimport type { TraceAggregate } from \"./trace-aggregate.type\";\nimport type { TraceQuery } from \"./trace-query.type\";\nimport type { TraceStoreContract } from \"./trace-store.contract\";\n\n/**\n * A {@link CacheDriver} instance, or a (possibly async) factory that\n * yields one on first use. The factory form lets production defer an\n * expensive connect (e.g. the Redis handshake) until the first trace is\n * actually written, and keeps the dashboard wiring free of a live driver\n * at module-import time.\n *\n * Typed `CacheDriver<any, any>` because the store only ever touches the\n * driver's `get` / `set` / `remove` surface and is agnostic to the\n * concrete client + options of whichever driver backs it.\n */\nexport type CacheDriverInput =\n // The store is driver-agnostic; it only uses get/set/remove, so the\n // concrete client/options generics are intentionally unconstrained.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | CacheDriver<any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | (() => CacheDriver<any, any> | Promise<CacheDriver<any, any>>);\n\n/**\n * Options for {@link createCacheTraceStore}.\n */\nexport type CacheTraceStoreOptions = {\n /**\n * Key prefix every cache entry this store writes is namespaced under.\n * Per-trace keys are `${prefix}:trace:${traceId}`; the newest-first\n * index lives at `${prefix}:index`. Default `\"panoptic\"`.\n */\n prefix?: string;\n /**\n * Maximum number of traces to retain. When set and exceeded, the\n * oldest-ingested trace is evicted from both the cache and the in-memory\n * mirror (insertion-order FIFO). Absent / `0` = unbounded.\n */\n capacity?: number;\n};\n\n/** One entry in the persisted newest-first index. */\ntype IndexEntry = {\n /** The trace's `traceId` — the suffix of its `${prefix}:trace:` key. */\n id: string;\n /**\n * Monotonic insertion order from an internal counter — NOT a wall clock.\n * Used only to keep the index in stable insertion order across a restart\n * so FIFO eviction stays honest.\n */\n addedAt: number;\n};\n\nconst DEFAULT_PREFIX = \"panoptic\";\n\n/**\n * Cache-backed {@link TraceStoreContract} with a **write-through** design\n * that reconciles the synchronous store contract with an asynchronous\n * cache driver.\n *\n * **How the sync/async tension is resolved.** The contract's `get` /\n * `query` / `aggregate` / `size` are synchronous (the dashboard polls them\n * on every request and the in-memory store answers instantly). A cache\n * driver is async. So this store keeps an **in-memory read mirror** — the\n * same insertion-ordered `Map<traceId, Trace>` the in-memory store uses —\n * and serves every read from it synchronously. Writes go **through** to the\n * cache: `add` updates the mirror immediately, then asynchronously persists\n * the trace + index to the cache (errors are swallowed via an optional\n * `onError` hook so a flaky cache never throws into the collector's hot\n * path). On process restart, {@link CacheTraceStore.ready} re-hydrates the\n * mirror from the cache so traces survive the restart.\n *\n * **Durability is best-effort.** Reads never wait on the cache; the mirror\n * is the source of truth at runtime and the cache is the durable backing\n * store. A write that the cache rejects is still visible in the mirror for\n * the life of the process — it just won't survive a restart.\n *\n * **Lazy driver resolution.** The driver (or its async factory) is resolved\n * on first use and memoized, so a production deployment can defer the Redis\n * connect until the first trace is collected, and the dashboard can be\n * wired with a factory at import time without a live connection.\n *\n * Doubles as an {@link ExporterContract} (`export` ≡ `add`), so it drops\n * straight into a collector via `collector.use(store)`.\n *\n * Instantiated via {@link createCacheTraceStore}; callers never see `new`.\n */\nclass CacheTraceStore implements TraceStoreContract, ExporterContract {\n /** Stable exporter id so a collector can dedupe / log this sink. */\n public readonly name = \"cache-trace-store\";\n\n /**\n * In-memory read mirror keyed by `traceId`. A `Map` preserves insertion\n * order, which newest-first `query` ordering and FIFO eviction both rely\n * on. Every read is served from here synchronously.\n */\n private readonly mirror = new Map<string, Trace>();\n\n private readonly prefix: string;\n\n private readonly capacity: number;\n\n /**\n * Monotonic insertion counter — the source of `IndexEntry.addedAt`.\n * Deliberately NOT `Date.now()`: an internal counter guarantees a stable\n * total order for the index even when many traces land in the same\n * millisecond.\n */\n private addCounter = 0;\n\n /** The optional input — a driver, a factory, or `undefined`. */\n private readonly input: CacheDriverInput;\n\n // The store is driver-agnostic; only get/set/remove are used.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private resolvedDriver?: CacheDriver<any, any>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private driverPending?: Promise<CacheDriver<any, any>>;\n\n private readonly onError?: (error: unknown) => void;\n\n public constructor(\n input: CacheDriverInput,\n options: CacheTraceStoreOptions & { onError?: (error: unknown) => void } = {},\n ) {\n this.input = input;\n this.prefix = options.prefix ?? DEFAULT_PREFIX;\n this.capacity = options.capacity ?? 0;\n this.onError = options.onError;\n }\n\n public get size(): number {\n return this.mirror.size;\n }\n\n /**\n * Hydrate the in-memory mirror from the cache. Idempotent-safe to call\n * once at startup (the dashboard / config wiring awaits it). Reads the\n * persisted index, fetches each referenced trace, and replays them into\n * the mirror in insertion order so newest-first ordering + eviction stay\n * correct after a restart. A cache failure is routed to `onError` and\n * leaves the mirror empty rather than throwing.\n */\n public async ready(): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n const index = await driver.get<IndexEntry[]>(this.indexKey());\n\n if (!Array.isArray(index)) {\n return;\n }\n\n // Oldest-first replay so the mirror's insertion order matches the\n // original ingestion order.\n const ordered = [...index].sort((left, right) => left.addedAt - right.addedAt);\n\n for (const entry of ordered) {\n const trace = await driver.get<Trace>(this.traceKey(entry.id));\n\n if (trace !== null && trace !== undefined) {\n this.mirror.delete(trace.traceId);\n this.mirror.set(trace.traceId, trace);\n }\n\n if (entry.addedAt >= this.addCounter) {\n this.addCounter = entry.addedAt + 1;\n }\n }\n\n this.evictOverflow();\n } catch (error) {\n this.reportError(error);\n }\n }\n\n public add(trace: Trace): void {\n // Mirror update is synchronous and authoritative for runtime reads.\n this.mirror.delete(trace.traceId);\n this.mirror.set(trace.traceId, trace);\n\n const addedAt = this.addCounter;\n this.addCounter += 1;\n\n const evicted = this.evictOverflow();\n\n // Write through to the cache fire-and-forget; reads never wait on this.\n void this.persist(trace, addedAt, evicted);\n }\n\n /**\n * `ExporterContract.export` — a collector dispatches a completed trace\n * here, which is exactly an `add`.\n */\n public export(trace: Trace): void {\n this.add(trace);\n }\n\n public get(traceId: string): Trace | undefined {\n return this.mirror.get(traceId);\n }\n\n public query(filter?: TraceQuery): Trace[] {\n const matched: Trace[] = [];\n\n for (const trace of this.mirror.values()) {\n if (matchTrace(trace, filter)) {\n matched.push(trace);\n }\n }\n\n return this.sortNewestFirst(matched);\n }\n\n public aggregate(filter?: TraceQuery): TraceAggregate {\n const aggregate: TraceAggregate = {\n traces: 0,\n completed: 0,\n failed: 0,\n cancelled: 0,\n usage: emptyUsage(),\n totalDuration: 0,\n };\n\n for (const trace of this.mirror.values()) {\n if (!matchTrace(trace, filter)) {\n continue;\n }\n\n aggregate.traces += 1;\n aggregate.totalDuration += trace.duration;\n aggregate.usage = sumUsage(aggregate.usage, trace.usage);\n\n this.countStatus(aggregate, trace);\n }\n\n if (aggregate.usage.cost !== undefined) {\n aggregate.cost = aggregate.usage.cost;\n }\n\n return aggregate;\n }\n\n public clear(): void {\n const ids = [...this.mirror.keys()];\n this.mirror.clear();\n\n void this.purge(ids);\n }\n\n /**\n * Persist one trace + the rebuilt index to the cache, optionally removing\n * a trace evicted by the capacity cap. Best-effort: any cache failure is\n * routed to `onError`, never thrown — the mirror already reflects the\n * write so runtime reads are unaffected.\n */\n private async persist(\n trace: Trace,\n addedAt: number,\n evictedId: string | undefined,\n ): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n\n await driver.set(this.traceKey(trace.traceId), trace);\n\n if (evictedId !== undefined) {\n await driver.remove(this.traceKey(evictedId));\n }\n\n await driver.set(this.indexKey(), this.buildIndex(addedAt));\n } catch (error) {\n this.reportError(error);\n }\n }\n\n /** Best-effort removal of every persisted trace + the index on `clear`. */\n private async purge(ids: string[]): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n\n for (const id of ids) {\n await driver.remove(this.traceKey(id));\n }\n\n await driver.remove(this.indexKey());\n } catch (error) {\n this.reportError(error);\n }\n }\n\n /**\n * Rebuild the newest-first index from the current mirror. The mirror's\n * `Map` iteration is oldest-first insertion order; we walk it and assign\n * `addedAt` from the surviving counter span so the persisted order\n * matches the in-memory one. The freshest entry uses `latestAddedAt`.\n */\n private buildIndex(latestAddedAt: number): IndexEntry[] {\n const ids = [...this.mirror.keys()];\n const base = latestAddedAt - (ids.length - 1);\n\n return ids.map((id, offset) => ({ id, addedAt: base + offset }));\n }\n\n /**\n * Resolve the driver once and memoize. Supports a bare driver, a sync\n * factory, and an async factory. Concurrent first-callers share one\n * in-flight resolution.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async resolveDriver(): Promise<CacheDriver<any, any>> {\n if (this.resolvedDriver !== undefined) {\n return this.resolvedDriver;\n }\n\n if (this.driverPending !== undefined) {\n return this.driverPending;\n }\n\n const candidate =\n typeof this.input === \"function\"\n ? (this.input as () =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | CacheDriver<any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | Promise<CacheDriver<any, any>>)()\n : this.input;\n\n this.driverPending = Promise.resolve(candidate);\n\n try {\n this.resolvedDriver = await this.driverPending;\n\n return this.resolvedDriver;\n } finally {\n this.driverPending = undefined;\n }\n }\n\n /** `${prefix}:trace:${traceId}` — the per-trace cache key. */\n private traceKey(traceId: string): string {\n return `${this.prefix}:trace:${traceId}`;\n }\n\n /** `${prefix}:index` — the newest-first index cache key. */\n private indexKey(): string {\n return `${this.prefix}:index`;\n }\n\n /** Route a swallowed cache error to the optional handler. */\n private reportError(error: unknown): void {\n if (this.onError !== undefined) {\n this.onError(error);\n }\n }\n\n /**\n * Increment the matching terminal-status counter for one trace.\n * Mirrors the in-memory store: non-terminal statuses are counted in\n * `traces` but tracked by none of the three headline counters.\n */\n private countStatus(aggregate: TraceAggregate, trace: Trace): void {\n switch (trace.root.status) {\n case \"completed\": {\n aggregate.completed += 1;\n break;\n }\n\n case \"failed\": {\n aggregate.failed += 1;\n break;\n }\n\n case \"cancelled\": {\n aggregate.cancelled += 1;\n break;\n }\n\n default: {\n break;\n }\n }\n }\n\n /**\n * Sort matched traces newest-started first. A copy is sorted so the\n * mirror's insertion order (which eviction depends on) is never disturbed.\n */\n private sortNewestFirst(traces: Trace[]): Trace[] {\n return traces.sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));\n }\n\n /**\n * Evict oldest-inserted traces from the mirror until within `capacity`.\n * Returns the id of the single evicted trace (the common case — one `add`\n * pushes at most one over the cap) so the caller can remove it from the\n * cache too. No-op + `undefined` when unbounded or within the cap.\n */\n private evictOverflow(): string | undefined {\n if (this.capacity <= 0) {\n return undefined;\n }\n\n let evicted: string | undefined;\n\n while (this.mirror.size > this.capacity) {\n const oldest = this.mirror.keys().next().value;\n\n if (oldest === undefined) {\n return evicted;\n }\n\n this.mirror.delete(oldest);\n evicted = oldest;\n }\n\n return evicted;\n }\n}\n\n/**\n * The concrete store type returned by {@link createCacheTraceStore} — the\n * standard {@link TraceStoreContract} + {@link ExporterContract} surface,\n * plus a `ready()` to hydrate the in-memory mirror from the cache on\n * startup so traces survive a process restart.\n */\nexport type CacheTraceStoreHandle = TraceStoreContract &\n ExporterContract & {\n /**\n * Hydrate the in-memory mirror from the cache. Await once at startup\n * (the dashboard wiring does this for you) so previously-persisted\n * traces are queryable after a restart.\n */\n ready(): Promise<void>;\n };\n\n/**\n * Create a cache-backed trace store. Reads are served synchronously from an\n * in-memory mirror; writes go through to the cache, and {@link\n * CacheTraceStoreHandle.ready} re-hydrates the mirror on startup so traces\n * survive a restart. See {@link CacheTraceStore} for the full write-through\n * design.\n *\n * @param cache a {@link CacheDriver}, or a (possibly async) factory that\n * yields one on first use — resolved lazily and memoized so a production\n * Redis connect can be deferred until the first trace is collected.\n * @param options `prefix` (default `\"panoptic\"`), `capacity` (FIFO cap),\n * and an optional `onError` hook for swallowed cache write failures.\n *\n * @example\n * import { RedisCacheDriver } from \"@warlock.js/cache\";\n *\n * // Lazy async factory — defers the Redis connect until first use.\n * const store = createCacheTraceStore(async () => {\n * const driver = new RedisCacheDriver();\n * await driver.connect();\n * return driver;\n * });\n *\n * await store.ready(); // hydrate from a prior run\n * collector.use(store); // fills as traces complete\n * const failed = store.query({ status: \"failed\" });\n */\nexport function createCacheTraceStore(\n cache: CacheDriverInput,\n options: CacheTraceStoreOptions & { onError?: (error: unknown) => void } = {},\n): CacheTraceStoreHandle {\n return new CacheTraceStore(cache, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cache-trace-store.mjs","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/cache-trace-store.ts"],"sourcesContent":["import type { CacheDriver } from \"@warlock.js/cache\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { matchTrace } from \"./match-trace\";\nimport { emptyUsage, sumUsage } from \"./sum-usage\";\nimport type { TraceAggregate } from \"./trace-aggregate.type\";\nimport type { TraceQuery } from \"./trace-query.type\";\nimport type { TraceStoreContract } from \"./trace-store.contract\";\n\n/**\n * A {@link CacheDriver} instance, or a (possibly async) factory that\n * yields one on first use. The factory form lets production defer an\n * expensive connect (e.g. the Redis handshake) until the first trace is\n * actually written, and keeps the dashboard wiring free of a live driver\n * at module-import time.\n *\n * Typed `CacheDriver<any, any>` because the store only ever touches the\n * driver's `get` / `set` / `remove` surface and is agnostic to the\n * concrete client + options of whichever driver backs it.\n */\nexport type CacheDriverInput =\n // The store is driver-agnostic; it only uses get/set/remove, so the\n // concrete client/options generics are intentionally unconstrained.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | CacheDriver<any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | (() => CacheDriver<any, any> | Promise<CacheDriver<any, any>>);\n\n/**\n * Options for {@link createCacheTraceStore}.\n */\nexport type CacheTraceStoreOptions = {\n /**\n * Key prefix every cache entry this store writes is namespaced under.\n * Per-trace keys are `${prefix}:trace:${traceId}`; the newest-first\n * index lives at `${prefix}:index`. Default `\"panoptic\"`.\n */\n prefix?: string;\n /**\n * Maximum number of traces to retain. When set and exceeded, the\n * oldest-ingested trace is evicted from both the cache and the in-memory\n * mirror (insertion-order FIFO). Absent / `0` = unbounded.\n */\n capacity?: number;\n};\n\n/** One entry in the persisted newest-first index. */\ntype IndexEntry = {\n /** The trace's `traceId` — the suffix of its `${prefix}:trace:` key. */\n id: string;\n /**\n * Monotonic insertion order from an internal counter — NOT a wall clock.\n * Used only to keep the index in stable insertion order across a restart\n * so FIFO eviction stays honest.\n */\n addedAt: number;\n};\n\nconst DEFAULT_PREFIX = \"panoptic\";\n\n/**\n * Cache-backed {@link TraceStoreContract} with a **write-through** design\n * that reconciles the synchronous store contract with an asynchronous\n * cache driver.\n *\n * **How the sync/async tension is resolved.** The contract's `get` /\n * `query` / `aggregate` / `size` are synchronous (the dashboard polls them\n * on every request and the in-memory store answers instantly). A cache\n * driver is async. So this store keeps an **in-memory read mirror** — the\n * same insertion-ordered `Map<traceId, Trace>` the in-memory store uses —\n * and serves every read from it synchronously. Writes go **through** to the\n * cache: `add` updates the mirror immediately, then asynchronously persists\n * the trace + index to the cache (errors are swallowed via an optional\n * `onError` hook so a flaky cache never throws into the collector's hot\n * path). On process restart, {@link CacheTraceStore.ready} re-hydrates the\n * mirror from the cache so traces survive the restart.\n *\n * **Durability is best-effort.** Reads never wait on the cache; the mirror\n * is the source of truth at runtime and the cache is the durable backing\n * store. A write that the cache rejects is still visible in the mirror for\n * the life of the process — it just won't survive a restart.\n *\n * **Lazy driver resolution.** The driver (or its async factory) is resolved\n * on first use and memoized, so a production deployment can defer the Redis\n * connect until the first trace is collected, and the dashboard can be\n * wired with a factory at import time without a live connection.\n *\n * Doubles as an {@link ExporterContract} (`export` ≡ `add`), so it drops\n * straight into a collector via `collector.use(store)`.\n *\n * Instantiated via {@link createCacheTraceStore}; callers never see `new`.\n */\nclass CacheTraceStore implements TraceStoreContract, ExporterContract {\n /** Stable exporter id so a collector can dedupe / log this sink. */\n public readonly name = \"cache-trace-store\";\n\n /**\n * In-memory read mirror keyed by `traceId`. A `Map` preserves insertion\n * order, which newest-first `query` ordering and FIFO eviction both rely\n * on. Every read is served from here synchronously.\n */\n private readonly mirror = new Map<string, Trace>();\n\n private readonly prefix: string;\n\n private readonly capacity: number;\n\n /**\n * Monotonic insertion counter — the source of `IndexEntry.addedAt`.\n * Deliberately NOT `Date.now()`: an internal counter guarantees a stable\n * total order for the index even when many traces land in the same\n * millisecond.\n */\n private addCounter = 0;\n\n /** The optional input — a driver, a factory, or `undefined`. */\n private readonly input: CacheDriverInput;\n\n // The store is driver-agnostic; only get/set/remove are used.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private resolvedDriver?: CacheDriver<any, any>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private driverPending?: Promise<CacheDriver<any, any>>;\n\n private readonly onError?: (error: unknown) => void;\n\n public constructor(\n input: CacheDriverInput,\n options: CacheTraceStoreOptions & { onError?: (error: unknown) => void } = {},\n ) {\n this.input = input;\n this.prefix = options.prefix ?? DEFAULT_PREFIX;\n this.capacity = options.capacity ?? 0;\n this.onError = options.onError;\n }\n\n public get size(): number {\n return this.mirror.size;\n }\n\n /**\n * Hydrate the in-memory mirror from the cache. Idempotent-safe to call\n * once at startup (the dashboard / config wiring awaits it). Reads the\n * persisted index, fetches each referenced trace, and replays them into\n * the mirror in insertion order so newest-first ordering + eviction stay\n * correct after a restart. A cache failure is routed to `onError` and\n * leaves the mirror empty rather than throwing.\n */\n public async ready(): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n const index = await driver.get<IndexEntry[]>(this.indexKey());\n\n if (!Array.isArray(index)) {\n return;\n }\n\n // Oldest-first replay so the mirror's insertion order matches the\n // original ingestion order.\n const ordered = [...index].sort((left, right) => left.addedAt - right.addedAt);\n\n for (const entry of ordered) {\n const trace = await driver.get<Trace>(this.traceKey(entry.id));\n\n if (trace !== null && trace !== undefined) {\n this.mirror.delete(trace.traceId);\n this.mirror.set(trace.traceId, trace);\n }\n\n if (entry.addedAt >= this.addCounter) {\n this.addCounter = entry.addedAt + 1;\n }\n }\n\n this.evictOverflow();\n } catch (error) {\n this.reportError(error);\n }\n }\n\n public add(trace: Trace): void {\n // Mirror update is synchronous and authoritative for runtime reads.\n this.mirror.delete(trace.traceId);\n this.mirror.set(trace.traceId, trace);\n\n const addedAt = this.addCounter;\n this.addCounter += 1;\n\n const evicted = this.evictOverflow();\n\n // Write through to the cache fire-and-forget; reads never wait on this.\n void this.persist(trace, addedAt, evicted);\n }\n\n /**\n * `ExporterContract.export` — a collector dispatches a completed trace\n * here, which is exactly an `add`.\n */\n public export(trace: Trace): void {\n this.add(trace);\n }\n\n public get(traceId: string): Trace | undefined {\n return this.mirror.get(traceId);\n }\n\n public query(filter?: TraceQuery): Trace[] {\n const matched: Trace[] = [];\n\n for (const trace of this.mirror.values()) {\n if (matchTrace(trace, filter)) {\n matched.push(trace);\n }\n }\n\n return this.sortNewestFirst(matched);\n }\n\n public aggregate(filter?: TraceQuery): TraceAggregate {\n const aggregate: TraceAggregate = {\n traces: 0,\n completed: 0,\n failed: 0,\n cancelled: 0,\n usage: emptyUsage(),\n totalDuration: 0,\n };\n\n for (const trace of this.mirror.values()) {\n if (!matchTrace(trace, filter)) {\n continue;\n }\n\n aggregate.traces += 1;\n aggregate.totalDuration += trace.duration;\n aggregate.usage = sumUsage(aggregate.usage, trace.usage);\n\n this.countStatus(aggregate, trace);\n }\n\n if (aggregate.usage.cost !== undefined) {\n aggregate.cost = aggregate.usage.cost;\n }\n\n return aggregate;\n }\n\n public clear(): void {\n const ids = [...this.mirror.keys()];\n this.mirror.clear();\n\n void this.purge(ids);\n }\n\n /**\n * Persist one trace + the rebuilt index to the cache, optionally removing\n * a trace evicted by the capacity cap. Best-effort: any cache failure is\n * routed to `onError`, never thrown — the mirror already reflects the\n * write so runtime reads are unaffected.\n */\n private async persist(\n trace: Trace,\n addedAt: number,\n evictedId: string | undefined,\n ): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n\n await driver.set(this.traceKey(trace.traceId), trace);\n\n if (evictedId !== undefined) {\n await driver.remove(this.traceKey(evictedId));\n }\n\n await driver.set(this.indexKey(), this.buildIndex(addedAt));\n } catch (error) {\n this.reportError(error);\n }\n }\n\n /** Best-effort removal of every persisted trace + the index on `clear`. */\n private async purge(ids: string[]): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n\n for (const id of ids) {\n await driver.remove(this.traceKey(id));\n }\n\n await driver.remove(this.indexKey());\n } catch (error) {\n this.reportError(error);\n }\n }\n\n /**\n * Rebuild the newest-first index from the current mirror. The mirror's\n * `Map` iteration is oldest-first insertion order; we walk it and assign\n * `addedAt` from the surviving counter span so the persisted order\n * matches the in-memory one. The freshest entry uses `latestAddedAt`.\n */\n private buildIndex(latestAddedAt: number): IndexEntry[] {\n const ids = [...this.mirror.keys()];\n const base = latestAddedAt - (ids.length - 1);\n\n return ids.map((id, offset) => ({ id, addedAt: base + offset }));\n }\n\n /**\n * Resolve the driver once and memoize. Supports a bare driver, a sync\n * factory, and an async factory. Concurrent first-callers share one\n * in-flight resolution.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async resolveDriver(): Promise<CacheDriver<any, any>> {\n if (this.resolvedDriver !== undefined) {\n return this.resolvedDriver;\n }\n\n if (this.driverPending !== undefined) {\n return this.driverPending;\n }\n\n const candidate =\n typeof this.input === \"function\"\n ? (\n this.input as () =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | CacheDriver<any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | Promise<CacheDriver<any, any>>\n )()\n : this.input;\n\n this.driverPending = Promise.resolve(candidate);\n\n try {\n this.resolvedDriver = await this.driverPending;\n\n return this.resolvedDriver;\n } finally {\n this.driverPending = undefined;\n }\n }\n\n /** `${prefix}:trace:${traceId}` — the per-trace cache key. */\n private traceKey(traceId: string): string {\n return `${this.prefix}:trace:${traceId}`;\n }\n\n /** `${prefix}:index` — the newest-first index cache key. */\n private indexKey(): string {\n return `${this.prefix}:index`;\n }\n\n /** Route a swallowed cache error to the optional handler. */\n private reportError(error: unknown): void {\n if (this.onError !== undefined) {\n this.onError(error);\n }\n }\n\n /**\n * Increment the matching terminal-status counter for one trace.\n * Mirrors the in-memory store: non-terminal statuses are counted in\n * `traces` but tracked by none of the three headline counters.\n */\n private countStatus(aggregate: TraceAggregate, trace: Trace): void {\n switch (trace.root.status) {\n case \"completed\": {\n aggregate.completed += 1;\n break;\n }\n\n // An incomplete agent run (its answer was cut off) errored.\n case \"failed\":\n case \"incomplete\": {\n aggregate.failed += 1;\n break;\n }\n\n case \"cancelled\": {\n aggregate.cancelled += 1;\n break;\n }\n\n default: {\n break;\n }\n }\n }\n\n /**\n * Sort matched traces newest-started first. A copy is sorted so the\n * mirror's insertion order (which eviction depends on) is never disturbed.\n */\n private sortNewestFirst(traces: Trace[]): Trace[] {\n return traces.sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));\n }\n\n /**\n * Evict oldest-inserted traces from the mirror until within `capacity`.\n * Returns the id of the single evicted trace (the common case — one `add`\n * pushes at most one over the cap) so the caller can remove it from the\n * cache too. No-op + `undefined` when unbounded or within the cap.\n */\n private evictOverflow(): string | undefined {\n if (this.capacity <= 0) {\n return undefined;\n }\n\n let evicted: string | undefined;\n\n while (this.mirror.size > this.capacity) {\n const oldest = this.mirror.keys().next().value;\n\n if (oldest === undefined) {\n return evicted;\n }\n\n this.mirror.delete(oldest);\n evicted = oldest;\n }\n\n return evicted;\n }\n}\n\n/**\n * The concrete store type returned by {@link createCacheTraceStore} — the\n * standard {@link TraceStoreContract} + {@link ExporterContract} surface,\n * plus a `ready()` to hydrate the in-memory mirror from the cache on\n * startup so traces survive a process restart.\n */\nexport type CacheTraceStoreHandle = TraceStoreContract &\n ExporterContract & {\n /**\n * Hydrate the in-memory mirror from the cache. Await once at startup\n * (the dashboard wiring does this for you) so previously-persisted\n * traces are queryable after a restart.\n */\n ready(): Promise<void>;\n };\n\n/**\n * Create a cache-backed trace store. Reads are served synchronously from an\n * in-memory mirror; writes go through to the cache, and {@link\n * CacheTraceStoreHandle.ready} re-hydrates the mirror on startup so traces\n * survive a restart. See {@link CacheTraceStore} for the full write-through\n * design.\n *\n * @param cache a {@link CacheDriver}, or a (possibly async) factory that\n * yields one on first use — resolved lazily and memoized so a production\n * Redis connect can be deferred until the first trace is collected.\n * @param options `prefix` (default `\"panoptic\"`), `capacity` (FIFO cap),\n * and an optional `onError` hook for swallowed cache write failures.\n *\n * @example\n * import { RedisCacheDriver } from \"@warlock.js/cache\";\n *\n * // Lazy async factory — defers the Redis connect until first use.\n * const store = createCacheTraceStore(async () => {\n * const driver = new RedisCacheDriver();\n * await driver.connect();\n * return driver;\n * });\n *\n * await store.ready(); // hydrate from a prior run\n * collector.use(store); // fills as traces complete\n * const failed = store.query({ status: \"failed\" });\n */\nexport function createCacheTraceStore(\n cache: CacheDriverInput,\n options: CacheTraceStoreOptions & { onError?: (error: unknown) => void } = {},\n): CacheTraceStoreHandle {\n return new CacheTraceStore(cache, options);\n}\n"],"mappings":"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"}
@@ -1,11 +1,10 @@
1
1
  import { ExporterContract } from "../contracts/exporter.contract.mjs";
2
2
  import { TraceStoreContract } from "./trace-store.contract.mjs";
3
-
4
3
  //#region ../../packages/ai-panoptic/src/store/in-memory-trace-store.d.ts
5
4
  /**
6
5
  * Options for {@link createInMemoryTraceStore}.
7
6
  */
8
- type InMemoryTraceStoreOptions = {
7
+ export type InMemoryTraceStoreOptions = {
9
8
  /**
10
9
  * Maximum number of traces to retain. When set and exceeded, the
11
10
  * oldest-ingested trace is evicted (insertion-order FIFO) so the
@@ -26,7 +25,6 @@ type InMemoryTraceStoreOptions = {
26
25
  * const recentFailures = store.query({ status: "failed" });
27
26
  * const sessionSpend = store.aggregate({ sessionId });
28
27
  */
29
- declare function createInMemoryTraceStore(options?: InMemoryTraceStoreOptions): TraceStoreContract & ExporterContract;
28
+ export declare function createInMemoryTraceStore(options?: InMemoryTraceStoreOptions): TraceStoreContract & ExporterContract;
30
29
  //#endregion
31
- export { InMemoryTraceStoreOptions, createInMemoryTraceStore };
32
30
  //# sourceMappingURL=in-memory-trace-store.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"in-memory-trace-store.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/in-memory-trace-store.ts"],"mappings":";;;;;;AAWA;KAAY,yBAAA;;;AAOF;AAoLV;;;EApLE,QAAQ;AAAA;;;;;;;;;AAoL0G;;;;iBAApG,wBAAA,CAAyB,OAAA,GAAU,yBAAA,GAA4B,kBAAA,GAAqB,gBAAA"}
1
+ {"version":3,"file":"in-memory-trace-store.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/in-memory-trace-store.ts"],"mappings":";;;;;;YAWY;;;;;;;EAOV;;;;;;;;;;;;;;wBAsLc,yBACd,UAAU,4BACT,qBAAqB"}
@@ -82,7 +82,8 @@ var InMemoryTraceStore = class {
82
82
  * Non-terminal statuses (`awaiting-input`, `max-iterations`) are
83
83
  * counted in `traces` but tracked by none of the three headline
84
84
  * counters — intentional, those three answer the common
85
- * "succeeded / errored / aborted" question.
85
+ * "succeeded / errored / aborted" question. An `incomplete` agent run
86
+ * (its answer was cut off) errored, so it counts as `failed`.
86
87
  */
87
88
  countStatus(aggregate, trace) {
88
89
  switch (trace.root.status) {
@@ -90,12 +91,10 @@ var InMemoryTraceStore = class {
90
91
  aggregate.completed += 1;
91
92
  break;
92
93
  case "failed":
94
+ case "incomplete":
93
95
  aggregate.failed += 1;
94
96
  break;
95
- case "cancelled":
96
- aggregate.cancelled += 1;
97
- break;
98
- default: break;
97
+ case "cancelled": aggregate.cancelled += 1;
99
98
  }
100
99
  }
101
100
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"in-memory-trace-store.mjs","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/in-memory-trace-store.ts"],"sourcesContent":["import type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { matchTrace } from \"./match-trace\";\nimport { emptyUsage, sumUsage } from \"./sum-usage\";\nimport type { TraceAggregate } from \"./trace-aggregate.type\";\nimport type { TraceQuery } from \"./trace-query.type\";\nimport type { TraceStoreContract } from \"./trace-store.contract\";\n\n/**\n * Options for {@link createInMemoryTraceStore}.\n */\nexport type InMemoryTraceStoreOptions = {\n /**\n * Maximum number of traces to retain. When set and exceeded, the\n * oldest-ingested trace is evicted (insertion-order FIFO) so the\n * store stays bounded for long-lived processes. Absent / `0` =\n * unbounded (keep everything until `clear`).\n */\n capacity?: number;\n};\n\n/**\n * In-memory {@link TraceStoreContract} that doubles as an\n * {@link ExporterContract} — register it on a collector\n * (`collector.use(store)`) and it fills as traces complete, then query\n * or aggregate it after the fact.\n *\n * Backed by an insertion-ordered `Map` keyed by `traceId`, giving O(1)\n * `get` / `add` / overwrite and O(n) scans for `query` / `aggregate`\n * (the price of an in-memory store with no secondary indexes — fine for\n * the dev/test and modest-volume runtime use this targets). When a\n * `capacity` is configured, ingesting past the cap evicts the oldest\n * trace.\n *\n * Instantiated fresh per store via {@link createInMemoryTraceStore};\n * callers never see `new`.\n */\nclass InMemoryTraceStore implements TraceStoreContract, ExporterContract {\n /** Stable exporter id so a collector can dedupe / log this sink. */\n public readonly name = \"in-memory-trace-store\";\n\n /**\n * Retained traces keyed by `traceId`. A `Map` preserves insertion\n * order, which is what FIFO eviction and newest-first `query` ordering\n * both rely on.\n */\n private readonly traces = new Map<string, Trace>();\n\n private readonly capacity: number;\n\n public constructor(options?: InMemoryTraceStoreOptions) {\n this.capacity = options?.capacity ?? 0;\n }\n\n public get size(): number {\n return this.traces.size;\n }\n\n public add(trace: Trace): void {\n // Re-insert so an overwrite also refreshes insertion position —\n // keeps \"oldest\" honest for FIFO eviction.\n this.traces.delete(trace.traceId);\n this.traces.set(trace.traceId, trace);\n\n this.evictOverflow();\n }\n\n /**\n * `ExporterContract.export` — a collector dispatches a completed\n * trace here, which is exactly an `add`. Lets the store be wired into\n * a collector as a sink without an adapter.\n */\n public export(trace: Trace): void {\n this.add(trace);\n }\n\n public get(traceId: string): Trace | undefined {\n return this.traces.get(traceId);\n }\n\n public query(filter?: TraceQuery): Trace[] {\n const matched: Trace[] = [];\n\n for (const trace of this.traces.values()) {\n if (matchTrace(trace, filter)) {\n matched.push(trace);\n }\n }\n\n return this.sortNewestFirst(matched);\n }\n\n public aggregate(filter?: TraceQuery): TraceAggregate {\n const aggregate: TraceAggregate = {\n traces: 0,\n completed: 0,\n failed: 0,\n cancelled: 0,\n usage: emptyUsage(),\n totalDuration: 0,\n };\n\n for (const trace of this.traces.values()) {\n if (!matchTrace(trace, filter)) {\n continue;\n }\n\n aggregate.traces += 1;\n aggregate.totalDuration += trace.duration;\n aggregate.usage = sumUsage(aggregate.usage, trace.usage);\n\n this.countStatus(aggregate, trace);\n }\n\n if (aggregate.usage.cost !== undefined) {\n aggregate.cost = aggregate.usage.cost;\n }\n\n return aggregate;\n }\n\n public clear(): void {\n this.traces.clear();\n }\n\n /**\n * Increment the matching terminal-status counter for one trace.\n * Non-terminal statuses (`awaiting-input`, `max-iterations`) are\n * counted in `traces` but tracked by none of the three headline\n * counters — intentional, those three answer the common\n * \"succeeded / errored / aborted\" question.\n */\n private countStatus(aggregate: TraceAggregate, trace: Trace): void {\n switch (trace.root.status) {\n case \"completed\": {\n aggregate.completed += 1;\n break;\n }\n\n case \"failed\": {\n aggregate.failed += 1;\n break;\n }\n\n case \"cancelled\": {\n aggregate.cancelled += 1;\n break;\n }\n\n default: {\n break;\n }\n }\n }\n\n /**\n * Sort matched traces newest-started first. A copy is sorted so the\n * underlying insertion order (which eviction depends on) is never\n * disturbed.\n */\n private sortNewestFirst(traces: Trace[]): Trace[] {\n return traces.sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));\n }\n\n /**\n * Evict oldest-inserted traces until the store is within `capacity`.\n * No-op when unbounded. The `Map` iterator yields keys in insertion\n * order, so the first key is always the oldest.\n */\n private evictOverflow(): void {\n if (this.capacity <= 0) {\n return;\n }\n\n while (this.traces.size > this.capacity) {\n const oldest = this.traces.keys().next().value;\n\n if (oldest === undefined) {\n return;\n }\n\n this.traces.delete(oldest);\n }\n }\n}\n\n/**\n * Create an in-memory trace store. Optionally bound it with `capacity`\n * for long-lived processes; leave it unset for dev/test where you want\n * every trace retained.\n *\n * @example\n * const store = createInMemoryTraceStore({ capacity: 1000 });\n * collector.use(store);\n * // later:\n * const recentFailures = store.query({ status: \"failed\" });\n * const sessionSpend = store.aggregate({ sessionId });\n */\nexport function createInMemoryTraceStore(options?: InMemoryTraceStoreOptions): TraceStoreContract & ExporterContract {\n return new InMemoryTraceStore(options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqCA,IAAM,qBAAN,MAAyE;;CAEvE,AAAgB,OAAO;;;;;;CAOvB,AAAiB,yBAAS,IAAI,IAAmB;CAEjD,AAAiB;CAEjB,AAAO,YAAY,SAAqC;EACtD,KAAK,WAAW,SAAS,YAAY;CACvC;CAEA,IAAW,OAAe;EACxB,OAAO,KAAK,OAAO;CACrB;CAEA,AAAO,IAAI,OAAoB;EAG7B,KAAK,OAAO,OAAO,MAAM,OAAO;EAChC,KAAK,OAAO,IAAI,MAAM,SAAS,KAAK;EAEpC,KAAK,cAAc;CACrB;;;;;;CAOA,AAAO,OAAO,OAAoB;EAChC,KAAK,IAAI,KAAK;CAChB;CAEA,AAAO,IAAI,SAAoC;EAC7C,OAAO,KAAK,OAAO,IAAI,OAAO;CAChC;CAEA,AAAO,MAAM,QAA8B;EACzC,MAAM,UAAmB,CAAC;EAE1B,KAAK,MAAM,SAAS,KAAK,OAAO,OAAO,GACrC,IAAI,WAAW,OAAO,MAAM,GAC1B,QAAQ,KAAK,KAAK;EAItB,OAAO,KAAK,gBAAgB,OAAO;CACrC;CAEA,AAAO,UAAU,QAAqC;EACpD,MAAM,YAA4B;GAChC,QAAQ;GACR,WAAW;GACX,QAAQ;GACR,WAAW;GACX,OAAO,WAAW;GAClB,eAAe;EACjB;EAEA,KAAK,MAAM,SAAS,KAAK,OAAO,OAAO,GAAG;GACxC,IAAI,CAAC,WAAW,OAAO,MAAM,GAC3B;GAGF,UAAU,UAAU;GACpB,UAAU,iBAAiB,MAAM;GACjC,UAAU,QAAQ,SAAS,UAAU,OAAO,MAAM,KAAK;GAEvD,KAAK,YAAY,WAAW,KAAK;EACnC;EAEA,IAAI,UAAU,MAAM,SAAS,QAC3B,UAAU,OAAO,UAAU,MAAM;EAGnC,OAAO;CACT;CAEA,AAAO,QAAc;EACnB,KAAK,OAAO,MAAM;CACpB;;;;;;;;CASA,AAAQ,YAAY,WAA2B,OAAoB;EACjE,QAAQ,MAAM,KAAK,QAAnB;GACE,KAAK;IACH,UAAU,aAAa;IACvB;GAGF,KAAK;IACH,UAAU,UAAU;IACpB;GAGF,KAAK;IACH,UAAU,aAAa;IACvB;GAGF,SACE;EAEJ;CACF;;;;;;CAOA,AAAQ,gBAAgB,QAA0B;EAChD,OAAO,OAAO,MAAM,MAAM,UAAU,KAAK,MAAM,MAAM,SAAS,IAAI,KAAK,MAAM,KAAK,SAAS,CAAC;CAC9F;;;;;;CAOA,AAAQ,gBAAsB;EAC5B,IAAI,KAAK,YAAY,GACnB;EAGF,OAAO,KAAK,OAAO,OAAO,KAAK,UAAU;GACvC,MAAM,SAAS,KAAK,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GAEzC,IAAI,WAAW,QACb;GAGF,KAAK,OAAO,OAAO,MAAM;EAC3B;CACF;AACF;;;;;;;;;;;;;AAcA,SAAgB,yBAAyB,SAA4E;CACnH,OAAO,IAAI,mBAAmB,OAAO;AACvC"}
1
+ {"version":3,"file":"in-memory-trace-store.mjs","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/in-memory-trace-store.ts"],"sourcesContent":["import type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { matchTrace } from \"./match-trace\";\nimport { emptyUsage, sumUsage } from \"./sum-usage\";\nimport type { TraceAggregate } from \"./trace-aggregate.type\";\nimport type { TraceQuery } from \"./trace-query.type\";\nimport type { TraceStoreContract } from \"./trace-store.contract\";\n\n/**\n * Options for {@link createInMemoryTraceStore}.\n */\nexport type InMemoryTraceStoreOptions = {\n /**\n * Maximum number of traces to retain. When set and exceeded, the\n * oldest-ingested trace is evicted (insertion-order FIFO) so the\n * store stays bounded for long-lived processes. Absent / `0` =\n * unbounded (keep everything until `clear`).\n */\n capacity?: number;\n};\n\n/**\n * In-memory {@link TraceStoreContract} that doubles as an\n * {@link ExporterContract} — register it on a collector\n * (`collector.use(store)`) and it fills as traces complete, then query\n * or aggregate it after the fact.\n *\n * Backed by an insertion-ordered `Map` keyed by `traceId`, giving O(1)\n * `get` / `add` / overwrite and O(n) scans for `query` / `aggregate`\n * (the price of an in-memory store with no secondary indexes — fine for\n * the dev/test and modest-volume runtime use this targets). When a\n * `capacity` is configured, ingesting past the cap evicts the oldest\n * trace.\n *\n * Instantiated fresh per store via {@link createInMemoryTraceStore};\n * callers never see `new`.\n */\nclass InMemoryTraceStore implements TraceStoreContract, ExporterContract {\n /** Stable exporter id so a collector can dedupe / log this sink. */\n public readonly name = \"in-memory-trace-store\";\n\n /**\n * Retained traces keyed by `traceId`. A `Map` preserves insertion\n * order, which is what FIFO eviction and newest-first `query` ordering\n * both rely on.\n */\n private readonly traces = new Map<string, Trace>();\n\n private readonly capacity: number;\n\n public constructor(options?: InMemoryTraceStoreOptions) {\n this.capacity = options?.capacity ?? 0;\n }\n\n public get size(): number {\n return this.traces.size;\n }\n\n public add(trace: Trace): void {\n // Re-insert so an overwrite also refreshes insertion position —\n // keeps \"oldest\" honest for FIFO eviction.\n this.traces.delete(trace.traceId);\n this.traces.set(trace.traceId, trace);\n\n this.evictOverflow();\n }\n\n /**\n * `ExporterContract.export` — a collector dispatches a completed\n * trace here, which is exactly an `add`. Lets the store be wired into\n * a collector as a sink without an adapter.\n */\n public export(trace: Trace): void {\n this.add(trace);\n }\n\n public get(traceId: string): Trace | undefined {\n return this.traces.get(traceId);\n }\n\n public query(filter?: TraceQuery): Trace[] {\n const matched: Trace[] = [];\n\n for (const trace of this.traces.values()) {\n if (matchTrace(trace, filter)) {\n matched.push(trace);\n }\n }\n\n return this.sortNewestFirst(matched);\n }\n\n public aggregate(filter?: TraceQuery): TraceAggregate {\n const aggregate: TraceAggregate = {\n traces: 0,\n completed: 0,\n failed: 0,\n cancelled: 0,\n usage: emptyUsage(),\n totalDuration: 0,\n };\n\n for (const trace of this.traces.values()) {\n if (!matchTrace(trace, filter)) {\n continue;\n }\n\n aggregate.traces += 1;\n aggregate.totalDuration += trace.duration;\n aggregate.usage = sumUsage(aggregate.usage, trace.usage);\n\n this.countStatus(aggregate, trace);\n }\n\n if (aggregate.usage.cost !== undefined) {\n aggregate.cost = aggregate.usage.cost;\n }\n\n return aggregate;\n }\n\n public clear(): void {\n this.traces.clear();\n }\n\n /**\n * Increment the matching terminal-status counter for one trace.\n * Non-terminal statuses (`awaiting-input`, `max-iterations`) are\n * counted in `traces` but tracked by none of the three headline\n * counters — intentional, those three answer the common\n * \"succeeded / errored / aborted\" question. An `incomplete` agent run\n * (its answer was cut off) errored, so it counts as `failed`.\n */\n private countStatus(aggregate: TraceAggregate, trace: Trace): void {\n switch (trace.root.status) {\n case \"completed\": {\n aggregate.completed += 1;\n break;\n }\n\n case \"failed\":\n case \"incomplete\": {\n aggregate.failed += 1;\n break;\n }\n\n case \"cancelled\": {\n aggregate.cancelled += 1;\n break;\n }\n\n default: {\n break;\n }\n }\n }\n\n /**\n * Sort matched traces newest-started first. A copy is sorted so the\n * underlying insertion order (which eviction depends on) is never\n * disturbed.\n */\n private sortNewestFirst(traces: Trace[]): Trace[] {\n return traces.sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));\n }\n\n /**\n * Evict oldest-inserted traces until the store is within `capacity`.\n * No-op when unbounded. The `Map` iterator yields keys in insertion\n * order, so the first key is always the oldest.\n */\n private evictOverflow(): void {\n if (this.capacity <= 0) {\n return;\n }\n\n while (this.traces.size > this.capacity) {\n const oldest = this.traces.keys().next().value;\n\n if (oldest === undefined) {\n return;\n }\n\n this.traces.delete(oldest);\n }\n }\n}\n\n/**\n * Create an in-memory trace store. Optionally bound it with `capacity`\n * for long-lived processes; leave it unset for dev/test where you want\n * every trace retained.\n *\n * @example\n * const store = createInMemoryTraceStore({ capacity: 1000 });\n * collector.use(store);\n * // later:\n * const recentFailures = store.query({ status: \"failed\" });\n * const sessionSpend = store.aggregate({ sessionId });\n */\nexport function createInMemoryTraceStore(\n options?: InMemoryTraceStoreOptions,\n): TraceStoreContract & ExporterContract {\n return new InMemoryTraceStore(options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqCA,IAAM,qBAAN,MAAyE;;CAEvE,AAAgB,OAAO;;;;;;CAOvB,AAAiB,yBAAS,IAAI,IAAmB;CAEjD,AAAiB;CAEjB,AAAO,YAAY,SAAqC;EACtD,KAAK,WAAW,SAAS,YAAY;CACvC;CAEA,IAAW,OAAe;EACxB,OAAO,KAAK,OAAO;CACrB;CAEA,AAAO,IAAI,OAAoB;EAG7B,KAAK,OAAO,OAAO,MAAM,OAAO;EAChC,KAAK,OAAO,IAAI,MAAM,SAAS,KAAK;EAEpC,KAAK,cAAc;CACrB;;;;;;CAOA,AAAO,OAAO,OAAoB;EAChC,KAAK,IAAI,KAAK;CAChB;CAEA,AAAO,IAAI,SAAoC;EAC7C,OAAO,KAAK,OAAO,IAAI,OAAO;CAChC;CAEA,AAAO,MAAM,QAA8B;EACzC,MAAM,UAAmB,CAAC;EAE1B,KAAK,MAAM,SAAS,KAAK,OAAO,OAAO,GACrC,IAAI,WAAW,OAAO,MAAM,GAC1B,QAAQ,KAAK,KAAK;EAItB,OAAO,KAAK,gBAAgB,OAAO;CACrC;CAEA,AAAO,UAAU,QAAqC;EACpD,MAAM,YAA4B;GAChC,QAAQ;GACR,WAAW;GACX,QAAQ;GACR,WAAW;GACX,OAAO,WAAW;GAClB,eAAe;EACjB;EAEA,KAAK,MAAM,SAAS,KAAK,OAAO,OAAO,GAAG;GACxC,IAAI,CAAC,WAAW,OAAO,MAAM,GAC3B;GAGF,UAAU,UAAU;GACpB,UAAU,iBAAiB,MAAM;GACjC,UAAU,QAAQ,SAAS,UAAU,OAAO,MAAM,KAAK;GAEvD,KAAK,YAAY,WAAW,KAAK;EACnC;EAEA,IAAI,UAAU,MAAM,SAAS,QAC3B,UAAU,OAAO,UAAU,MAAM;EAGnC,OAAO;CACT;CAEA,AAAO,QAAc;EACnB,KAAK,OAAO,MAAM;CACpB;;;;;;;;;CAUA,AAAQ,YAAY,WAA2B,OAAoB;EACjE,QAAQ,MAAM,KAAK,QAAnB;GACE,KAAK;IACH,UAAU,aAAa;IACvB;GAGF,KAAK;GACL,KAAK;IACH,UAAU,UAAU;IACpB;GAGF,KAAK,aACH,UAAU,aAAa;EAO3B;CACF;;;;;;CAOA,AAAQ,gBAAgB,QAA0B;EAChD,OAAO,OAAO,MAAM,MAAM,UAAU,KAAK,MAAM,MAAM,SAAS,IAAI,KAAK,MAAM,KAAK,SAAS,CAAC;CAC9F;;;;;;CAOA,AAAQ,gBAAsB;EAC5B,IAAI,KAAK,YAAY,GACnB;EAGF,OAAO,KAAK,OAAO,OAAO,KAAK,UAAU;GACvC,MAAM,SAAS,KAAK,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GAEzC,IAAI,WAAW,QACb;GAGF,KAAK,OAAO,OAAO,MAAM;EAC3B;CACF;AACF;;;;;;;;;;;;;AAcA,SAAgB,yBACd,SACuC;CACvC,OAAO,IAAI,mBAAmB,OAAO;AACvC"}
@@ -4,4 +4,5 @@ import { TraceStoreContract } from "./trace-store.contract.mjs";
4
4
  import { CacheDriverInput, CacheTraceStoreHandle, CacheTraceStoreOptions, createCacheTraceStore } from "./cache-trace-store.mjs";
5
5
  import { InMemoryTraceStoreOptions, createInMemoryTraceStore } from "./in-memory-trace-store.mjs";
6
6
  import { matchTrace } from "./match-trace.mjs";
7
- import { emptyUsage, sumUsage } from "./sum-usage.mjs";
7
+ import { emptyUsage, sumUsage } from "./sum-usage.mjs";
8
+ export { type CacheDriverInput, type CacheTraceStoreHandle, type CacheTraceStoreOptions, type InMemoryTraceStoreOptions, type TraceAggregate, type TraceQuery, type TraceStoreContract, createCacheTraceStore, createInMemoryTraceStore, emptyUsage, matchTrace, sumUsage };
@@ -3,4 +3,4 @@ import { emptyUsage, sumUsage } from "./sum-usage.mjs";
3
3
  import { createCacheTraceStore } from "./cache-trace-store.mjs";
4
4
  import { createInMemoryTraceStore } from "./in-memory-trace-store.mjs";
5
5
 
6
- export { };
6
+ export { createCacheTraceStore, createInMemoryTraceStore, emptyUsage, matchTrace, sumUsage };
@@ -1,6 +1,5 @@
1
1
  import { Trace } from "../contracts/trace.type.mjs";
2
2
  import { TraceQuery } from "./trace-query.type.mjs";
3
-
4
3
  //#region ../../packages/ai-panoptic/src/store/match-trace.d.ts
5
4
  /**
6
5
  * Test whether one trace satisfies a {@link TraceQuery}. Every declared
@@ -15,7 +14,6 @@ import { TraceQuery } from "./trace-query.type.mjs";
15
14
  * Pure — used by the store's `query` and `aggregate` so both share one
16
15
  * matching definition.
17
16
  */
18
- declare function matchTrace(trace: Trace, filter?: TraceQuery): boolean;
17
+ export declare function matchTrace(trace: Trace, filter?: TraceQuery): boolean;
19
18
  //#endregion
20
- export { matchTrace };
21
19
  //# sourceMappingURL=match-trace.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"match-trace.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/match-trace.ts"],"mappings":";;;;;;AAiBA;;;;;;;;;AAA4D;;iBAA5C,UAAA,CAAW,KAAA,EAAO,KAAA,EAAO,MAAA,GAAS,UAAU"}
1
+ {"version":3,"file":"match-trace.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/match-trace.ts"],"mappings":";;;;;;;;;;;;;;;;wBAiBgB,WAAW,OAAO,OAAO,SAAS"}
@@ -1,34 +1,8 @@
1
1
  import { Usage } from "@warlock.js/ai";
2
-
3
2
  //#region ../../packages/ai-panoptic/src/store/sum-usage.d.ts
4
- /**
5
- * Fold a child {@link Usage} into a running accumulator. Token channels
6
- * (`input` / `output` / `total`) always sum; the optional cache /
7
- * reasoning channels (`cachedTokens` / `cacheWriteTokens` /
8
- * `reasoningTokens`) sum only when at least one side reported them, so
9
- * a provider that never meters a channel doesn't fabricate a `0` for
10
- * it. The `cost` breakdown is merged with the core framework's
11
- * {@link accumulateCost}, keeping cost-rollup semantics identical to a
12
- * native report tree — an unpriced contributor never erases a priced
13
- * one.
14
- *
15
- * Pure: returns a fresh `Usage`, never mutates either argument. Seed an
16
- * aggregation with {@link emptyUsage}.
17
- *
18
- * @example
19
- * let total = emptyUsage();
20
- * for (const trace of traces) {
21
- * total = sumUsage(total, trace.usage);
22
- * }
23
- */
24
- declare function sumUsage(accumulator: Usage, next: Usage): Usage;
25
- /**
26
- * A zero-valued {@link Usage} to seed an aggregation. Only the required
27
- * token channels are set; optional channels stay absent until a
28
- * contributor reports them, preserving the "never reported" vs
29
- * "reported as 0" distinction.
30
- */
31
- declare function emptyUsage(): Usage;
3
+ /** Return an independent all-channel usage rollup, preserving hosted-tool counters. */
4
+ export declare function sumUsage(accumulator: Usage, next: Usage): Usage;
5
+ /** Seed an aggregation without claiming any optional counter was reported. */
6
+ export declare function emptyUsage(): Usage;
32
7
  //#endregion
33
- export { emptyUsage, sumUsage };
34
8
  //# sourceMappingURL=sum-usage.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sum-usage.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/sum-usage.ts"],"mappings":";;;;;AAsBA;;;;;;;;;;;;;;AAAgE;AAoChE;;;iBApCgB,QAAA,CAAS,WAAA,EAAa,KAAA,EAAO,IAAA,EAAM,KAAA,GAAQ,KAAA;AAoCxB;;;;;;AAAA,iBAAnB,UAAA,IAAc,KAAK"}
1
+ {"version":3,"file":"sum-usage.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/sum-usage.ts"],"mappings":";;;wBAGgB,SAAS,aAAa,OAAO,MAAM,QAAQ;;wBAQ3C,cAAc"}
@@ -1,48 +1,14 @@
1
- import { accumulateCost } from "@warlock.js/ai";
1
+ import { mergeUsage } from "@warlock.js/ai";
2
2
 
3
3
  //#region ../../packages/ai-panoptic/src/store/sum-usage.ts
4
- /**
5
- * Fold a child {@link Usage} into a running accumulator. Token channels
6
- * (`input` / `output` / `total`) always sum; the optional cache /
7
- * reasoning channels (`cachedTokens` / `cacheWriteTokens` /
8
- * `reasoningTokens`) sum only when at least one side reported them, so
9
- * a provider that never meters a channel doesn't fabricate a `0` for
10
- * it. The `cost` breakdown is merged with the core framework's
11
- * {@link accumulateCost}, keeping cost-rollup semantics identical to a
12
- * native report tree — an unpriced contributor never erases a priced
13
- * one.
14
- *
15
- * Pure: returns a fresh `Usage`, never mutates either argument. Seed an
16
- * aggregation with {@link emptyUsage}.
17
- *
18
- * @example
19
- * let total = emptyUsage();
20
- * for (const trace of traces) {
21
- * total = sumUsage(total, trace.usage);
22
- * }
23
- */
4
+ /** Return an independent all-channel usage rollup, preserving hosted-tool counters. */
24
5
  function sumUsage(accumulator, next) {
25
- const merged = {
26
- input: accumulator.input + next.input,
27
- output: accumulator.output + next.output,
28
- total: accumulator.total + next.total
29
- };
30
- const cachedTokens = sumOptional(accumulator.cachedTokens, next.cachedTokens);
31
- if (cachedTokens !== void 0) merged.cachedTokens = cachedTokens;
32
- const cacheWriteTokens = sumOptional(accumulator.cacheWriteTokens, next.cacheWriteTokens);
33
- if (cacheWriteTokens !== void 0) merged.cacheWriteTokens = cacheWriteTokens;
34
- const reasoningTokens = sumOptional(accumulator.reasoningTokens, next.reasoningTokens);
35
- if (reasoningTokens !== void 0) merged.reasoningTokens = reasoningTokens;
36
- const cost = accumulateCost(accumulator.cost, next.cost);
37
- if (cost !== void 0) merged.cost = cost;
6
+ const merged = emptyUsage();
7
+ mergeUsage(merged, accumulator);
8
+ mergeUsage(merged, next);
38
9
  return merged;
39
10
  }
40
- /**
41
- * A zero-valued {@link Usage} to seed an aggregation. Only the required
42
- * token channels are set; optional channels stay absent until a
43
- * contributor reports them, preserving the "never reported" vs
44
- * "reported as 0" distinction.
45
- */
11
+ /** Seed an aggregation without claiming any optional counter was reported. */
46
12
  function emptyUsage() {
47
13
  return {
48
14
  input: 0,
@@ -50,15 +16,6 @@ function emptyUsage() {
50
16
  total: 0
51
17
  };
52
18
  }
53
- /**
54
- * Add two optional token counts, treating either side's `undefined` as
55
- * zero — but return `undefined` when both are absent, so an unreported
56
- * channel stays unreported rather than collapsing to `0`.
57
- */
58
- function sumOptional(accumulator, next) {
59
- if (accumulator === void 0 && next === void 0) return;
60
- return (accumulator ?? 0) + (next ?? 0);
61
- }
62
19
 
63
20
  //#endregion
64
21
  export { emptyUsage, sumUsage };
@@ -1 +1 @@
1
- {"version":3,"file":"sum-usage.mjs","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/sum-usage.ts"],"sourcesContent":["import { accumulateCost, type Usage } from \"@warlock.js/ai\";\n\n/**\n * Fold a child {@link Usage} into a running accumulator. Token channels\n * (`input` / `output` / `total`) always sum; the optional cache /\n * reasoning channels (`cachedTokens` / `cacheWriteTokens` /\n * `reasoningTokens`) sum only when at least one side reported them, so\n * a provider that never meters a channel doesn't fabricate a `0` for\n * it. The `cost` breakdown is merged with the core framework's\n * {@link accumulateCost}, keeping cost-rollup semantics identical to a\n * native report tree — an unpriced contributor never erases a priced\n * one.\n *\n * Pure: returns a fresh `Usage`, never mutates either argument. Seed an\n * aggregation with {@link emptyUsage}.\n *\n * @example\n * let total = emptyUsage();\n * for (const trace of traces) {\n * total = sumUsage(total, trace.usage);\n * }\n */\nexport function sumUsage(accumulator: Usage, next: Usage): Usage {\n const merged: Usage = {\n input: accumulator.input + next.input,\n output: accumulator.output + next.output,\n total: accumulator.total + next.total,\n };\n\n const cachedTokens = sumOptional(accumulator.cachedTokens, next.cachedTokens);\n if (cachedTokens !== undefined) {\n merged.cachedTokens = cachedTokens;\n }\n\n const cacheWriteTokens = sumOptional(accumulator.cacheWriteTokens, next.cacheWriteTokens);\n if (cacheWriteTokens !== undefined) {\n merged.cacheWriteTokens = cacheWriteTokens;\n }\n\n const reasoningTokens = sumOptional(accumulator.reasoningTokens, next.reasoningTokens);\n if (reasoningTokens !== undefined) {\n merged.reasoningTokens = reasoningTokens;\n }\n\n const cost = accumulateCost(accumulator.cost, next.cost);\n if (cost !== undefined) {\n merged.cost = cost;\n }\n\n return merged;\n}\n\n/**\n * A zero-valued {@link Usage} to seed an aggregation. Only the required\n * token channels are set; optional channels stay absent until a\n * contributor reports them, preserving the \"never reported\" vs\n * \"reported as 0\" distinction.\n */\nexport function emptyUsage(): Usage {\n return {\n input: 0,\n output: 0,\n total: 0,\n };\n}\n\n/**\n * Add two optional token counts, treating either side's `undefined` as\n * zero — but return `undefined` when both are absent, so an unreported\n * channel stays unreported rather than collapsing to `0`.\n */\nfunction sumOptional(accumulator: number | undefined, next: number | undefined): number | undefined {\n if (accumulator === undefined && next === undefined) {\n return undefined;\n }\n\n return (accumulator ?? 0) + (next ?? 0);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,SAAS,aAAoB,MAAoB;CAC/D,MAAM,SAAgB;EACpB,OAAO,YAAY,QAAQ,KAAK;EAChC,QAAQ,YAAY,SAAS,KAAK;EAClC,OAAO,YAAY,QAAQ,KAAK;CAClC;CAEA,MAAM,eAAe,YAAY,YAAY,cAAc,KAAK,YAAY;CAC5E,IAAI,iBAAiB,QACnB,OAAO,eAAe;CAGxB,MAAM,mBAAmB,YAAY,YAAY,kBAAkB,KAAK,gBAAgB;CACxF,IAAI,qBAAqB,QACvB,OAAO,mBAAmB;CAG5B,MAAM,kBAAkB,YAAY,YAAY,iBAAiB,KAAK,eAAe;CACrF,IAAI,oBAAoB,QACtB,OAAO,kBAAkB;CAG3B,MAAM,OAAO,eAAe,YAAY,MAAM,KAAK,IAAI;CACvD,IAAI,SAAS,QACX,OAAO,OAAO;CAGhB,OAAO;AACT;;;;;;;AAQA,SAAgB,aAAoB;CAClC,OAAO;EACL,OAAO;EACP,QAAQ;EACR,OAAO;CACT;AACF;;;;;;AAOA,SAAS,YAAY,aAAiC,MAA8C;CAClG,IAAI,gBAAgB,UAAa,SAAS,QACxC;CAGF,QAAQ,eAAe,MAAM,QAAQ;AACvC"}
1
+ {"version":3,"file":"sum-usage.mjs","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/sum-usage.ts"],"sourcesContent":["import { mergeUsage, type Usage } from \"@warlock.js/ai\";\n\n/** Return an independent all-channel usage rollup, preserving hosted-tool counters. */\nexport function sumUsage(accumulator: Usage, next: Usage): Usage {\n const merged = emptyUsage();\n mergeUsage(merged, accumulator);\n mergeUsage(merged, next);\n return merged;\n}\n\n/** Seed an aggregation without claiming any optional counter was reported. */\nexport function emptyUsage(): Usage {\n return { input: 0, output: 0, total: 0 };\n}\n"],"mappings":";;;;AAGA,SAAgB,SAAS,aAAoB,MAAoB;CAC/D,MAAM,SAAS,WAAW;CAC1B,WAAW,QAAQ,WAAW;CAC9B,WAAW,QAAQ,IAAI;CACvB,OAAO;AACT;;AAGA,SAAgB,aAAoB;CAClC,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAG,OAAO;CAAE;AACzC"}
@@ -1,5 +1,4 @@
1
- import { ModelPricing, Usage } from "@warlock.js/ai";
2
-
1
+ import { ModelCost, Usage } from "@warlock.js/ai";
3
2
  //#region ../../packages/ai-panoptic/src/store/trace-aggregate.type.d.ts
4
3
  /**
5
4
  * Rolled-up usage + cost across a set of stored traces, returned by the
@@ -19,16 +18,16 @@ import { ModelPricing, Usage } from "@warlock.js/ai";
19
18
  * @example
20
19
  * const stats = store.aggregate({ sessionId: "session-42" });
21
20
  * console.log(`${stats.traces} runs, ${stats.usage.total} tokens`);
22
- * const totalUsd =
23
- * (stats.cost?.input ?? 0) +
24
- * (stats.cost?.output ?? 0) +
25
- * (stats.cost?.cachedInput ?? 0) +
26
- * (stats.cost?.cachedOutput ?? 0);
21
+ * const totalUsd = totalCost(stats.cost) ?? 0; // `totalCost` from @warlock.js/ai
27
22
  */
28
- type TraceAggregate = {
29
- /** Number of traces matched by the query. */traces: number; /** Count of matched traces whose root status was `"completed"`. */
30
- completed: number; /** Count of matched traces whose root status was `"failed"`. */
31
- failed: number; /** Count of matched traces whose root status was `"cancelled"`. */
23
+ export type TraceAggregate = {
24
+ /** Number of traces matched by the query. */
25
+ traces: number;
26
+ /** Count of matched traces whose root status was `"completed"`. */
27
+ completed: number;
28
+ /** Count of matched traces whose root status was `"failed"` or `"incomplete"`. */
29
+ failed: number;
30
+ /** Count of matched traces whose root status was `"cancelled"`. */
32
31
  cancelled: number;
33
32
  /**
34
33
  * Summed token usage across every matched trace. `input` / `output` /
@@ -40,12 +39,12 @@ type TraceAggregate = {
40
39
  /**
41
40
  * Merged per-channel USD cost across every matched trace, or
42
41
  * `undefined` when no matched trace carried pricing. Same shape as
43
- * `ModelPricing` so callers can read the input/output/cache split,
42
+ * `ModelCost` so callers can read the input/output/cache split,
44
43
  * not just one opaque number.
45
44
  */
46
- cost?: ModelPricing; /** Summed wall-clock duration in milliseconds across matched traces. */
45
+ cost?: ModelCost;
46
+ /** Summed wall-clock duration in milliseconds across matched traces. */
47
47
  totalDuration: number;
48
48
  };
49
49
  //#endregion
50
- export { TraceAggregate };
51
50
  //# sourceMappingURL=trace-aggregate.type.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"trace-aggregate.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/trace-aggregate.type.ts"],"mappings":";;;;;AA0BA;;;;;;;;;;;;;;AAwBe;;;;;;;;KAxBH,cAAA;+CAEV,MAAA;EAEA,SAAA;EAEA,MAAA;EAEA,SAAA;;;;;;;EAOA,KAAA,EAAO,KAAA;;;;;;;EAOP,IAAA,GAAO,YAAY;EAEnB,aAAA;AAAA"}
1
+ {"version":3,"file":"trace-aggregate.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/trace-aggregate.type.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;YAsBY;;EAEV;;EAEA;;EAEA;;EAEA;;;;;;;EAOA,OAAO;;;;;;;EAOP,OAAO;;EAEP"}
@@ -1,5 +1,4 @@
1
1
  import { ReportStatus } from "@warlock.js/ai";
2
-
3
2
  //#region ../../packages/ai-panoptic/src/store/trace-query.type.d.ts
4
3
  /**
5
4
  * Filter passed to the trace store's `query` / `aggregate`. Every field
@@ -21,7 +20,7 @@ import { ReportStatus } from "@warlock.js/ai";
21
20
  * @example
22
21
  * const sessionSpend = store.aggregate({ sessionId: "session-42" });
23
22
  */
24
- type TraceQuery = {
23
+ export type TraceQuery = {
25
24
  /**
26
25
  * Match a single trace by its `traceId` (equals the root run's
27
26
  * `runId`). The most specific filter — narrows to at most one trace.
@@ -36,10 +35,11 @@ type TraceQuery = {
36
35
  * Match traces whose root terminal `status` equals this value, or any
37
36
  * of these values when an array is given.
38
37
  */
39
- status?: ReportStatus | ReportStatus[]; /** Inclusive lower bound on the trace's root `startedAt`. */
40
- startedAfter?: string | Date; /** Inclusive upper bound on the trace's root `startedAt`. */
38
+ status?: ReportStatus | ReportStatus[];
39
+ /** Inclusive lower bound on the trace's root `startedAt`. */
40
+ startedAfter?: string | Date;
41
+ /** Inclusive upper bound on the trace's root `startedAt`. */
41
42
  startedBefore?: string | Date;
42
43
  };
43
44
  //#endregion
44
- export { TraceQuery };
45
45
  //# sourceMappingURL=trace-query.type.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"trace-query.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/trace-query.type.ts"],"mappings":";;;;;AAsBA;;;;;;;;;;;;;;;;;;KAAY,UAAA;EAmBmB;;;;EAd7B,OAAA;;;;;EAKA,SAAA;;;;;EAKA,MAAA,GAAS,YAAA,GAAe,YAAA;EAExB,YAAA,YAAwB,IAAA;EAExB,aAAA,YAAyB,IAAA;AAAA"}
1
+ {"version":3,"file":"trace-query.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/trace-query.type.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;YAsBY;;;;;EAKV;;;;;EAKA;;;;;EAKA,SAAS,eAAe;;EAExB,wBAAwB;;EAExB,yBAAyB"}
@@ -1,7 +1,6 @@
1
1
  import { Trace } from "../contracts/trace.type.mjs";
2
2
  import { TraceAggregate } from "./trace-aggregate.type.mjs";
3
3
  import { TraceQuery } from "./trace-query.type.mjs";
4
-
5
4
  //#region ../../packages/ai-panoptic/src/store/trace-store.contract.d.ts
6
5
  /**
7
6
  * Contract for a Panoptic trace store — a queryable sink that retains
@@ -26,7 +25,7 @@ import { TraceQuery } from "./trace-query.type.mjs";
26
25
  * const failed = store.query({ status: "failed" });
27
26
  * const spend = store.aggregate({ sessionId });
28
27
  */
29
- interface TraceStoreContract {
28
+ export interface TraceStoreContract {
30
29
  /**
31
30
  * Ingest one finalized trace. Replaces any existing trace with the
32
31
  * same `traceId` (re-collecting a run overwrites rather than
@@ -51,5 +50,4 @@ interface TraceStoreContract {
51
50
  clear(): void;
52
51
  }
53
52
  //#endregion
54
- export { TraceStoreContract };
55
53
  //# sourceMappingURL=trace-store.contract.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"trace-store.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/trace-store.contract.ts"],"mappings":";;;;;;;AA2BA;;;;;;;;;;;;;;;;;;;;;UAAiB,kBAAA;EAqBI;;;;;EAfnB,GAAA,CAAI,KAAA,EAAO,KAAA;EAqBN;EAlBL,GAAA,CAAI,OAAA,WAAkB,KAAA;;;;;EAMtB,KAAA,CAAM,MAAA,GAAS,UAAA,GAAa,KAAA;;;;;EAM5B,SAAA,CAAU,MAAA,GAAS,UAAA,GAAa,cAAA;;WAGvB,IAAA;;EAGT,KAAA;AAAA"}
1
+ {"version":3,"file":"trace-store.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-panoptic/src/store/trace-store.contract.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BiB;;;;;;EAMf,IAAI,OAAO;;EAGX,IAAI,kBAAkB;;;;;EAMtB,MAAM,SAAS,aAAa;;;;;EAM5B,UAAU,SAAS,aAAa;;WAGvB;;EAGT"}