@warlock.js/ai-panoptic 4.8.1 → 4.9.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 (162) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/cjs/index.cjs +340 -42
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/collector/collector.d.mts +1 -1
  5. package/esm/collector/collector.d.mts.map +1 -1
  6. package/esm/collector/collector.mjs +1 -1
  7. package/esm/collector/collector.mjs.map +1 -1
  8. package/esm/collector/content-capture.type.d.mts +1 -1
  9. package/esm/collector/content-capture.type.d.mts.map +1 -1
  10. package/esm/collector/extract-span-attributes.d.mts +1 -1
  11. package/esm/collector/extract-span-attributes.d.mts.map +1 -1
  12. package/esm/collector/extract-span-attributes.mjs +1 -1
  13. package/esm/collector/extract-span-attributes.mjs.map +1 -1
  14. package/esm/collector/normalize-error.d.mts +1 -1
  15. package/esm/collector/normalize-error.d.mts.map +1 -1
  16. package/esm/collector/normalize-error.mjs +1 -1
  17. package/esm/collector/normalize-error.mjs.map +1 -1
  18. package/esm/collector/report-to-span.d.mts +1 -1
  19. package/esm/collector/report-to-span.d.mts.map +1 -1
  20. package/esm/collector/report-to-span.mjs +1 -1
  21. package/esm/collector/report-to-span.mjs.map +1 -1
  22. package/esm/collector/report-to-trace.d.mts +1 -1
  23. package/esm/collector/report-to-trace.d.mts.map +1 -1
  24. package/esm/collector/report-to-trace.mjs +1 -1
  25. package/esm/collector/report-to-trace.mjs.map +1 -1
  26. package/esm/config/apply-panoptic-config.d.mts +1 -1
  27. package/esm/config/apply-panoptic-config.d.mts.map +1 -1
  28. package/esm/config/apply-panoptic-config.mjs +4 -2
  29. package/esm/config/apply-panoptic-config.mjs.map +1 -1
  30. package/esm/config/panoptic-config.type.d.mts +12 -1
  31. package/esm/config/panoptic-config.type.d.mts.map +1 -1
  32. package/esm/contracts/collector.contract.d.mts +1 -1
  33. package/esm/contracts/collector.contract.d.mts.map +1 -1
  34. package/esm/contracts/exporter.contract.d.mts +1 -1
  35. package/esm/contracts/exporter.contract.d.mts.map +1 -1
  36. package/esm/contracts/trace.type.d.mts +1 -1
  37. package/esm/contracts/trace.type.d.mts.map +1 -1
  38. package/esm/dashboard/dashboard.d.mts +1 -1
  39. package/esm/dashboard/dashboard.d.mts.map +1 -1
  40. package/esm/dashboard/dashboard.mjs +3 -2
  41. package/esm/dashboard/dashboard.mjs.map +1 -1
  42. package/esm/dashboard/dashboard.type.d.mts +16 -3
  43. package/esm/dashboard/dashboard.type.d.mts.map +1 -1
  44. package/esm/dashboard/index.mjs +7 -0
  45. package/esm/dashboard/parse-query.mjs +1 -1
  46. package/esm/dashboard/parse-query.mjs.map +1 -1
  47. package/esm/dashboard/serve.mjs +124 -9
  48. package/esm/dashboard/serve.mjs.map +1 -1
  49. package/esm/dashboard/trace-filter.d.mts +1 -1
  50. package/esm/dashboard/trace-filter.d.mts.map +1 -1
  51. package/esm/dashboard/trace-filter.mjs +1 -1
  52. package/esm/dashboard/trace-filter.mjs.map +1 -1
  53. package/esm/dashboard/ui.html.mjs +134 -5
  54. package/esm/dashboard/ui.html.mjs.map +1 -1
  55. package/esm/dashboard/warlock-logo.mjs +1 -1
  56. package/esm/dashboard/warlock-logo.mjs.map +1 -1
  57. package/esm/evaluate/evaluate-system-prompt.d.mts +19 -0
  58. package/esm/evaluate/evaluate-system-prompt.d.mts.map +1 -0
  59. package/esm/evaluate/evaluate-system-prompt.mjs +22 -0
  60. package/esm/evaluate/evaluate-system-prompt.mjs.map +1 -0
  61. package/esm/evaluate/evaluate.type.d.mts +47 -0
  62. package/esm/evaluate/evaluate.type.d.mts.map +1 -0
  63. package/esm/evaluate/extract-last-system-prompt.d.mts +19 -0
  64. package/esm/evaluate/extract-last-system-prompt.d.mts.map +1 -0
  65. package/esm/evaluate/extract-last-system-prompt.mjs +24 -0
  66. package/esm/evaluate/extract-last-system-prompt.mjs.map +1 -0
  67. package/esm/evaluate/find-span-by-id.d.mts +8 -0
  68. package/esm/evaluate/find-span-by-id.d.mts.map +1 -0
  69. package/esm/evaluate/find-span-by-id.mjs +13 -0
  70. package/esm/evaluate/find-span-by-id.mjs.map +1 -0
  71. package/esm/evaluate/index.d.mts +4 -0
  72. package/esm/evaluate/index.mjs +5 -0
  73. package/esm/exporters/console/console-exporter.d.mts +1 -1
  74. package/esm/exporters/console/console-exporter.d.mts.map +1 -1
  75. package/esm/exporters/console/console-exporter.mjs +1 -1
  76. package/esm/exporters/console/console-exporter.mjs.map +1 -1
  77. package/esm/exporters/console/console-exporter.type.d.mts +1 -1
  78. package/esm/exporters/console/console-exporter.type.d.mts.map +1 -1
  79. package/esm/exporters/console/format-span-io.d.mts +1 -1
  80. package/esm/exporters/console/format-span-io.d.mts.map +1 -1
  81. package/esm/exporters/console/format-span-io.mjs +1 -1
  82. package/esm/exporters/console/format-span-io.mjs.map +1 -1
  83. package/esm/exporters/console/format-span-line.d.mts +1 -1
  84. package/esm/exporters/console/format-span-line.d.mts.map +1 -1
  85. package/esm/exporters/console/format-span-line.mjs +1 -1
  86. package/esm/exporters/console/format-span-line.mjs.map +1 -1
  87. package/esm/exporters/file/file-exporter.d.mts +1 -1
  88. package/esm/exporters/file/file-exporter.d.mts.map +1 -1
  89. package/esm/exporters/file/file-exporter.mjs +1 -1
  90. package/esm/exporters/file/file-exporter.mjs.map +1 -1
  91. package/esm/exporters/file/file-exporter.type.d.mts +1 -1
  92. package/esm/exporters/file/file-exporter.type.d.mts.map +1 -1
  93. package/esm/exporters/langfuse/langfuse-exporter.d.mts +1 -1
  94. package/esm/exporters/langfuse/langfuse-exporter.d.mts.map +1 -1
  95. package/esm/exporters/langfuse/langfuse-exporter.mjs +1 -1
  96. package/esm/exporters/langfuse/langfuse-exporter.mjs.map +1 -1
  97. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts +1 -1
  98. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts.map +1 -1
  99. package/esm/exporters/otel/otel-api.shim.type.d.mts +1 -1
  100. package/esm/exporters/otel/otel-api.shim.type.d.mts.map +1 -1
  101. package/esm/exporters/otel/otel-exporter.d.mts +1 -1
  102. package/esm/exporters/otel/otel-exporter.d.mts.map +1 -1
  103. package/esm/exporters/otel/otel-exporter.mjs +1 -1
  104. package/esm/exporters/otel/otel-exporter.mjs.map +1 -1
  105. package/esm/exporters/otel/otel-exporter.type.d.mts +1 -1
  106. package/esm/exporters/otel/otel-exporter.type.d.mts.map +1 -1
  107. package/esm/exporters/utils/gen-ai-attributes.d.mts +1 -1
  108. package/esm/exporters/utils/gen-ai-attributes.d.mts.map +1 -1
  109. package/esm/exporters/utils/gen-ai-attributes.mjs +1 -1
  110. package/esm/exporters/utils/gen-ai-attributes.mjs.map +1 -1
  111. package/esm/exporters/utils/total-cost.d.mts +1 -1
  112. package/esm/exporters/utils/total-cost.d.mts.map +1 -1
  113. package/esm/exporters/utils/total-cost.mjs +1 -1
  114. package/esm/exporters/utils/total-cost.mjs.map +1 -1
  115. package/esm/exporters/utils/walk-spans.d.mts +1 -1
  116. package/esm/exporters/utils/walk-spans.d.mts.map +1 -1
  117. package/esm/exporters/utils/walk-spans.mjs +1 -1
  118. package/esm/exporters/utils/walk-spans.mjs.map +1 -1
  119. package/esm/index.d.mts +5 -1
  120. package/esm/index.mjs +6 -1
  121. package/esm/panoptic/panoptic-middleware.d.mts +1 -1
  122. package/esm/panoptic/panoptic-middleware.d.mts.map +1 -1
  123. package/esm/panoptic/panoptic-middleware.mjs +1 -1
  124. package/esm/panoptic/panoptic-middleware.mjs.map +1 -1
  125. package/esm/panoptic/panoptic-target.type.d.mts +1 -1
  126. package/esm/panoptic/panoptic-target.type.d.mts.map +1 -1
  127. package/esm/panoptic/panoptic.d.mts +1 -1
  128. package/esm/panoptic/panoptic.d.mts.map +1 -1
  129. package/esm/panoptic/panoptic.mjs +1 -1
  130. package/esm/panoptic/panoptic.mjs.map +1 -1
  131. package/esm/panoptic/panoptic.type.d.mts +1 -1
  132. package/esm/panoptic/panoptic.type.d.mts.map +1 -1
  133. package/esm/register.mjs +1 -1
  134. package/esm/register.mjs.map +1 -1
  135. package/esm/store/cache-trace-store.d.mts +1 -1
  136. package/esm/store/cache-trace-store.d.mts.map +1 -1
  137. package/esm/store/cache-trace-store.mjs +1 -1
  138. package/esm/store/cache-trace-store.mjs.map +1 -1
  139. package/esm/store/in-memory-trace-store.d.mts +1 -1
  140. package/esm/store/in-memory-trace-store.d.mts.map +1 -1
  141. package/esm/store/in-memory-trace-store.mjs +1 -1
  142. package/esm/store/in-memory-trace-store.mjs.map +1 -1
  143. package/esm/store/match-trace.d.mts +1 -1
  144. package/esm/store/match-trace.d.mts.map +1 -1
  145. package/esm/store/match-trace.mjs +1 -1
  146. package/esm/store/match-trace.mjs.map +1 -1
  147. package/esm/store/sum-usage.d.mts +1 -1
  148. package/esm/store/sum-usage.d.mts.map +1 -1
  149. package/esm/store/sum-usage.mjs +1 -1
  150. package/esm/store/sum-usage.mjs.map +1 -1
  151. package/esm/store/trace-aggregate.type.d.mts +1 -1
  152. package/esm/store/trace-aggregate.type.d.mts.map +1 -1
  153. package/esm/store/trace-query.type.d.mts +1 -1
  154. package/esm/store/trace-query.type.d.mts.map +1 -1
  155. package/esm/store/trace-store.contract.d.mts +1 -1
  156. package/esm/store/trace-store.contract.d.mts.map +1 -1
  157. package/llms-full.txt +95 -3
  158. package/llms.txt +1 -0
  159. package/package.json +4 -3
  160. package/skills/README.md +4 -0
  161. package/skills/evaluate-system-prompt/SKILL.md +86 -0
  162. package/skills/use-local-dashboard/SKILL.md +5 -3
@@ -0,0 +1,47 @@
1
+ import { JudgeOutcome, ModelContract } from "@warlock.js/ai";
2
+
3
+ //#region ../ai-panoptic/src/evaluate/evaluate.type.d.ts
4
+ /**
5
+ * Enables the dashboard's per-trace "Evaluate system prompt" action — the
6
+ * drawer's only write-capable feature. Grades the LAST `{role: "system"}`
7
+ * message captured on a span's `input` (requires `captureContent`) against
8
+ * `instructions`, via the same LLM-as-judge machinery `ai.prompts().validate()`
9
+ * uses ({@link import("@warlock.js/ai").judgePromptBody}).
10
+ *
11
+ * Absent `evaluate` ⇒ the dashboard's write route (`POST
12
+ * {basePath}api/traces/:traceId/spans/:spanId/evaluate`) 405s like any other
13
+ * route and the drawer never renders the button — the dashboard stays fully
14
+ * read-only by default.
15
+ *
16
+ * @example
17
+ * ai.config({
18
+ * panoptic: {
19
+ * dashboard: {
20
+ * evaluate: { model: openai.model("gpt-4o-mini"), instructions: "Must address the user by name." },
21
+ * },
22
+ * },
23
+ * });
24
+ */
25
+ type EvaluateConfig = {
26
+ /**
27
+ * The judge model, or a (possibly async) factory resolved on first use —
28
+ * the factory form defers construction (e.g. an SDK client) until the
29
+ * first evaluate click. Resolved fresh on every request (evaluate is a
30
+ * deliberate, infrequent, human-triggered action — not the collector's
31
+ * hot path — so no memoization).
32
+ */
33
+ model: ModelContract | (() => ModelContract | Promise<ModelContract>);
34
+ /**
35
+ * Default grading rubric/criteria, verbatim (a single string) — the same
36
+ * `criteria` shape `ai.prompts().validate({ criteria })` accepts. Seeds
37
+ * the dashboard's editable instructions textarea; the built-in
38
+ * prompt-quality rubric is used when neither this nor a per-run override
39
+ * is supplied.
40
+ */
41
+ instructions?: string;
42
+ };
43
+ /** The judge verdict returned by the evaluate endpoint — `{@link JudgeOutcome}` verbatim. */
44
+ type EvaluateVerdict = JudgeOutcome;
45
+ //#endregion
46
+ export { EvaluateConfig, EvaluateVerdict };
47
+ //# sourceMappingURL=evaluate.type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.type.d.mts","names":[],"sources":["../../../../../../../ai-panoptic/src/evaluate/evaluate.type.ts"],"mappings":";;;;;AAuBA;;;;;;;;;;;;;;;;AAgBc;AAId;;KApBY,cAAA;EAoB8B;AAAA;;;;;;EAZxC,KAAA,EAAO,aAAA,UAAuB,aAAA,GAAgB,OAAA,CAAQ,aAAA;;;;;;;;EAQtD,YAAA;AAAA;;KAIU,eAAA,GAAkB,YAAY"}
@@ -0,0 +1,19 @@
1
+ import { TraceSpan } from "../contracts/trace.type.mjs";
2
+
3
+ //#region ../ai-panoptic/src/evaluate/extract-last-system-prompt.d.ts
4
+ /**
5
+ * Pull the LAST `{role: "system"}` message off a span's captured `input`.
6
+ *
7
+ * Only present under `captureContent` (off by default), and only shaped
8
+ * this way for non-tool spans — either the `[system, user]` first-trip pair
9
+ * or, under `fullHistory`, the full `CapturedMessage[]` conversation (see
10
+ * `collector/report-to-span.ts`). "Last" (not "only") matters for
11
+ * `fullHistory`: a long-running agent can carry more than one system-role
12
+ * turn, and the most recent one is the one actually in effect. Returns
13
+ * `undefined` for a tool span, an agent with no system prompt, or when
14
+ * content capture is off — the caller treats that as "nothing to evaluate."
15
+ */
16
+ declare function extractLastSystemPrompt(span: TraceSpan): string | undefined;
17
+ //#endregion
18
+ export { extractLastSystemPrompt };
19
+ //# sourceMappingURL=extract-last-system-prompt.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-last-system-prompt.d.mts","names":[],"sources":["../../../../../../../ai-panoptic/src/evaluate/extract-last-system-prompt.ts"],"mappings":";;;;;AAcA;;;;AAAuD;;;;;;iBAAvC,uBAAA,CAAwB,IAAe,EAAT,SAAS"}
@@ -0,0 +1,24 @@
1
+ //#region ../ai-panoptic/src/evaluate/extract-last-system-prompt.ts
2
+ /**
3
+ * Pull the LAST `{role: "system"}` message off a span's captured `input`.
4
+ *
5
+ * Only present under `captureContent` (off by default), and only shaped
6
+ * this way for non-tool spans — either the `[system, user]` first-trip pair
7
+ * or, under `fullHistory`, the full `CapturedMessage[]` conversation (see
8
+ * `collector/report-to-span.ts`). "Last" (not "only") matters for
9
+ * `fullHistory`: a long-running agent can carry more than one system-role
10
+ * turn, and the most recent one is the one actually in effect. Returns
11
+ * `undefined` for a tool span, an agent with no system prompt, or when
12
+ * content capture is off — the caller treats that as "nothing to evaluate."
13
+ */
14
+ function extractLastSystemPrompt(span) {
15
+ if (!Array.isArray(span.input)) return;
16
+ for (let index = span.input.length - 1; index >= 0; index -= 1) {
17
+ const entry = span.input[index];
18
+ if (entry && typeof entry === "object" && entry.role === "system") return typeof entry.content === "string" ? entry.content : void 0;
19
+ }
20
+ }
21
+
22
+ //#endregion
23
+ export { extractLastSystemPrompt };
24
+ //# sourceMappingURL=extract-last-system-prompt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-last-system-prompt.mjs","names":[],"sources":["../../../../../../../ai-panoptic/src/evaluate/extract-last-system-prompt.ts"],"sourcesContent":["import type { TraceSpan } from \"../contracts/trace.type\";\n\n/**\n * Pull the LAST `{role: \"system\"}` message off a span's captured `input`.\n *\n * Only present under `captureContent` (off by default), and only shaped\n * this way for non-tool spans — either the `[system, user]` first-trip pair\n * or, under `fullHistory`, the full `CapturedMessage[]` conversation (see\n * `collector/report-to-span.ts`). \"Last\" (not \"only\") matters for\n * `fullHistory`: a long-running agent can carry more than one system-role\n * turn, and the most recent one is the one actually in effect. Returns\n * `undefined` for a tool span, an agent with no system prompt, or when\n * content capture is off — the caller treats that as \"nothing to evaluate.\"\n */\nexport function extractLastSystemPrompt(span: TraceSpan): string | undefined {\n if (!Array.isArray(span.input)) {\n return undefined;\n }\n\n for (let index = span.input.length - 1; index >= 0; index -= 1) {\n const entry = span.input[index] as { role?: unknown; content?: unknown };\n\n if (entry && typeof entry === \"object\" && entry.role === \"system\") {\n return typeof entry.content === \"string\" ? entry.content : undefined;\n }\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,SAAgB,wBAAwB,MAAqC;CAC3E,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,GAC3B;CAGF,KAAK,IAAI,QAAQ,KAAK,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG;EAC9D,MAAM,QAAQ,KAAK,MAAM;EAEzB,IAAI,SAAS,OAAO,UAAU,YAAY,MAAM,SAAS,UACvD,OAAO,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU;CAE/D;AAGF"}
@@ -0,0 +1,8 @@
1
+ import { TraceSpan } from "../contracts/trace.type.mjs";
2
+
3
+ //#region ../ai-panoptic/src/evaluate/find-span-by-id.d.ts
4
+ /** Depth-first search for the span with `spanId` inside a trace's span tree. */
5
+ declare function findSpanById(root: TraceSpan, spanId: string): TraceSpan | undefined;
6
+ //#endregion
7
+ export { findSpanById };
8
+ //# sourceMappingURL=find-span-by-id.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-span-by-id.d.mts","names":[],"sources":["../../../../../../../ai-panoptic/src/evaluate/find-span-by-id.ts"],"mappings":";;;;iBAGgB,YAAA,CAAa,IAAA,EAAM,SAAA,EAAW,MAAA,WAAiB,SAAS"}
@@ -0,0 +1,13 @@
1
+ //#region ../ai-panoptic/src/evaluate/find-span-by-id.ts
2
+ /** Depth-first search for the span with `spanId` inside a trace's span tree. */
3
+ function findSpanById(root, spanId) {
4
+ if (root.spanId === spanId) return root;
5
+ for (const child of root.children) {
6
+ const found = findSpanById(child, spanId);
7
+ if (found !== void 0) return found;
8
+ }
9
+ }
10
+
11
+ //#endregion
12
+ export { findSpanById };
13
+ //# sourceMappingURL=find-span-by-id.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-span-by-id.mjs","names":[],"sources":["../../../../../../../ai-panoptic/src/evaluate/find-span-by-id.ts"],"sourcesContent":["import type { TraceSpan } from \"../contracts/trace.type\";\n\n/** Depth-first search for the span with `spanId` inside a trace's span tree. */\nexport function findSpanById(root: TraceSpan, spanId: string): TraceSpan | undefined {\n if (root.spanId === spanId) {\n return root;\n }\n\n for (const child of root.children) {\n const found = findSpanById(child, spanId);\n\n if (found !== undefined) {\n return found;\n }\n }\n\n return undefined;\n}\n"],"mappings":";;AAGA,SAAgB,aAAa,MAAiB,QAAuC;CACnF,IAAI,KAAK,WAAW,QAClB,OAAO;CAGT,KAAK,MAAM,SAAS,KAAK,UAAU;EACjC,MAAM,QAAQ,aAAa,OAAO,MAAM;EAExC,IAAI,UAAU,QACZ,OAAO;CAEX;AAGF"}
@@ -0,0 +1,4 @@
1
+ import { EvaluateConfig, EvaluateVerdict } from "./evaluate.type.mjs";
2
+ import { evaluateSystemPrompt } from "./evaluate-system-prompt.mjs";
3
+ import { extractLastSystemPrompt } from "./extract-last-system-prompt.mjs";
4
+ import { findSpanById } from "./find-span-by-id.mjs";
@@ -0,0 +1,5 @@
1
+ import { evaluateSystemPrompt } from "./evaluate-system-prompt.mjs";
2
+ import { extractLastSystemPrompt } from "./extract-last-system-prompt.mjs";
3
+ import { findSpanById } from "./find-span-by-id.mjs";
4
+
5
+ export { };
@@ -1,7 +1,7 @@
1
1
  import { ExporterContract } from "../../contracts/exporter.contract.mjs";
2
2
  import { ConsoleExporterOptions } from "./console-exporter.type.mjs";
3
3
 
4
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.d.ts
4
+ //#region ../ai-panoptic/src/exporters/console/console-exporter.d.ts
5
5
  /**
6
6
  * Zero-dependency {@link ExporterContract} that prints traces to a
7
7
  * console-like sink. The simplest exporter — useful in development and
@@ -1 +1 @@
1
- {"version":3,"file":"console-exporter.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.ts"],"mappings":";;;;;;;AAiCA;;;;;;;;AAAuF;;;;;;;;;;;;;;iBAAvE,eAAA,CAAgB,OAAA,GAAS,sBAAA,GAA8B,gBAAgB"}
1
+ {"version":3,"file":"console-exporter.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/console/console-exporter.ts"],"mappings":";;;;;;;AAiCA;;;;;;;;AAAuF;;;;;;;;;;;;;;iBAAvE,eAAA,CAAgB,OAAA,GAAS,sBAAA,GAA8B,gBAAgB"}
@@ -2,7 +2,7 @@ import { walkSpans } from "../utils/walk-spans.mjs";
2
2
  import { formatSpanIO } from "./format-span-io.mjs";
3
3
  import { formatSpanLine } from "./format-span-line.mjs";
4
4
 
5
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.ts
5
+ //#region ../ai-panoptic/src/exporters/console/console-exporter.ts
6
6
  const EXPORTER_NAME = "console";
7
7
  /**
8
8
  * Zero-dependency {@link ExporterContract} that prints traces to a
@@ -1 +1 @@
1
- {"version":3,"file":"console-exporter.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.ts"],"sourcesContent":["import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { walkSpans } from \"../utils\";\nimport type { ConsoleExporterOptions, ConsoleLike } from \"./console-exporter.type\";\nimport { formatSpanIO } from \"./format-span-io\";\nimport { formatSpanLine } from \"./format-span-line\";\n\nconst EXPORTER_NAME = \"console\";\n\n/**\n * Zero-dependency {@link ExporterContract} that prints traces to a\n * console-like sink. The simplest exporter — useful in development and\n * as the reference implementation of the contract.\n *\n * By default it prints one summary line per completed trace. Set\n * `tree: true` to print the full indented span tree, `io: true` to also\n * print each span's captured `input` / `output` (needs the collector's\n * `captureContent`), and `streaming: true` to print each span the moment\n * it finalizes (via the optional `exportSpan` hook).\n *\n * @example\n * collector.use(consoleExporter());\n * // ok workflow \"checkout\" — 2103ms, 1820 tok, $0.0094\n *\n * @example\n * collector.use(consoleExporter({ tree: true }));\n *\n * @example\n * // Full content trace — prompts, responses, and tool I/O:\n * const observe = panoptic({\n * captureContent: true,\n * exporters: [consoleExporter({ tree: true, io: true })],\n * });\n */\nexport function consoleExporter(options: ConsoleExporterOptions = {}): ExporterContract {\n const sink: ConsoleLike = options.console ?? console;\n const tree = options.tree ?? false;\n const io = options.io ?? false;\n const ioMaxChars = options.ioMaxChars;\n\n const exporter: ExporterContract = {\n name: EXPORTER_NAME,\n export(trace: Trace): void {\n writeTrace(sink, trace, tree, io, ioMaxChars);\n },\n };\n\n if (options.streaming) {\n exporter.exportSpan = (span: TraceSpan): void => {\n sink.log(formatSpanLine(span));\n\n if (io) {\n for (const line of formatSpanIO(span, 0, ioMaxChars)) {\n sink.log(line);\n }\n }\n };\n }\n\n return exporter;\n}\n\n/**\n * Write a completed trace — either a single root summary line or the\n * full indented tree, each span optionally followed by its captured\n * `input` / `output`. Failed / cancelled spans route to `console.error`\n * so they surface at the right severity in log aggregators.\n */\nfunction writeTrace(\n sink: ConsoleLike,\n trace: Trace,\n tree: boolean,\n io: boolean,\n ioMaxChars: number | undefined,\n): void {\n if (!tree) {\n writeSpan(sink, trace.root, 0, io, ioMaxChars);\n return;\n }\n\n for (const span of walkSpans(trace.root)) {\n const depth = spanDepth(trace.root, span.spanId);\n writeSpan(sink, span, depth, io, ioMaxChars);\n }\n}\n\n/**\n * Write one span's line and — when `io` is on — its captured content,\n * all routed at the span's own severity so a failed span keeps its\n * content beside it in the error stream.\n */\nfunction writeSpan(\n sink: ConsoleLike,\n span: TraceSpan,\n depth: number,\n io: boolean,\n ioMaxChars: number | undefined,\n): void {\n writeAtSeverity(sink, span.status, formatSpanLine(span, depth));\n\n if (io) {\n for (const line of formatSpanIO(span, depth, ioMaxChars)) {\n writeAtSeverity(sink, span.status, line);\n }\n }\n}\n\n/**\n * Route a line to `error` when the span failed/cancelled, otherwise to\n * `log`. Keeps healthy traces out of the error stream.\n */\nfunction writeAtSeverity(sink: ConsoleLike, status: TraceSpan[\"status\"], line: string): void {\n if (status === \"failed\" || status === \"cancelled\") {\n sink.error(line);\n return;\n }\n\n sink.log(line);\n}\n\n/**\n * Depth of `targetSpanId` below `root` for indentation. Walks the tree\n * once; returns 0 when the span is the root or not found.\n */\nfunction spanDepth(root: TraceSpan, targetSpanId: string, depth = 0): number {\n if (root.spanId === targetSpanId) {\n return depth;\n }\n\n for (const child of root.children) {\n const found = spanDepth(child, targetSpanId, depth + 1);\n\n if (found > 0) {\n return found;\n }\n }\n\n return 0;\n}\n"],"mappings":";;;;;AAMA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BtB,SAAgB,gBAAgB,UAAkC,CAAC,GAAqB;CACtF,MAAM,OAAoB,QAAQ,WAAW;CAC7C,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,KAAK,QAAQ,MAAM;CACzB,MAAM,aAAa,QAAQ;CAE3B,MAAM,WAA6B;EACjC,MAAM;EACN,OAAO,OAAoB;GACzB,WAAW,MAAM,OAAO,MAAM,IAAI,UAAU;EAC9C;CACF;CAEA,IAAI,QAAQ,WACV,SAAS,cAAc,SAA0B;EAC/C,KAAK,IAAI,eAAe,IAAI,CAAC;EAE7B,IAAI,IACF,KAAK,MAAM,QAAQ,aAAa,MAAM,GAAG,UAAU,GACjD,KAAK,IAAI,IAAI;CAGnB;CAGF,OAAO;AACT;;;;;;;AAQA,SAAS,WACP,MACA,OACA,MACA,IACA,YACM;CACN,IAAI,CAAC,MAAM;EACT,UAAU,MAAM,MAAM,MAAM,GAAG,IAAI,UAAU;EAC7C;CACF;CAEA,KAAK,MAAM,QAAQ,UAAU,MAAM,IAAI,GAErC,UAAU,MAAM,MADF,UAAU,MAAM,MAAM,KAAK,MACf,GAAG,IAAI,UAAU;AAE/C;;;;;;AAOA,SAAS,UACP,MACA,MACA,OACA,IACA,YACM;CACN,gBAAgB,MAAM,KAAK,QAAQ,eAAe,MAAM,KAAK,CAAC;CAE9D,IAAI,IACF,KAAK,MAAM,QAAQ,aAAa,MAAM,OAAO,UAAU,GACrD,gBAAgB,MAAM,KAAK,QAAQ,IAAI;AAG7C;;;;;AAMA,SAAS,gBAAgB,MAAmB,QAA6B,MAAoB;CAC3F,IAAI,WAAW,YAAY,WAAW,aAAa;EACjD,KAAK,MAAM,IAAI;EACf;CACF;CAEA,KAAK,IAAI,IAAI;AACf;;;;;AAMA,SAAS,UAAU,MAAiB,cAAsB,QAAQ,GAAW;CAC3E,IAAI,KAAK,WAAW,cAClB,OAAO;CAGT,KAAK,MAAM,SAAS,KAAK,UAAU;EACjC,MAAM,QAAQ,UAAU,OAAO,cAAc,QAAQ,CAAC;EAEtD,IAAI,QAAQ,GACV,OAAO;CAEX;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"console-exporter.mjs","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/console/console-exporter.ts"],"sourcesContent":["import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { walkSpans } from \"../utils\";\nimport type { ConsoleExporterOptions, ConsoleLike } from \"./console-exporter.type\";\nimport { formatSpanIO } from \"./format-span-io\";\nimport { formatSpanLine } from \"./format-span-line\";\n\nconst EXPORTER_NAME = \"console\";\n\n/**\n * Zero-dependency {@link ExporterContract} that prints traces to a\n * console-like sink. The simplest exporter — useful in development and\n * as the reference implementation of the contract.\n *\n * By default it prints one summary line per completed trace. Set\n * `tree: true` to print the full indented span tree, `io: true` to also\n * print each span's captured `input` / `output` (needs the collector's\n * `captureContent`), and `streaming: true` to print each span the moment\n * it finalizes (via the optional `exportSpan` hook).\n *\n * @example\n * collector.use(consoleExporter());\n * // ok workflow \"checkout\" — 2103ms, 1820 tok, $0.0094\n *\n * @example\n * collector.use(consoleExporter({ tree: true }));\n *\n * @example\n * // Full content trace — prompts, responses, and tool I/O:\n * const observe = panoptic({\n * captureContent: true,\n * exporters: [consoleExporter({ tree: true, io: true })],\n * });\n */\nexport function consoleExporter(options: ConsoleExporterOptions = {}): ExporterContract {\n const sink: ConsoleLike = options.console ?? console;\n const tree = options.tree ?? false;\n const io = options.io ?? false;\n const ioMaxChars = options.ioMaxChars;\n\n const exporter: ExporterContract = {\n name: EXPORTER_NAME,\n export(trace: Trace): void {\n writeTrace(sink, trace, tree, io, ioMaxChars);\n },\n };\n\n if (options.streaming) {\n exporter.exportSpan = (span: TraceSpan): void => {\n sink.log(formatSpanLine(span));\n\n if (io) {\n for (const line of formatSpanIO(span, 0, ioMaxChars)) {\n sink.log(line);\n }\n }\n };\n }\n\n return exporter;\n}\n\n/**\n * Write a completed trace — either a single root summary line or the\n * full indented tree, each span optionally followed by its captured\n * `input` / `output`. Failed / cancelled spans route to `console.error`\n * so they surface at the right severity in log aggregators.\n */\nfunction writeTrace(\n sink: ConsoleLike,\n trace: Trace,\n tree: boolean,\n io: boolean,\n ioMaxChars: number | undefined,\n): void {\n if (!tree) {\n writeSpan(sink, trace.root, 0, io, ioMaxChars);\n return;\n }\n\n for (const span of walkSpans(trace.root)) {\n const depth = spanDepth(trace.root, span.spanId);\n writeSpan(sink, span, depth, io, ioMaxChars);\n }\n}\n\n/**\n * Write one span's line and — when `io` is on — its captured content,\n * all routed at the span's own severity so a failed span keeps its\n * content beside it in the error stream.\n */\nfunction writeSpan(\n sink: ConsoleLike,\n span: TraceSpan,\n depth: number,\n io: boolean,\n ioMaxChars: number | undefined,\n): void {\n writeAtSeverity(sink, span.status, formatSpanLine(span, depth));\n\n if (io) {\n for (const line of formatSpanIO(span, depth, ioMaxChars)) {\n writeAtSeverity(sink, span.status, line);\n }\n }\n}\n\n/**\n * Route a line to `error` when the span failed/cancelled, otherwise to\n * `log`. Keeps healthy traces out of the error stream.\n */\nfunction writeAtSeverity(sink: ConsoleLike, status: TraceSpan[\"status\"], line: string): void {\n if (status === \"failed\" || status === \"cancelled\") {\n sink.error(line);\n return;\n }\n\n sink.log(line);\n}\n\n/**\n * Depth of `targetSpanId` below `root` for indentation. Walks the tree\n * once; returns 0 when the span is the root or not found.\n */\nfunction spanDepth(root: TraceSpan, targetSpanId: string, depth = 0): number {\n if (root.spanId === targetSpanId) {\n return depth;\n }\n\n for (const child of root.children) {\n const found = spanDepth(child, targetSpanId, depth + 1);\n\n if (found > 0) {\n return found;\n }\n }\n\n return 0;\n}\n"],"mappings":";;;;;AAMA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BtB,SAAgB,gBAAgB,UAAkC,CAAC,GAAqB;CACtF,MAAM,OAAoB,QAAQ,WAAW;CAC7C,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,KAAK,QAAQ,MAAM;CACzB,MAAM,aAAa,QAAQ;CAE3B,MAAM,WAA6B;EACjC,MAAM;EACN,OAAO,OAAoB;GACzB,WAAW,MAAM,OAAO,MAAM,IAAI,UAAU;EAC9C;CACF;CAEA,IAAI,QAAQ,WACV,SAAS,cAAc,SAA0B;EAC/C,KAAK,IAAI,eAAe,IAAI,CAAC;EAE7B,IAAI,IACF,KAAK,MAAM,QAAQ,aAAa,MAAM,GAAG,UAAU,GACjD,KAAK,IAAI,IAAI;CAGnB;CAGF,OAAO;AACT;;;;;;;AAQA,SAAS,WACP,MACA,OACA,MACA,IACA,YACM;CACN,IAAI,CAAC,MAAM;EACT,UAAU,MAAM,MAAM,MAAM,GAAG,IAAI,UAAU;EAC7C;CACF;CAEA,KAAK,MAAM,QAAQ,UAAU,MAAM,IAAI,GAErC,UAAU,MAAM,MADF,UAAU,MAAM,MAAM,KAAK,MACf,GAAG,IAAI,UAAU;AAE/C;;;;;;AAOA,SAAS,UACP,MACA,MACA,OACA,IACA,YACM;CACN,gBAAgB,MAAM,KAAK,QAAQ,eAAe,MAAM,KAAK,CAAC;CAE9D,IAAI,IACF,KAAK,MAAM,QAAQ,aAAa,MAAM,OAAO,UAAU,GACrD,gBAAgB,MAAM,KAAK,QAAQ,IAAI;AAG7C;;;;;AAMA,SAAS,gBAAgB,MAAmB,QAA6B,MAAoB;CAC3F,IAAI,WAAW,YAAY,WAAW,aAAa;EACjD,KAAK,MAAM,IAAI;EACf;CACF;CAEA,KAAK,IAAI,IAAI;AACf;;;;;AAMA,SAAS,UAAU,MAAiB,cAAsB,QAAQ,GAAW;CAC3E,IAAI,KAAK,WAAW,cAClB,OAAO;CAGT,KAAK,MAAM,SAAS,KAAK,UAAU;EACjC,MAAM,QAAQ,UAAU,OAAO,cAAc,QAAQ,CAAC;EAEtD,IAAI,QAAQ,GACV,OAAO;CAEX;CAEA,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.type.d.ts
1
+ //#region ../ai-panoptic/src/exporters/console/console-exporter.type.d.ts
2
2
  /**
3
3
  * Sink a {@link ConsoleExporter} writes formatted lines to. Defaults to
4
4
  * the global `console`; injectable so tests can capture output without
@@ -1 +1 @@
1
- {"version":3,"file":"console-exporter.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.type.ts"],"mappings":";;AAMA;;;;;KAAY,WAAA;EACV,GAAA,IAAO,IAAA;EACP,KAAA,IAAS,IAAA;AAAA;AAAI;AAMf;;AANe,KAMH,sBAAA;EAKW;;;;EAArB,OAAA,GAAU,WAAW;EAoBrB;;;AAOU;;EArBV,IAAA;;;;;;EAMA,SAAA;;;;;;;;EAQA,EAAA;;;;;;;EAOA,UAAA;AAAA"}
1
+ {"version":3,"file":"console-exporter.type.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/console/console-exporter.type.ts"],"mappings":";;AAMA;;;;;KAAY,WAAA;EACV,GAAA,IAAO,IAAA;EACP,KAAA,IAAS,IAAA;AAAA;AAAI;AAMf;;AANe,KAMH,sBAAA;EAKW;;;;EAArB,OAAA,GAAU,WAAW;EAoBrB;;;AAOU;;EArBV,IAAA;;;;;;EAMA,SAAA;;;;;;;;EAQA,EAAA;;;;;;;EAOA,UAAA;AAAA"}
@@ -1,5 +1,5 @@
1
1
  import { TraceSpan } from "../../contracts/trace.type.mjs";
2
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/format-span-io.d.ts
2
+ //#region ../ai-panoptic/src/exporters/console/format-span-io.d.ts
3
3
  /**
4
4
  * Render a span's captured content ({@link TraceSpan.input} /
5
5
  * {@link TraceSpan.output}) as extra indented console lines, one level
@@ -1 +1 @@
1
- {"version":3,"file":"format-span-io.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/format-span-io.ts"],"mappings":";;;;;AA0BA;;;;;;;;;AAAwF;;;;;;;;;iBAAxE,YAAA,CAAa,IAAA,EAAM,SAAS,EAAE,KAAA,WAAW,QAAA"}
1
+ {"version":3,"file":"format-span-io.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/console/format-span-io.ts"],"mappings":";;;;;AA0BA;;;;;;;;;AAAwF;;;;;;;;;iBAAxE,YAAA,CAAa,IAAA,EAAM,SAAS,EAAE,KAAA,WAAW,QAAA"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/format-span-io.ts
1
+ //#region ../ai-panoptic/src/exporters/console/format-span-io.ts
2
2
  /** Default cap on how many characters of captured content a console line shows. */
3
3
  const DEFAULT_IO_MAX_CHARS = 500;
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"format-span-io.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/format-span-io.ts"],"sourcesContent":["import type { TraceSpan } from \"../../contracts\";\n\n/** Default cap on how many characters of captured content a console line shows. */\nconst DEFAULT_IO_MAX_CHARS = 500;\n\n/**\n * Render a span's captured content ({@link TraceSpan.input} /\n * {@link TraceSpan.output}) as extra indented console lines, one level\n * below the span's own line:\n *\n * ```text\n * ok agent \"market-research\" — 1794ms, 224 tok, $0.0008\n * in: Research the market for Acme Coffee Roasters …\n * out: Demand is steady; specialty buyers skew premium …\n * ```\n *\n * Returns `[]` when the span carries no content — capture disabled, or a\n * composite node with no own I/O. Each value is stringified (JSON for\n * non-strings), whitespace-collapsed to stay scannable, and truncated to\n * `maxChars` (default {@link DEFAULT_IO_MAX_CHARS}) with an ellipsis. Use\n * the file exporter for the full, untruncated payload.\n *\n * @example\n * formatSpanIO(toolSpan, 1);\n * // [' in: {\"query\":\"specialty coffee demand\"}', ' out: {\"results\":[…]}']\n */\nexport function formatSpanIO(span: TraceSpan, depth = 0, maxChars = DEFAULT_IO_MAX_CHARS): string[] {\n const lines: string[] = [];\n const indent = \" \".repeat(depth + 1);\n\n if (span.input !== undefined) {\n lines.push(`${indent}in: ${preview(span.input, maxChars)}`);\n }\n\n if (span.output !== undefined) {\n lines.push(`${indent}out: ${preview(span.output, maxChars)}`);\n }\n\n return lines;\n}\n\n/**\n * One-line, length-capped preview of a captured value. Strings pass\n * through; everything else is JSON-stringified (falling back to\n * `String()` on a circular / unstringifiable value). Internal whitespace\n * is collapsed so the preview never breaks the tree layout.\n */\nfunction preview(value: unknown, maxChars: number): string {\n const text = typeof value === \"string\" ? value : stringify(value);\n const collapsed = text.replace(/\\s+/g, \" \").trim();\n\n return collapsed.length > maxChars ? `${collapsed.slice(0, maxChars)}…` : collapsed;\n}\n\nfunction stringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n"],"mappings":";;AAGA,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;AAuB7B,SAAgB,aAAa,MAAiB,QAAQ,GAAG,WAAW,sBAAgC;CAClG,MAAM,QAAkB,CAAC;CACzB,MAAM,SAAS,KAAK,OAAO,QAAQ,CAAC;CAEpC,IAAI,KAAK,UAAU,QACjB,MAAM,KAAK,GAAG,OAAO,OAAO,QAAQ,KAAK,OAAO,QAAQ,GAAG;CAG7D,IAAI,KAAK,WAAW,QAClB,MAAM,KAAK,GAAG,OAAO,OAAO,QAAQ,KAAK,QAAQ,QAAQ,GAAG;CAG9D,OAAO;AACT;;;;;;;AAQA,SAAS,QAAQ,OAAgB,UAA0B;CAEzD,MAAM,aADO,OAAO,UAAU,WAAW,QAAQ,UAAU,KAAK,EAC1C,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;CAEjD,OAAO,UAAU,SAAS,WAAW,GAAG,UAAU,MAAM,GAAG,QAAQ,EAAE,KAAK;AAC5E;AAEA,SAAS,UAAU,OAAwB;CACzC,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;CAC9C,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF"}
1
+ {"version":3,"file":"format-span-io.mjs","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/console/format-span-io.ts"],"sourcesContent":["import type { TraceSpan } from \"../../contracts\";\n\n/** Default cap on how many characters of captured content a console line shows. */\nconst DEFAULT_IO_MAX_CHARS = 500;\n\n/**\n * Render a span's captured content ({@link TraceSpan.input} /\n * {@link TraceSpan.output}) as extra indented console lines, one level\n * below the span's own line:\n *\n * ```text\n * ok agent \"market-research\" — 1794ms, 224 tok, $0.0008\n * in: Research the market for Acme Coffee Roasters …\n * out: Demand is steady; specialty buyers skew premium …\n * ```\n *\n * Returns `[]` when the span carries no content — capture disabled, or a\n * composite node with no own I/O. Each value is stringified (JSON for\n * non-strings), whitespace-collapsed to stay scannable, and truncated to\n * `maxChars` (default {@link DEFAULT_IO_MAX_CHARS}) with an ellipsis. Use\n * the file exporter for the full, untruncated payload.\n *\n * @example\n * formatSpanIO(toolSpan, 1);\n * // [' in: {\"query\":\"specialty coffee demand\"}', ' out: {\"results\":[…]}']\n */\nexport function formatSpanIO(span: TraceSpan, depth = 0, maxChars = DEFAULT_IO_MAX_CHARS): string[] {\n const lines: string[] = [];\n const indent = \" \".repeat(depth + 1);\n\n if (span.input !== undefined) {\n lines.push(`${indent}in: ${preview(span.input, maxChars)}`);\n }\n\n if (span.output !== undefined) {\n lines.push(`${indent}out: ${preview(span.output, maxChars)}`);\n }\n\n return lines;\n}\n\n/**\n * One-line, length-capped preview of a captured value. Strings pass\n * through; everything else is JSON-stringified (falling back to\n * `String()` on a circular / unstringifiable value). Internal whitespace\n * is collapsed so the preview never breaks the tree layout.\n */\nfunction preview(value: unknown, maxChars: number): string {\n const text = typeof value === \"string\" ? value : stringify(value);\n const collapsed = text.replace(/\\s+/g, \" \").trim();\n\n return collapsed.length > maxChars ? `${collapsed.slice(0, maxChars)}…` : collapsed;\n}\n\nfunction stringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n"],"mappings":";;AAGA,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;AAuB7B,SAAgB,aAAa,MAAiB,QAAQ,GAAG,WAAW,sBAAgC;CAClG,MAAM,QAAkB,CAAC;CACzB,MAAM,SAAS,KAAK,OAAO,QAAQ,CAAC;CAEpC,IAAI,KAAK,UAAU,QACjB,MAAM,KAAK,GAAG,OAAO,OAAO,QAAQ,KAAK,OAAO,QAAQ,GAAG;CAG7D,IAAI,KAAK,WAAW,QAClB,MAAM,KAAK,GAAG,OAAO,OAAO,QAAQ,KAAK,QAAQ,QAAQ,GAAG;CAG9D,OAAO;AACT;;;;;;;AAQA,SAAS,QAAQ,OAAgB,UAA0B;CAEzD,MAAM,aADO,OAAO,UAAU,WAAW,QAAQ,UAAU,KAAK,EAC1C,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;CAEjD,OAAO,UAAU,SAAS,WAAW,GAAG,UAAU,MAAM,GAAG,QAAQ,EAAE,KAAK;AAC5E;AAEA,SAAS,UAAU,OAAwB;CACzC,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;CAC9C,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { TraceSpan } from "../../contracts/trace.type.mjs";
2
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.d.ts
2
+ //#region ../ai-panoptic/src/exporters/console/format-span-line.d.ts
3
3
  /**
4
4
  * Render a single {@link TraceSpan} as one scannable console line:
5
5
  * `<status> <type> "<name>" — <duration>ms, <tokens> tok[, $<cost>]`.
@@ -1 +1 @@
1
- {"version":3,"file":"format-span-line.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.ts"],"mappings":";;;;;AAcA;;;;;;;;iBAAgB,cAAA,CAAe,IAAA,EAAM,SAAS,EAAE,KAAA"}
1
+ {"version":3,"file":"format-span-line.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/console/format-span-line.ts"],"mappings":";;;;;AAcA;;;;;;;;iBAAgB,cAAA,CAAe,IAAA,EAAM,SAAS,EAAE,KAAA"}
@@ -1,6 +1,6 @@
1
1
  import { totalCostUsd } from "../utils/total-cost.mjs";
2
2
 
3
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.ts
3
+ //#region ../ai-panoptic/src/exporters/console/format-span-line.ts
4
4
  /**
5
5
  * Render a single {@link TraceSpan} as one scannable console line:
6
6
  * `<status> <type> "<name>" — <duration>ms, <tokens> tok[, $<cost>]`.
@@ -1 +1 @@
1
- {"version":3,"file":"format-span-line.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.ts"],"sourcesContent":["import type { TraceSpan } from \"../../contracts\";\nimport { totalCostUsd } from \"../utils\";\n\n/**\n * Render a single {@link TraceSpan} as one scannable console line:\n * `<status> <type> \"<name>\" — <duration>ms, <tokens> tok[, $<cost>]`.\n * `depth` controls leading indentation when printing a tree. Pure — no\n * side effects — so it is trivially testable and reused by both the\n * per-trace summary and the per-span streaming line.\n *\n * @example\n * formatSpanLine(span, 1);\n * // ' ok agent \"router\" — 1240ms, 470 tok, $0.0021'\n */\nexport function formatSpanLine(span: TraceSpan, depth = 0): string {\n const indent = \" \".repeat(depth);\n const marker = statusMarker(span.status);\n const cost = totalCostUsd(span.usage);\n const costSuffix = cost === undefined ? \"\" : `, $${cost.toFixed(4)}`;\n\n let line = `${indent}${marker} ${span.type} \"${span.name}\" — ${span.duration}ms, ${span.usage.total} tok${costSuffix}`;\n\n if (span.error) {\n line += ` [${span.error.type}: ${span.error.message}]`;\n }\n\n return line;\n}\n\n/**\n * Short ASCII marker for a span's terminal status. Plain ASCII (no\n * emoji/color codes) so output stays clean in log aggregators and CI.\n */\nfunction statusMarker(status: TraceSpan[\"status\"]): string {\n switch (status) {\n case \"completed\":\n return \"ok\";\n case \"failed\":\n return \"ERR\";\n case \"cancelled\":\n return \"cancel\";\n case \"max-iterations\":\n return \"max-iter\";\n case \"awaiting-input\":\n return \"await\";\n default:\n return status;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,eAAe,MAAiB,QAAQ,GAAW;CACjE,MAAM,SAAS,KAAK,OAAO,KAAK;CAChC,MAAM,SAAS,aAAa,KAAK,MAAM;CACvC,MAAM,OAAO,aAAa,KAAK,KAAK;CACpC,MAAM,aAAa,SAAS,SAAY,KAAK,MAAM,KAAK,QAAQ,CAAC;CAEjE,IAAI,OAAO,GAAG,SAAS,OAAO,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,SAAS,MAAM,KAAK,MAAM,MAAM,MAAM;CAE1G,IAAI,KAAK,OACP,QAAQ,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,QAAQ;CAGtD,OAAO;AACT;;;;;AAMA,SAAS,aAAa,QAAqC;CACzD,QAAQ,QAAR;EACE,KAAK,aACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,kBACH,OAAO;EACT,KAAK,kBACH,OAAO;EACT,SACE,OAAO;CACX;AACF"}
1
+ {"version":3,"file":"format-span-line.mjs","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/console/format-span-line.ts"],"sourcesContent":["import type { TraceSpan } from \"../../contracts\";\nimport { totalCostUsd } from \"../utils\";\n\n/**\n * Render a single {@link TraceSpan} as one scannable console line:\n * `<status> <type> \"<name>\" — <duration>ms, <tokens> tok[, $<cost>]`.\n * `depth` controls leading indentation when printing a tree. Pure — no\n * side effects — so it is trivially testable and reused by both the\n * per-trace summary and the per-span streaming line.\n *\n * @example\n * formatSpanLine(span, 1);\n * // ' ok agent \"router\" — 1240ms, 470 tok, $0.0021'\n */\nexport function formatSpanLine(span: TraceSpan, depth = 0): string {\n const indent = \" \".repeat(depth);\n const marker = statusMarker(span.status);\n const cost = totalCostUsd(span.usage);\n const costSuffix = cost === undefined ? \"\" : `, $${cost.toFixed(4)}`;\n\n let line = `${indent}${marker} ${span.type} \"${span.name}\" — ${span.duration}ms, ${span.usage.total} tok${costSuffix}`;\n\n if (span.error) {\n line += ` [${span.error.type}: ${span.error.message}]`;\n }\n\n return line;\n}\n\n/**\n * Short ASCII marker for a span's terminal status. Plain ASCII (no\n * emoji/color codes) so output stays clean in log aggregators and CI.\n */\nfunction statusMarker(status: TraceSpan[\"status\"]): string {\n switch (status) {\n case \"completed\":\n return \"ok\";\n case \"failed\":\n return \"ERR\";\n case \"cancelled\":\n return \"cancel\";\n case \"max-iterations\":\n return \"max-iter\";\n case \"awaiting-input\":\n return \"await\";\n default:\n return status;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,eAAe,MAAiB,QAAQ,GAAW;CACjE,MAAM,SAAS,KAAK,OAAO,KAAK;CAChC,MAAM,SAAS,aAAa,KAAK,MAAM;CACvC,MAAM,OAAO,aAAa,KAAK,KAAK;CACpC,MAAM,aAAa,SAAS,SAAY,KAAK,MAAM,KAAK,QAAQ,CAAC;CAEjE,IAAI,OAAO,GAAG,SAAS,OAAO,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,SAAS,MAAM,KAAK,MAAM,MAAM,MAAM;CAE1G,IAAI,KAAK,OACP,QAAQ,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,QAAQ;CAGtD,OAAO;AACT;;;;;AAMA,SAAS,aAAa,QAAqC;CACzD,QAAQ,QAAR;EACE,KAAK,aACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,kBACH,OAAO;EACT,KAAK,kBACH,OAAO;EACT,SACE,OAAO;CACX;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { ExporterContract } from "../../contracts/exporter.contract.mjs";
2
2
  import { FileExporterOptions } from "./file-exporter.type.mjs";
3
3
 
4
- //#region ../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.d.ts
4
+ //#region ../ai-panoptic/src/exporters/file/file-exporter.d.ts
5
5
  /**
6
6
  * Zero-dependency {@link ExporterContract} that appends completed traces
7
7
  * to a JSON-Lines file (one JSON record per line by default). Buffers in
@@ -1 +1 @@
1
- {"version":3,"file":"file-exporter.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts"],"mappings":";;;;;;;AAsBA;;;;;;;;AAA4E;;;;iBAA5D,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,gBAAgB"}
1
+ {"version":3,"file":"file-exporter.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/file/file-exporter.ts"],"mappings":";;;;;;;AAsBA;;;;;;;;AAA4E;;;;iBAA5D,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,gBAAgB"}
@@ -1,7 +1,7 @@
1
1
  import { appendFile, mkdir } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
3
 
4
- //#region ../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts
4
+ //#region ../ai-panoptic/src/exporters/file/file-exporter.ts
5
5
  const EXPORTER_NAME = "file";
6
6
  /**
7
7
  * Zero-dependency {@link ExporterContract} that appends completed traces
@@ -1 +1 @@
1
- {"version":3,"file":"file-exporter.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts"],"sourcesContent":["import { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { ExporterContract, Trace } from \"../../contracts\";\nimport type { FileExporterOptions, TraceRecord } from \"./file-exporter.type\";\n\nconst EXPORTER_NAME = \"file\";\n\n/**\n * Zero-dependency {@link ExporterContract} that appends completed traces\n * to a JSON-Lines file (one JSON record per line by default). Buffers in\n * memory and flushes either every `flushEvery` traces or on an explicit\n * `flush()` / `shutdown()`, so a batch of traces costs one append.\n *\n * Useful as a durable local sink (replay traces later, ship the file to\n * a backend out of band) and as a test fixture for the pipeline without\n * a vendor SDK.\n *\n * @example\n * collector.use(fileExporter({ path: \"storage/traces.jsonl\" }));\n * // on shutdown:\n * await collector.shutdown(); // drains the buffer\n */\nexport function fileExporter(options: FileExporterOptions): ExporterContract {\n const writer = new FileTraceWriter(options);\n\n return {\n name: EXPORTER_NAME,\n async export(trace: Trace): Promise<void> {\n await writer.add(trace);\n },\n async flush(): Promise<void> {\n await writer.flush();\n },\n async shutdown(): Promise<void> {\n await writer.flush();\n },\n };\n}\n\n/**\n * Internal buffered writer for {@link fileExporter}. Owns the pending\n * trace buffer and the directory-created guard across the exporter's\n * lifetime; kept unexported so callers only ever see the factory.\n */\nclass FileTraceWriter {\n private readonly path: string;\n private readonly flushEvery: number;\n private readonly pretty: boolean;\n private buffer: TraceRecord[] = [];\n private directoryReady = false;\n\n public constructor(options: FileExporterOptions) {\n this.path = options.path;\n this.flushEvery = Math.max(1, options.flushEvery ?? 1);\n this.pretty = options.pretty ?? false;\n }\n\n /**\n * Buffer one trace and flush when the buffer reaches `flushEvery`.\n */\n public async add(trace: Trace): Promise<void> {\n this.buffer.push({\n type: \"trace\",\n exportedAt: new Date().toISOString(),\n trace,\n });\n\n if (this.buffer.length >= this.flushEvery) {\n await this.flush();\n }\n }\n\n /**\n * Serialize and append every buffered record, then clear the buffer.\n * No-op when nothing is pending so callers can flush defensively.\n */\n public async flush(): Promise<void> {\n if (this.buffer.length === 0) {\n return;\n }\n\n const pending = this.buffer;\n this.buffer = [];\n\n await this.ensureDirectory();\n\n const payload = pending.map((record) => this.serialize(record)).join(\"\");\n\n await appendFile(this.path, payload, \"utf8\");\n }\n\n /**\n * Create the parent directory once, lazily, on the first write. Stores\n * a guard so subsequent flushes skip the syscall.\n */\n private async ensureDirectory(): Promise<void> {\n if (this.directoryReady) {\n return;\n }\n\n await mkdir(dirname(this.path), { recursive: true });\n this.directoryReady = true;\n }\n\n /**\n * Render one record as a newline-terminated JSON string. Pretty mode\n * indents for human reading; compact mode keeps the file valid JSON\n * Lines (exactly one record per physical line).\n */\n private serialize(record: TraceRecord): string {\n const json = this.pretty\n ? JSON.stringify(record, undefined, 2)\n : JSON.stringify(record);\n\n return `${json}\\n`;\n }\n}\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;;;;;;;;;;;;;;;;AAiBtB,SAAgB,aAAa,SAAgD;CAC3E,MAAM,SAAS,IAAI,gBAAgB,OAAO;CAE1C,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAA6B;GACxC,MAAM,OAAO,IAAI,KAAK;EACxB;EACA,MAAM,QAAuB;GAC3B,MAAM,OAAO,MAAM;EACrB;EACA,MAAM,WAA0B;GAC9B,MAAM,OAAO,MAAM;EACrB;CACF;AACF;;;;;;AAOA,IAAM,kBAAN,MAAsB;CACpB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ,SAAwB,CAAC;CACjC,AAAQ,iBAAiB;CAEzB,AAAO,YAAY,SAA8B;EAC/C,KAAK,OAAO,QAAQ;EACpB,KAAK,aAAa,KAAK,IAAI,GAAG,QAAQ,cAAc,CAAC;EACrD,KAAK,SAAS,QAAQ,UAAU;CAClC;;;;CAKA,MAAa,IAAI,OAA6B;EAC5C,KAAK,OAAO,KAAK;GACf,MAAM;GACN,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY;GACnC;EACF,CAAC;EAED,IAAI,KAAK,OAAO,UAAU,KAAK,YAC7B,MAAM,KAAK,MAAM;CAErB;;;;;CAMA,MAAa,QAAuB;EAClC,IAAI,KAAK,OAAO,WAAW,GACzB;EAGF,MAAM,UAAU,KAAK;EACrB,KAAK,SAAS,CAAC;EAEf,MAAM,KAAK,gBAAgB;EAE3B,MAAM,UAAU,QAAQ,KAAK,WAAW,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE;EAEvE,MAAM,WAAW,KAAK,MAAM,SAAS,MAAM;CAC7C;;;;;CAMA,MAAc,kBAAiC;EAC7C,IAAI,KAAK,gBACP;EAGF,MAAM,MAAM,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EACnD,KAAK,iBAAiB;CACxB;;;;;;CAOA,AAAQ,UAAU,QAA6B;EAK7C,OAAO,GAJM,KAAK,SACd,KAAK,UAAU,QAAQ,QAAW,CAAC,IACnC,KAAK,UAAU,MAAM,EAEV;CACjB;AACF"}
1
+ {"version":3,"file":"file-exporter.mjs","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/file/file-exporter.ts"],"sourcesContent":["import { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { ExporterContract, Trace } from \"../../contracts\";\nimport type { FileExporterOptions, TraceRecord } from \"./file-exporter.type\";\n\nconst EXPORTER_NAME = \"file\";\n\n/**\n * Zero-dependency {@link ExporterContract} that appends completed traces\n * to a JSON-Lines file (one JSON record per line by default). Buffers in\n * memory and flushes either every `flushEvery` traces or on an explicit\n * `flush()` / `shutdown()`, so a batch of traces costs one append.\n *\n * Useful as a durable local sink (replay traces later, ship the file to\n * a backend out of band) and as a test fixture for the pipeline without\n * a vendor SDK.\n *\n * @example\n * collector.use(fileExporter({ path: \"storage/traces.jsonl\" }));\n * // on shutdown:\n * await collector.shutdown(); // drains the buffer\n */\nexport function fileExporter(options: FileExporterOptions): ExporterContract {\n const writer = new FileTraceWriter(options);\n\n return {\n name: EXPORTER_NAME,\n async export(trace: Trace): Promise<void> {\n await writer.add(trace);\n },\n async flush(): Promise<void> {\n await writer.flush();\n },\n async shutdown(): Promise<void> {\n await writer.flush();\n },\n };\n}\n\n/**\n * Internal buffered writer for {@link fileExporter}. Owns the pending\n * trace buffer and the directory-created guard across the exporter's\n * lifetime; kept unexported so callers only ever see the factory.\n */\nclass FileTraceWriter {\n private readonly path: string;\n private readonly flushEvery: number;\n private readonly pretty: boolean;\n private buffer: TraceRecord[] = [];\n private directoryReady = false;\n\n public constructor(options: FileExporterOptions) {\n this.path = options.path;\n this.flushEvery = Math.max(1, options.flushEvery ?? 1);\n this.pretty = options.pretty ?? false;\n }\n\n /**\n * Buffer one trace and flush when the buffer reaches `flushEvery`.\n */\n public async add(trace: Trace): Promise<void> {\n this.buffer.push({\n type: \"trace\",\n exportedAt: new Date().toISOString(),\n trace,\n });\n\n if (this.buffer.length >= this.flushEvery) {\n await this.flush();\n }\n }\n\n /**\n * Serialize and append every buffered record, then clear the buffer.\n * No-op when nothing is pending so callers can flush defensively.\n */\n public async flush(): Promise<void> {\n if (this.buffer.length === 0) {\n return;\n }\n\n const pending = this.buffer;\n this.buffer = [];\n\n await this.ensureDirectory();\n\n const payload = pending.map((record) => this.serialize(record)).join(\"\");\n\n await appendFile(this.path, payload, \"utf8\");\n }\n\n /**\n * Create the parent directory once, lazily, on the first write. Stores\n * a guard so subsequent flushes skip the syscall.\n */\n private async ensureDirectory(): Promise<void> {\n if (this.directoryReady) {\n return;\n }\n\n await mkdir(dirname(this.path), { recursive: true });\n this.directoryReady = true;\n }\n\n /**\n * Render one record as a newline-terminated JSON string. Pretty mode\n * indents for human reading; compact mode keeps the file valid JSON\n * Lines (exactly one record per physical line).\n */\n private serialize(record: TraceRecord): string {\n const json = this.pretty\n ? JSON.stringify(record, undefined, 2)\n : JSON.stringify(record);\n\n return `${json}\\n`;\n }\n}\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;;;;;;;;;;;;;;;;AAiBtB,SAAgB,aAAa,SAAgD;CAC3E,MAAM,SAAS,IAAI,gBAAgB,OAAO;CAE1C,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAA6B;GACxC,MAAM,OAAO,IAAI,KAAK;EACxB;EACA,MAAM,QAAuB;GAC3B,MAAM,OAAO,MAAM;EACrB;EACA,MAAM,WAA0B;GAC9B,MAAM,OAAO,MAAM;EACrB;CACF;AACF;;;;;;AAOA,IAAM,kBAAN,MAAsB;CACpB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ,SAAwB,CAAC;CACjC,AAAQ,iBAAiB;CAEzB,AAAO,YAAY,SAA8B;EAC/C,KAAK,OAAO,QAAQ;EACpB,KAAK,aAAa,KAAK,IAAI,GAAG,QAAQ,cAAc,CAAC;EACrD,KAAK,SAAS,QAAQ,UAAU;CAClC;;;;CAKA,MAAa,IAAI,OAA6B;EAC5C,KAAK,OAAO,KAAK;GACf,MAAM;GACN,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY;GACnC;EACF,CAAC;EAED,IAAI,KAAK,OAAO,UAAU,KAAK,YAC7B,MAAM,KAAK,MAAM;CAErB;;;;;CAMA,MAAa,QAAuB;EAClC,IAAI,KAAK,OAAO,WAAW,GACzB;EAGF,MAAM,UAAU,KAAK;EACrB,KAAK,SAAS,CAAC;EAEf,MAAM,KAAK,gBAAgB;EAE3B,MAAM,UAAU,QAAQ,KAAK,WAAW,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE;EAEvE,MAAM,WAAW,KAAK,MAAM,SAAS,MAAM;CAC7C;;;;;CAMA,MAAc,kBAAiC;EAC7C,IAAI,KAAK,gBACP;EAGF,MAAM,MAAM,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EACnD,KAAK,iBAAiB;CACxB;;;;;;CAOA,AAAQ,UAAU,QAA6B;EAK7C,OAAO,GAJM,KAAK,SACd,KAAK,UAAU,QAAQ,QAAW,CAAC,IACnC,KAAK,UAAU,MAAM,EAEV;CACjB;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { Trace } from "../../contracts/trace.type.mjs";
2
- //#region ../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.type.d.ts
2
+ //#region ../ai-panoptic/src/exporters/file/file-exporter.type.d.ts
3
3
  /**
4
4
  * One JSON-Lines record the {@link fileExporter} appends per trace. A
5
5
  * thin envelope around {@link Trace} carrying the wire-format `type` and
@@ -1 +1 @@
1
- {"version":3,"file":"file-exporter.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.type.ts"],"mappings":";;;;;AAQA;;;KAAY,WAAA;EAEV,oEAAA,IAAA,WAIA;EAFA,UAAA,UAEY;EAAZ,KAAA,EAAO,KAAK;AAAA;;;;KAMF,mBAAA;EAYV;;;AAMM;;EAZN,IAAA;;;;;;EAMA,UAAA;;;;;;EAMA,MAAA;AAAA"}
1
+ {"version":3,"file":"file-exporter.type.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/file/file-exporter.type.ts"],"mappings":";;;;;AAQA;;;KAAY,WAAA;EAEV,oEAAA,IAAA,WAIA;EAFA,UAAA,UAEY;EAAZ,KAAA,EAAO,KAAK;AAAA;;;;KAMF,mBAAA;EAYV;;;AAMM;;EAZN,IAAA;;;;;;EAMA,UAAA;;;;;;EAMA,MAAA;AAAA"}
@@ -1,7 +1,7 @@
1
1
  import { ExporterContract } from "../../contracts/exporter.contract.mjs";
2
2
  import { LangfuseExporterOptions } from "./langfuse-exporter.type.mjs";
3
3
 
4
- //#region ../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.d.ts
4
+ //#region ../ai-panoptic/src/exporters/langfuse/langfuse-exporter.d.ts
5
5
  /**
6
6
  * {@link ExporterContract} that maps Panoptic traces onto Langfuse
7
7
  * traces and observations. Lazily imports `langfuse` so it stays an
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-exporter.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts"],"mappings":";;;;;;;AAqFA;;;;;;;;AAAoF;;;;;;;;;;;;iBAApE,gBAAA,CAAiB,OAAA,EAAS,uBAAA,GAA0B,gBAAgB"}
1
+ {"version":3,"file":"langfuse-exporter.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts"],"mappings":";;;;;;;AAqFA;;;;;;;;AAAoF;;;;;;;;;;;;iBAApE,gBAAA,CAAiB,OAAA,EAAS,uBAAA,GAA0B,gBAAgB"}
@@ -1,6 +1,6 @@
1
1
  import { GEN_AI_ATTRIBUTES, WARLOCK_ATTRIBUTES, toGenAiAttributes } from "../utils/gen-ai-attributes.mjs";
2
2
 
3
- //#region ../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts
3
+ //#region ../ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts
4
4
  const EXPORTER_NAME = "langfuse";
5
5
  let LangfuseSdk;
6
6
  let isModuleExists = null;
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-exporter.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts"],"sourcesContent":["import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport type { AttributeValue } from \"../utils\";\nimport { GEN_AI_ATTRIBUTES, toGenAiAttributes, WARLOCK_ATTRIBUTES } from \"../utils\";\nimport type {\n LangfuseClientLike,\n LangfuseExporterOptions,\n LangfuseObservationBody,\n LangfuseObservationLevel,\n LangfuseObservationLike,\n LangfuseTraceBody,\n LangfuseTraceLike,\n} from \"./langfuse-exporter.type\";\n\nconst EXPORTER_NAME = \"langfuse\";\n\n// ============================================================\n// Lazily-loaded langfuse SDK (OPTIONAL peer)\n// ============================================================\n\nlet LangfuseSdk: typeof import(\"langfuse\");\nlet isModuleExists: boolean | null = null;\nlet loadingPromise: Promise<void> | undefined;\n\nconst LANGFUSE_INSTALL_INSTRUCTIONS = `\nThe Panoptic Langfuse exporter requires the langfuse package.\nInstall it with:\n\n npm install langfuse\n\nOr with your preferred package manager:\n\n pnpm add langfuse\n yarn add langfuse\n`.trim();\n\n/**\n * Settle the lazy import of `langfuse` once, concurrency-safe. Only\n * needed when the caller did not pass a ready `client`. A bare `catch`\n * flips the flag to `false`; the curated install string surfaces at use\n * time, never a raw module-resolution stack trace.\n */\nfunction loadLangfuse(): Promise<void> {\n if (isModuleExists !== null) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n LangfuseSdk = await import(\"langfuse\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * {@link ExporterContract} that maps Panoptic traces onto Langfuse\n * traces and observations. Lazily imports `langfuse` so it stays an\n * OPTIONAL peer — importing this module never forces the SDK to be\n * installed, and a missing SDK surfaces as a curated \"install this\"\n * error when the exporter first needs to build a client.\n *\n * The root {@link TraceSpan} becomes a Langfuse trace AND its top\n * observation (so the root execution's own tokens are metered, not just\n * its children's); every node is an observation — a `generation` when it\n * metered its OWN tokens (LLM-backed agents, supervisors), otherwise a\n * plain `span` (tools, callbacks, and composite nodes whose tokens came\n * only from children). Timing, status, and version map 1:1; each\n * generation reports its own token usage (rolled-up minus children) so\n * the trace total isn't double-counted; non-usage GenAI attributes ride\n * along as metadata, and captured content (under `captureContent`)\n * surfaces as native `input` / `output`.\n *\n * @example\n * collector.use(langfuseExporter({ publicKey: \"pk-...\", secretKey: \"sk-...\" }));\n * // or reuse an existing client:\n * collector.use(langfuseExporter({ client: myLangfuse }));\n */\nexport function langfuseExporter(options: LangfuseExporterOptions): ExporterContract {\n let client: LangfuseClientLike | undefined = options.client;\n\n if (!client) {\n loadLangfuse();\n }\n\n const resolveClient = async (): Promise<LangfuseClientLike> => {\n if (client) {\n return client;\n }\n\n await loadLangfuse();\n\n if (!isModuleExists) {\n throw new Error(LANGFUSE_INSTALL_INSTRUCTIONS);\n }\n\n client = new LangfuseSdk.Langfuse({\n publicKey: options.publicKey,\n secretKey: options.secretKey,\n baseUrl: options.baseUrl,\n }) as unknown as LangfuseClientLike;\n\n return client;\n };\n\n return {\n name: EXPORTER_NAME,\n async export(trace: Trace): Promise<void> {\n const activeClient = await resolveClient();\n emitTrace(activeClient, trace);\n },\n async flush(): Promise<void> {\n if (!client) {\n return;\n }\n\n await client.flushAsync();\n },\n async shutdown(): Promise<void> {\n if (!client) {\n return;\n }\n\n await client.shutdownAsync();\n },\n };\n}\n\n/**\n * Create the Langfuse trace from the root span, then recurse the\n * children into nested observations.\n */\nfunction emitTrace(client: LangfuseClientLike, trace: Trace): void {\n const root = trace.root;\n\n const traceBody: LangfuseTraceBody = {\n id: root.traceId,\n name: root.name,\n sessionId: trace.sessionId,\n version: root.version,\n timestamp: new Date(root.startedAt),\n metadata: langfuseMetadata(root),\n };\n\n // Surface the root's captured content at the TRACE level too — not only on\n // the root observation — so Langfuse's trace Preview shows the top-level\n // input/output instead of \"this trace received no input/output\". Present\n // only under content capture, and only for roots that carry I/O (an agent\n // or tool; a composite root like a planner has none of its own).\n if (root.input !== undefined) {\n traceBody.input = root.input;\n }\n\n if (root.output !== undefined) {\n traceBody.output = root.output;\n }\n\n const langfuseTrace = client.trace(traceBody);\n\n // Emit the ROOT as an observation too — not just its children — so the\n // root execution's OWN tokens are metered. Langfuse derives the trace\n // total by summing observation usage; leaving the root (often the single\n // top-level agent) as trace-only would drop its own spend. Children nest\n // under the root observation, mirroring the execution tree, and own-usage\n // metering telescopes the per-node sums back to the true trace total.\n emitObservation(langfuseTrace, root);\n}\n\n/**\n * Map one {@link TraceSpan} onto a Langfuse observation under `parent`,\n * then recurse its children. Spans that metered their OWN tokens become\n * `generation`s; everything else (tools, callbacks, composite nodes)\n * becomes a plain `span`.\n */\nfunction emitObservation(\n parent: LangfuseTraceLike | LangfuseObservationLike,\n span: TraceSpan,\n): void {\n const body: LangfuseObservationBody = {\n id: span.spanId,\n name: span.name,\n startTime: new Date(span.startedAt),\n endTime: new Date(span.endedAt),\n level: toLevel(span),\n statusMessage: span.error?.message,\n version: span.version,\n metadata: langfuseMetadata(span),\n };\n\n // Captured content (only present under `captureContent`) maps onto\n // Langfuse's native observation input/output.\n if (span.input !== undefined) {\n body.input = span.input;\n }\n\n if (span.output !== undefined) {\n body.output = span.output;\n }\n\n let observation: LangfuseObservationLike;\n\n // Classify + meter on OWN tokens (this node's rolled-up usage minus its\n // children's), not the subtree rollup. A composite node whose tokens\n // came only from descendants has zero own-usage and becomes a plain\n // span — so its children's tokens aren't counted twice in the trace\n // total Langfuse sums across observations.\n const own = ownUsage(span);\n\n if (own.total > 0) {\n body.usage = {\n input: own.input,\n output: own.output,\n total: own.total,\n unit: \"TOKENS\",\n };\n observation = parent.generation(body);\n } else {\n observation = parent.span(body);\n }\n\n for (const child of span.children) {\n emitObservation(observation, child);\n }\n\n // Explicitly end the observation. The body already carries `endTime`,\n // so this is idempotent — but the SDK only finalizes (and flushes) an\n // observation on `end()`, so without it long-lived clients can leave\n // observations open. Safe against the local `LangfuseObservationLike`\n // shape, which declares `end(body?)`.\n observation.end({ endTime: body.endTime });\n}\n\n/**\n * Own token usage for a span — its rolled-up {@link TraceSpan.usage}\n * minus the rolled-up usage of its direct children. `TraceSpan.usage` is\n * the subtree total (this node plus every descendant), so subtracting the\n * children leaves the tokens THIS node alone metered, clamped at zero\n * defensively.\n *\n * Langfuse sums observation usage into the trace total, so reporting\n * own-usage on each generation (rather than the subtree rollup) is what\n * keeps the trace total correct instead of multiply-counting nested\n * spans. A composite node with no own tokens (e.g. a workflow whose\n * tokens all came from agent children) yields `total: 0` and is emitted\n * as a plain span, not a generation.\n */\nfunction ownUsage(span: TraceSpan): { input: number; output: number; total: number } {\n let childInput = 0;\n let childOutput = 0;\n let childTotal = 0;\n\n for (const child of span.children) {\n childInput += child.usage.input;\n childOutput += child.usage.output;\n childTotal += child.usage.total;\n }\n\n return {\n input: Math.max(0, span.usage.input - childInput),\n output: Math.max(0, span.usage.output - childOutput),\n total: Math.max(0, span.usage.total - childTotal),\n };\n}\n\n/**\n * Token-usage + cost keys that the per-observation `usage` block already\n * carries authoritatively (as OWN usage). Omitting them from `metadata`\n * avoids a confusing contradiction — metadata would otherwise show the\n * rolled-up subtree totals next to an own-usage `usage` block. Cost is\n * omitted for the same reason; Langfuse prices the own tokens itself.\n */\nconst LANGFUSE_METADATA_OMIT = new Set<string>([\n GEN_AI_ATTRIBUTES.usageInputTokens,\n GEN_AI_ATTRIBUTES.usageOutputTokens,\n WARLOCK_ATTRIBUTES.totalTokens,\n WARLOCK_ATTRIBUTES.cachedTokens,\n WARLOCK_ATTRIBUTES.reasoningTokens,\n WARLOCK_ATTRIBUTES.costUsd,\n]);\n\n/**\n * Observation metadata — the GenAI attribute set minus the usage/cost\n * keys that live authoritatively on the `usage` block (see\n * {@link LANGFUSE_METADATA_OMIT}). Keeps model identity, report type,\n * version, session id, and any collector-set attributes.\n */\nfunction langfuseMetadata(span: TraceSpan): Record<string, AttributeValue> {\n const all = toGenAiAttributes(span);\n const metadata: Record<string, AttributeValue> = {};\n\n for (const [key, value] of Object.entries(all)) {\n if (!LANGFUSE_METADATA_OMIT.has(key)) {\n metadata[key] = value;\n }\n }\n\n return metadata;\n}\n\n/**\n * Map the Panoptic span status onto a Langfuse observation level —\n * failed/cancelled spans surface as `ERROR`, everything else `DEFAULT`.\n */\nfunction toLevel(span: TraceSpan): LangfuseObservationLevel {\n if (span.status === \"failed\" || span.status === \"cancelled\") {\n return \"ERROR\";\n }\n\n return \"DEFAULT\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse-exporter.mjs","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts"],"sourcesContent":["import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport type { AttributeValue } from \"../utils\";\nimport { GEN_AI_ATTRIBUTES, toGenAiAttributes, WARLOCK_ATTRIBUTES } from \"../utils\";\nimport type {\n LangfuseClientLike,\n LangfuseExporterOptions,\n LangfuseObservationBody,\n LangfuseObservationLevel,\n LangfuseObservationLike,\n LangfuseTraceBody,\n LangfuseTraceLike,\n} from \"./langfuse-exporter.type\";\n\nconst EXPORTER_NAME = \"langfuse\";\n\n// ============================================================\n// Lazily-loaded langfuse SDK (OPTIONAL peer)\n// ============================================================\n\nlet LangfuseSdk: typeof import(\"langfuse\");\nlet isModuleExists: boolean | null = null;\nlet loadingPromise: Promise<void> | undefined;\n\nconst LANGFUSE_INSTALL_INSTRUCTIONS = `\nThe Panoptic Langfuse exporter requires the langfuse package.\nInstall it with:\n\n npm install langfuse\n\nOr with your preferred package manager:\n\n pnpm add langfuse\n yarn add langfuse\n`.trim();\n\n/**\n * Settle the lazy import of `langfuse` once, concurrency-safe. Only\n * needed when the caller did not pass a ready `client`. A bare `catch`\n * flips the flag to `false`; the curated install string surfaces at use\n * time, never a raw module-resolution stack trace.\n */\nfunction loadLangfuse(): Promise<void> {\n if (isModuleExists !== null) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n LangfuseSdk = await import(\"langfuse\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * {@link ExporterContract} that maps Panoptic traces onto Langfuse\n * traces and observations. Lazily imports `langfuse` so it stays an\n * OPTIONAL peer — importing this module never forces the SDK to be\n * installed, and a missing SDK surfaces as a curated \"install this\"\n * error when the exporter first needs to build a client.\n *\n * The root {@link TraceSpan} becomes a Langfuse trace AND its top\n * observation (so the root execution's own tokens are metered, not just\n * its children's); every node is an observation — a `generation` when it\n * metered its OWN tokens (LLM-backed agents, supervisors), otherwise a\n * plain `span` (tools, callbacks, and composite nodes whose tokens came\n * only from children). Timing, status, and version map 1:1; each\n * generation reports its own token usage (rolled-up minus children) so\n * the trace total isn't double-counted; non-usage GenAI attributes ride\n * along as metadata, and captured content (under `captureContent`)\n * surfaces as native `input` / `output`.\n *\n * @example\n * collector.use(langfuseExporter({ publicKey: \"pk-...\", secretKey: \"sk-...\" }));\n * // or reuse an existing client:\n * collector.use(langfuseExporter({ client: myLangfuse }));\n */\nexport function langfuseExporter(options: LangfuseExporterOptions): ExporterContract {\n let client: LangfuseClientLike | undefined = options.client;\n\n if (!client) {\n loadLangfuse();\n }\n\n const resolveClient = async (): Promise<LangfuseClientLike> => {\n if (client) {\n return client;\n }\n\n await loadLangfuse();\n\n if (!isModuleExists) {\n throw new Error(LANGFUSE_INSTALL_INSTRUCTIONS);\n }\n\n client = new LangfuseSdk.Langfuse({\n publicKey: options.publicKey,\n secretKey: options.secretKey,\n baseUrl: options.baseUrl,\n }) as unknown as LangfuseClientLike;\n\n return client;\n };\n\n return {\n name: EXPORTER_NAME,\n async export(trace: Trace): Promise<void> {\n const activeClient = await resolveClient();\n emitTrace(activeClient, trace);\n },\n async flush(): Promise<void> {\n if (!client) {\n return;\n }\n\n await client.flushAsync();\n },\n async shutdown(): Promise<void> {\n if (!client) {\n return;\n }\n\n await client.shutdownAsync();\n },\n };\n}\n\n/**\n * Create the Langfuse trace from the root span, then recurse the\n * children into nested observations.\n */\nfunction emitTrace(client: LangfuseClientLike, trace: Trace): void {\n const root = trace.root;\n\n const traceBody: LangfuseTraceBody = {\n id: root.traceId,\n name: root.name,\n sessionId: trace.sessionId,\n version: root.version,\n timestamp: new Date(root.startedAt),\n metadata: langfuseMetadata(root),\n };\n\n // Surface the root's captured content at the TRACE level too — not only on\n // the root observation — so Langfuse's trace Preview shows the top-level\n // input/output instead of \"this trace received no input/output\". Present\n // only under content capture, and only for roots that carry I/O (an agent\n // or tool; a composite root like a planner has none of its own).\n if (root.input !== undefined) {\n traceBody.input = root.input;\n }\n\n if (root.output !== undefined) {\n traceBody.output = root.output;\n }\n\n const langfuseTrace = client.trace(traceBody);\n\n // Emit the ROOT as an observation too — not just its children — so the\n // root execution's OWN tokens are metered. Langfuse derives the trace\n // total by summing observation usage; leaving the root (often the single\n // top-level agent) as trace-only would drop its own spend. Children nest\n // under the root observation, mirroring the execution tree, and own-usage\n // metering telescopes the per-node sums back to the true trace total.\n emitObservation(langfuseTrace, root);\n}\n\n/**\n * Map one {@link TraceSpan} onto a Langfuse observation under `parent`,\n * then recurse its children. Spans that metered their OWN tokens become\n * `generation`s; everything else (tools, callbacks, composite nodes)\n * becomes a plain `span`.\n */\nfunction emitObservation(\n parent: LangfuseTraceLike | LangfuseObservationLike,\n span: TraceSpan,\n): void {\n const body: LangfuseObservationBody = {\n id: span.spanId,\n name: span.name,\n startTime: new Date(span.startedAt),\n endTime: new Date(span.endedAt),\n level: toLevel(span),\n statusMessage: span.error?.message,\n version: span.version,\n metadata: langfuseMetadata(span),\n };\n\n // Captured content (only present under `captureContent`) maps onto\n // Langfuse's native observation input/output.\n if (span.input !== undefined) {\n body.input = span.input;\n }\n\n if (span.output !== undefined) {\n body.output = span.output;\n }\n\n let observation: LangfuseObservationLike;\n\n // Classify + meter on OWN tokens (this node's rolled-up usage minus its\n // children's), not the subtree rollup. A composite node whose tokens\n // came only from descendants has zero own-usage and becomes a plain\n // span — so its children's tokens aren't counted twice in the trace\n // total Langfuse sums across observations.\n const own = ownUsage(span);\n\n if (own.total > 0) {\n body.usage = {\n input: own.input,\n output: own.output,\n total: own.total,\n unit: \"TOKENS\",\n };\n observation = parent.generation(body);\n } else {\n observation = parent.span(body);\n }\n\n for (const child of span.children) {\n emitObservation(observation, child);\n }\n\n // Explicitly end the observation. The body already carries `endTime`,\n // so this is idempotent — but the SDK only finalizes (and flushes) an\n // observation on `end()`, so without it long-lived clients can leave\n // observations open. Safe against the local `LangfuseObservationLike`\n // shape, which declares `end(body?)`.\n observation.end({ endTime: body.endTime });\n}\n\n/**\n * Own token usage for a span — its rolled-up {@link TraceSpan.usage}\n * minus the rolled-up usage of its direct children. `TraceSpan.usage` is\n * the subtree total (this node plus every descendant), so subtracting the\n * children leaves the tokens THIS node alone metered, clamped at zero\n * defensively.\n *\n * Langfuse sums observation usage into the trace total, so reporting\n * own-usage on each generation (rather than the subtree rollup) is what\n * keeps the trace total correct instead of multiply-counting nested\n * spans. A composite node with no own tokens (e.g. a workflow whose\n * tokens all came from agent children) yields `total: 0` and is emitted\n * as a plain span, not a generation.\n */\nfunction ownUsage(span: TraceSpan): { input: number; output: number; total: number } {\n let childInput = 0;\n let childOutput = 0;\n let childTotal = 0;\n\n for (const child of span.children) {\n childInput += child.usage.input;\n childOutput += child.usage.output;\n childTotal += child.usage.total;\n }\n\n return {\n input: Math.max(0, span.usage.input - childInput),\n output: Math.max(0, span.usage.output - childOutput),\n total: Math.max(0, span.usage.total - childTotal),\n };\n}\n\n/**\n * Token-usage + cost keys that the per-observation `usage` block already\n * carries authoritatively (as OWN usage). Omitting them from `metadata`\n * avoids a confusing contradiction — metadata would otherwise show the\n * rolled-up subtree totals next to an own-usage `usage` block. Cost is\n * omitted for the same reason; Langfuse prices the own tokens itself.\n */\nconst LANGFUSE_METADATA_OMIT = new Set<string>([\n GEN_AI_ATTRIBUTES.usageInputTokens,\n GEN_AI_ATTRIBUTES.usageOutputTokens,\n WARLOCK_ATTRIBUTES.totalTokens,\n WARLOCK_ATTRIBUTES.cachedTokens,\n WARLOCK_ATTRIBUTES.reasoningTokens,\n WARLOCK_ATTRIBUTES.costUsd,\n]);\n\n/**\n * Observation metadata — the GenAI attribute set minus the usage/cost\n * keys that live authoritatively on the `usage` block (see\n * {@link LANGFUSE_METADATA_OMIT}). Keeps model identity, report type,\n * version, session id, and any collector-set attributes.\n */\nfunction langfuseMetadata(span: TraceSpan): Record<string, AttributeValue> {\n const all = toGenAiAttributes(span);\n const metadata: Record<string, AttributeValue> = {};\n\n for (const [key, value] of Object.entries(all)) {\n if (!LANGFUSE_METADATA_OMIT.has(key)) {\n metadata[key] = value;\n }\n }\n\n return metadata;\n}\n\n/**\n * Map the Panoptic span status onto a Langfuse observation level —\n * failed/cancelled spans surface as `ERROR`, everything else `DEFAULT`.\n */\nfunction toLevel(span: TraceSpan): LangfuseObservationLevel {\n if (span.status === \"failed\" || span.status === \"cancelled\") {\n return \"ERROR\";\n }\n\n return \"DEFAULT\";\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.type.d.ts
1
+ //#region ../ai-panoptic/src/exporters/langfuse/langfuse-exporter.type.d.ts
2
2
  /**
3
3
  * Minimal structural view of the parts of the `langfuse` SDK the
4
4
  * exporter actually calls. Declared locally (rather than importing the
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-exporter.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.type.ts"],"mappings":";;AASA;;;;;;;;KAAY,kBAAA;EACV,KAAA,CAAM,IAAA,EAAM,iBAAA,GAAoB,iBAAA;EAChC,UAAA,IAAc,OAAA;EACd,aAAA,IAAiB,OAAA;AAAA;;;;;KAOP,iBAAA;EACV,IAAA,CAAK,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EACrC,UAAA,CAAW,IAAA,EAAM,uBAAA,GAA0B,uBAAA;AAAA;;;;;KAOjC,uBAAA;EACV,IAAA,CAAK,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EACrC,UAAA,CAAW,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EAC3C,GAAA,CAAI,IAAA,GAAO,0BAAA;AAAA;;KAID,iBAAA;EACV,EAAA;EACA,IAAA;EACA,SAAA;EACA,OAAA;EACA,SAAA,GAAY,IAAA;EACZ,QAAA,GAAW,MAAM,mBAbP;EAeV,KAAA;EAEA,MAAA;AAAA;;KAIU,uBAAA;EACV,EAAA;EACA,IAAA;EACA,SAAA,GAAY,IAAA;EACZ,OAAA,GAAU,IAAA;EACV,KAAA,GAAQ,wBAAA;EACR,aAAA;EACA,OAAA;EACA,KAAA,GAAQ,iBAAA;EACR,QAAA,GAAW,MAAA,mBA5BA;EA8BX,KAAA,YA7BA;EA+BA,MAAA;AAAA;;KAIU,0BAAA;EACV,OAAA,GAAU,IAAA;EACV,KAAA,GAAQ,wBAAwB;EAChC,aAAA;AAAA;;KAIU,iBAAA;EACV,KAAA;EACA,MAAA;EACA,KAAA;EACA,IAAA;AAAA;;KAIU,wBAAA;;;AApCJ;AAIR;;;;;KA0CY,uBAAA;EAlCF;;;;;EAwCR,MAAA,GAAS,kBAAkB,EA7C3B;EA+CA,SAAA,WA9CA;EAgDA,SAAA,WA/CA;EAiDA,OAAA;AAAA"}
1
+ {"version":3,"file":"langfuse-exporter.type.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/langfuse/langfuse-exporter.type.ts"],"mappings":";;AASA;;;;;;;;KAAY,kBAAA;EACV,KAAA,CAAM,IAAA,EAAM,iBAAA,GAAoB,iBAAA;EAChC,UAAA,IAAc,OAAA;EACd,aAAA,IAAiB,OAAA;AAAA;;;;;KAOP,iBAAA;EACV,IAAA,CAAK,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EACrC,UAAA,CAAW,IAAA,EAAM,uBAAA,GAA0B,uBAAA;AAAA;;;;;KAOjC,uBAAA;EACV,IAAA,CAAK,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EACrC,UAAA,CAAW,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EAC3C,GAAA,CAAI,IAAA,GAAO,0BAAA;AAAA;;KAID,iBAAA;EACV,EAAA;EACA,IAAA;EACA,SAAA;EACA,OAAA;EACA,SAAA,GAAY,IAAA;EACZ,QAAA,GAAW,MAAM,mBAbP;EAeV,KAAA;EAEA,MAAA;AAAA;;KAIU,uBAAA;EACV,EAAA;EACA,IAAA;EACA,SAAA,GAAY,IAAA;EACZ,OAAA,GAAU,IAAA;EACV,KAAA,GAAQ,wBAAA;EACR,aAAA;EACA,OAAA;EACA,KAAA,GAAQ,iBAAA;EACR,QAAA,GAAW,MAAA,mBA5BA;EA8BX,KAAA,YA7BA;EA+BA,MAAA;AAAA;;KAIU,0BAAA;EACV,OAAA,GAAU,IAAA;EACV,KAAA,GAAQ,wBAAwB;EAChC,aAAA;AAAA;;KAIU,iBAAA;EACV,KAAA;EACA,MAAA;EACA,KAAA;EACA,IAAA;AAAA;;KAIU,wBAAA;;;AApCJ;AAIR;;;;;KA0CY,uBAAA;EAlCF;;;;;EAwCR,MAAA,GAAS,kBAAkB,EA7C3B;EA+CA,SAAA,WA9CA;EAgDA,SAAA,WA/CA;EAiDA,OAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-panoptic/src/exporters/otel/otel-api.shim.type.d.ts
1
+ //#region ../ai-panoptic/src/exporters/otel/otel-api.shim.type.d.ts
2
2
  /**
3
3
  * Minimal structural shims for the slice of the `@opentelemetry/api`
4
4
  * surface the Panoptic OTel exporter touches.
@@ -1 +1 @@
1
- {"version":3,"file":"otel-api.shim.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/otel/otel-api.shim.type.ts"],"mappings":";;AAqBA;;;;AAAyB;AAGzB;;;;AAAuB;AAMvB;;;;;;;;AAGO;AAAA,KAZK,aAAA;;KAGA,WAAA;;AAeH;AAOT;;KAhBY,aAAA;EACV,IAAA;EACA,OAAA;EACA,KAAA;AAAA;;KAIU,cAAA;EACV,IAAA;EACA,OAAO;AAAA;;;;;KAOG,QAAA;EACV,aAAA,CAAc,UAAA,EAAY,MAAA;EAC1B,SAAA,CAAU,MAAA,EAAQ,cAAA;EAClB,eAAA,CAAgB,SAAA,EAAW,aAAA;EAC3B,GAAA,CAAI,OAAA,GAAU,aAAA;AAAA;AAAa;AAO7B;;;AAP6B,KAOjB,UAAA;EACV,SAAA,CACE,IAAA,UACA,OAAA;IAAW,SAAA,GAAY,aAAA;EAAA,GACvB,OAAA,GAAU,WAAA,GACT,QAAA;AAAA"}
1
+ {"version":3,"file":"otel-api.shim.type.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/otel/otel-api.shim.type.ts"],"mappings":";;AAqBA;;;;AAAyB;AAGzB;;;;AAAuB;AAMvB;;;;;;;;AAGO;AAAA,KAZK,aAAA;;KAGA,WAAA;;AAeH;AAOT;;KAhBY,aAAA;EACV,IAAA;EACA,OAAA;EACA,KAAA;AAAA;;KAIU,cAAA;EACV,IAAA;EACA,OAAO;AAAA;;;;;KAOG,QAAA;EACV,aAAA,CAAc,UAAA,EAAY,MAAA;EAC1B,SAAA,CAAU,MAAA,EAAQ,cAAA;EAClB,eAAA,CAAgB,SAAA,EAAW,aAAA;EAC3B,GAAA,CAAI,OAAA,GAAU,aAAA;AAAA;AAAa;AAO7B;;;AAP6B,KAOjB,UAAA;EACV,SAAA,CACE,IAAA,UACA,OAAA;IAAW,SAAA,GAAY,aAAA;EAAA,GACvB,OAAA,GAAU,WAAA,GACT,QAAA;AAAA"}
@@ -1,7 +1,7 @@
1
1
  import { ExporterContract } from "../../contracts/exporter.contract.mjs";
2
2
  import { OtelExporterOptions } from "./otel-exporter.type.mjs";
3
3
 
4
- //#region ../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.d.ts
4
+ //#region ../ai-panoptic/src/exporters/otel/otel-exporter.d.ts
5
5
  /**
6
6
  * {@link ExporterContract} that maps Panoptic traces onto OpenTelemetry
7
7
  * spans following the GenAI semantic conventions (`gen_ai.*`
@@ -1 +1 @@
1
- {"version":3,"file":"otel-exporter.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.ts"],"mappings":";;;;;;;AAuFA;;;;;;;;AAAiF;;;;;;;;;;iBAAjE,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,gBAAgB"}
1
+ {"version":3,"file":"otel-exporter.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/otel/otel-exporter.ts"],"mappings":";;;;;;;AAuFA;;;;;;;;AAAiF;;;;;;;;;;iBAAjE,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,gBAAgB"}
@@ -1,6 +1,6 @@
1
1
  import { GEN_AI_ATTRIBUTES, toGenAiAttributes } from "../utils/gen-ai-attributes.mjs";
2
2
 
3
- //#region ../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.ts
3
+ //#region ../ai-panoptic/src/exporters/otel/otel-exporter.ts
4
4
  const EXPORTER_NAME = "otel";
5
5
  const DEFAULT_TRACER_NAME = "@warlock.js/ai-panoptic";
6
6
  let OtelApi;
@@ -1 +1 @@
1
- {"version":3,"file":"otel-exporter.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.ts"],"sourcesContent":["import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { toGenAiAttributes, GEN_AI_ATTRIBUTES } from \"../utils\";\nimport type {\n OtelApiModule,\n OtelContext,\n OtelSpan,\n OtelSpanStatusCode,\n OtelTracer,\n} from \"./otel-api.shim.type\";\nimport type { OtelExporterOptions } from \"./otel-exporter.type\";\n\nconst EXPORTER_NAME = \"otel\";\nconst DEFAULT_TRACER_NAME = \"@warlock.js/ai-panoptic\";\n\n// ============================================================\n// Lazily-loaded @opentelemetry/api (OPTIONAL peer)\n// ============================================================\n\nlet OtelApi: OtelApiModule;\nlet isModuleExists: boolean | null = null;\nlet loadingPromise: Promise<void> | undefined;\n\nconst OTEL_INSTALL_INSTRUCTIONS = `\nThe Panoptic OpenTelemetry exporter requires the @opentelemetry/api package.\nInstall it with:\n\n npm install @opentelemetry/api\n\nOr with your preferred package manager:\n\n pnpm add @opentelemetry/api\n yarn add @opentelemetry/api\n`.trim();\n\n/**\n * Settle the lazy import of `@opentelemetry/api` once, concurrency-safe.\n * A bare `catch` flips the flag to `false`; the curated install string\n * surfaces at use time so a missing SDK never throws a raw module\n * resolution error.\n */\nfunction loadOtel(): Promise<void> {\n if (isModuleExists !== null) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n // Indirect specifier so TS does not statically resolve the\n // optional `@opentelemetry/api` peer at compile time (it is\n // intentionally not installed). The result is structurally the\n // `OtelApiModule` shim — the exporter only touches that surface.\n const moduleName = \"@opentelemetry/api\";\n OtelApi = (await import(moduleName)) as OtelApiModule;\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * {@link ExporterContract} that maps Panoptic traces onto OpenTelemetry\n * spans following the GenAI semantic conventions (`gen_ai.*`\n * attributes). Lazily imports `@opentelemetry/api` so it stays an\n * OPTIONAL peer — importing this module never forces the SDK to be\n * installed, and a missing SDK surfaces as a curated \"install this\"\n * error on first `export`, not a boot-time stack trace.\n *\n * The exporter emits onto a `Tracer` you supply (or fetches one from the\n * globally registered provider). It never configures the SDK — wiring a\n * `TracerProvider`, processors, and span exporters is the host app's\n * job, exactly as with any other OTel instrumentation.\n *\n * Each {@link TraceSpan} becomes one OTel span with the source span's\n * start/end times and parent relationship reconstructed, so the emitted\n * tree matches the original execution tree.\n *\n * @example\n * // app already set up @opentelemetry/sdk-trace-base + a provider\n * collector.use(otelExporter({ tracerName: \"my-app\", system: \"openai\" }));\n */\nexport function otelExporter(options: OtelExporterOptions = {}): ExporterContract {\n loadOtel();\n\n return {\n name: EXPORTER_NAME,\n async export(trace: Trace): Promise<void> {\n await loadOtel();\n\n if (!isModuleExists) {\n throw new Error(OTEL_INSTALL_INSTRUCTIONS);\n }\n\n const tracer = resolveTracer(options);\n emitSpan(tracer, trace.root, undefined, options);\n },\n };\n}\n\n/**\n * Resolve the `Tracer` spans are emitted on — the caller-supplied one,\n * or one fetched from the globally registered provider by name.\n */\nfunction resolveTracer(options: OtelExporterOptions): OtelTracer {\n if (options.tracer) {\n return options.tracer;\n }\n\n return OtelApi.trace.getTracer(\n options.tracerName ?? DEFAULT_TRACER_NAME,\n options.tracerVersion,\n );\n}\n\n/**\n * Recreate one {@link TraceSpan} (and its subtree) as OTel spans. The\n * span is started with the source `startedAt`, parented under\n * `parentContext` so the tree is preserved, annotated with GenAI\n * attributes, given the mapped status, and ended at `endedAt`. Children\n * recurse under this span's context.\n */\nfunction emitSpan(\n tracer: OtelTracer,\n span: TraceSpan,\n parentContext: OtelContext | undefined,\n options: OtelExporterOptions,\n): void {\n const startTime = toEpochMillis(span.startedAt);\n const baseContext = parentContext ?? OtelApi.context.active();\n\n const otelSpan = tracer.startSpan(span.name, { startTime }, baseContext);\n\n applyAttributes(otelSpan, span, options);\n applyStatus(otelSpan, span);\n\n const childContext = OtelApi.trace.setSpan(baseContext, otelSpan);\n\n for (const child of span.children) {\n emitSpan(tracer, child, childContext, options);\n }\n\n otelSpan.end(toEpochMillis(span.endedAt));\n}\n\n/**\n * Set the GenAI + Warlock attributes on the OTel span, defaulting\n * `gen_ai.system` from the exporter options when the span carried none.\n */\nfunction applyAttributes(\n otelSpan: OtelSpan,\n span: TraceSpan,\n options: OtelExporterOptions,\n): void {\n const attributes = toGenAiAttributes(span);\n\n if (options.system !== undefined && attributes[GEN_AI_ATTRIBUTES.system] === undefined) {\n attributes[GEN_AI_ATTRIBUTES.system] = options.system;\n }\n\n // Captured content (only present under `captureContent`) maps onto the\n // GenAI prompt/completion attributes, stringified since OTel attribute\n // values must be primitives.\n if (span.input !== undefined) {\n attributes[GEN_AI_ATTRIBUTES.prompt] = stringifyContent(span.input);\n }\n\n if (span.output !== undefined) {\n attributes[GEN_AI_ATTRIBUTES.completion] = stringifyContent(span.output);\n }\n\n otelSpan.setAttributes(attributes);\n}\n\n/**\n * Coerce a captured content value to a string OTel attribute. Strings\n * pass through; structured values are JSON-encoded (falling back to\n * `String()` if they can't be serialized).\n */\nfunction stringifyContent(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Map the Panoptic span status onto the OTel span status, recording the\n * normalized error as an exception event + ERROR status when present.\n */\nfunction applyStatus(otelSpan: OtelSpan, span: TraceSpan): void {\n const codes: OtelSpanStatusCode = OtelApi.SpanStatusCode;\n\n if (span.error) {\n otelSpan.recordException({\n name: span.error.type,\n message: span.error.message,\n stack: span.error.stack,\n });\n }\n\n if (span.status === \"failed\" || span.status === \"cancelled\") {\n otelSpan.setStatus({\n code: codes.ERROR,\n message: span.error?.message,\n });\n return;\n }\n\n // A capped / paused run is neither a failure nor a clean success.\n // Mapping it to OK would let a hit iteration cap read as a healthy\n // run; leave the OTel status UNSET with a descriptive message so the\n // outcome is visible without being miscounted as an error.\n if (span.status === \"max-iterations\" || span.status === \"awaiting-input\") {\n otelSpan.setStatus({\n code: codes.UNSET,\n message:\n span.status === \"max-iterations\"\n ? \"Run hit the iteration cap without an explicit end\"\n : \"Run is awaiting the next input turn\",\n });\n return;\n }\n\n otelSpan.setStatus({ code: codes.OK });\n}\n\n/**\n * Convert an ISO-8601 timestamp to epoch milliseconds — the `TimeInput`\n * form OTel's `startSpan` / `Span.end` accept directly.\n */\nfunction toEpochMillis(isoTimestamp: string): number {\n return new Date(isoTimestamp).getTime();\n}\n"],"mappings":";;;AAWA,MAAM,gBAAgB;AACtB,MAAM,sBAAsB;AAM5B,IAAI;AACJ,IAAI,iBAAiC;AACrC,IAAI;AAEJ,MAAM,4BAA4B;;;;;;;;;;EAUhC,KAAK;;;;;;;AAQP,SAAS,WAA0B;CACjC,IAAI,mBAAmB,MACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GAMF,UAAW,MAAM,OAAO;GACxB,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,EAAC,CAAE;CAEH,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,aAAa,UAA+B,CAAC,GAAqB;CAChF,SAAS;CAET,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAA6B;GACxC,MAAM,SAAS;GAEf,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,yBAAyB;GAI3C,SADe,cAAc,OACf,GAAG,MAAM,MAAM,QAAW,OAAO;EACjD;CACF;AACF;;;;;AAMA,SAAS,cAAc,SAA0C;CAC/D,IAAI,QAAQ,QACV,OAAO,QAAQ;CAGjB,OAAO,QAAQ,MAAM,UACnB,QAAQ,cAAc,qBACtB,QAAQ,aACV;AACF;;;;;;;;AASA,SAAS,SACP,QACA,MACA,eACA,SACM;CACN,MAAM,YAAY,cAAc,KAAK,SAAS;CAC9C,MAAM,cAAc,iBAAiB,QAAQ,QAAQ,OAAO;CAE5D,MAAM,WAAW,OAAO,UAAU,KAAK,MAAM,EAAE,UAAU,GAAG,WAAW;CAEvE,gBAAgB,UAAU,MAAM,OAAO;CACvC,YAAY,UAAU,IAAI;CAE1B,MAAM,eAAe,QAAQ,MAAM,QAAQ,aAAa,QAAQ;CAEhE,KAAK,MAAM,SAAS,KAAK,UACvB,SAAS,QAAQ,OAAO,cAAc,OAAO;CAG/C,SAAS,IAAI,cAAc,KAAK,OAAO,CAAC;AAC1C;;;;;AAMA,SAAS,gBACP,UACA,MACA,SACM;CACN,MAAM,aAAa,kBAAkB,IAAI;CAEzC,IAAI,QAAQ,WAAW,UAAa,WAAW,kBAAkB,YAAY,QAC3E,WAAW,kBAAkB,UAAU,QAAQ;CAMjD,IAAI,KAAK,UAAU,QACjB,WAAW,kBAAkB,UAAU,iBAAiB,KAAK,KAAK;CAGpE,IAAI,KAAK,WAAW,QAClB,WAAW,kBAAkB,cAAc,iBAAiB,KAAK,MAAM;CAGzE,SAAS,cAAc,UAAU;AACnC;;;;;;AAOA,SAAS,iBAAiB,OAAwB;CAChD,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;CAC9C,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF;;;;;AAMA,SAAS,YAAY,UAAoB,MAAuB;CAC9D,MAAM,QAA4B,QAAQ;CAE1C,IAAI,KAAK,OACP,SAAS,gBAAgB;EACvB,MAAM,KAAK,MAAM;EACjB,SAAS,KAAK,MAAM;EACpB,OAAO,KAAK,MAAM;CACpB,CAAC;CAGH,IAAI,KAAK,WAAW,YAAY,KAAK,WAAW,aAAa;EAC3D,SAAS,UAAU;GACjB,MAAM,MAAM;GACZ,SAAS,KAAK,OAAO;EACvB,CAAC;EACD;CACF;CAMA,IAAI,KAAK,WAAW,oBAAoB,KAAK,WAAW,kBAAkB;EACxE,SAAS,UAAU;GACjB,MAAM,MAAM;GACZ,SACE,KAAK,WAAW,mBACZ,sDACA;EACR,CAAC;EACD;CACF;CAEA,SAAS,UAAU,EAAE,MAAM,MAAM,GAAG,CAAC;AACvC;;;;;AAMA,SAAS,cAAc,cAA8B;CACnD,OAAO,IAAI,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC"}
1
+ {"version":3,"file":"otel-exporter.mjs","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/otel/otel-exporter.ts"],"sourcesContent":["import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { toGenAiAttributes, GEN_AI_ATTRIBUTES } from \"../utils\";\nimport type {\n OtelApiModule,\n OtelContext,\n OtelSpan,\n OtelSpanStatusCode,\n OtelTracer,\n} from \"./otel-api.shim.type\";\nimport type { OtelExporterOptions } from \"./otel-exporter.type\";\n\nconst EXPORTER_NAME = \"otel\";\nconst DEFAULT_TRACER_NAME = \"@warlock.js/ai-panoptic\";\n\n// ============================================================\n// Lazily-loaded @opentelemetry/api (OPTIONAL peer)\n// ============================================================\n\nlet OtelApi: OtelApiModule;\nlet isModuleExists: boolean | null = null;\nlet loadingPromise: Promise<void> | undefined;\n\nconst OTEL_INSTALL_INSTRUCTIONS = `\nThe Panoptic OpenTelemetry exporter requires the @opentelemetry/api package.\nInstall it with:\n\n npm install @opentelemetry/api\n\nOr with your preferred package manager:\n\n pnpm add @opentelemetry/api\n yarn add @opentelemetry/api\n`.trim();\n\n/**\n * Settle the lazy import of `@opentelemetry/api` once, concurrency-safe.\n * A bare `catch` flips the flag to `false`; the curated install string\n * surfaces at use time so a missing SDK never throws a raw module\n * resolution error.\n */\nfunction loadOtel(): Promise<void> {\n if (isModuleExists !== null) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n // Indirect specifier so TS does not statically resolve the\n // optional `@opentelemetry/api` peer at compile time (it is\n // intentionally not installed). The result is structurally the\n // `OtelApiModule` shim — the exporter only touches that surface.\n const moduleName = \"@opentelemetry/api\";\n OtelApi = (await import(moduleName)) as OtelApiModule;\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * {@link ExporterContract} that maps Panoptic traces onto OpenTelemetry\n * spans following the GenAI semantic conventions (`gen_ai.*`\n * attributes). Lazily imports `@opentelemetry/api` so it stays an\n * OPTIONAL peer — importing this module never forces the SDK to be\n * installed, and a missing SDK surfaces as a curated \"install this\"\n * error on first `export`, not a boot-time stack trace.\n *\n * The exporter emits onto a `Tracer` you supply (or fetches one from the\n * globally registered provider). It never configures the SDK — wiring a\n * `TracerProvider`, processors, and span exporters is the host app's\n * job, exactly as with any other OTel instrumentation.\n *\n * Each {@link TraceSpan} becomes one OTel span with the source span's\n * start/end times and parent relationship reconstructed, so the emitted\n * tree matches the original execution tree.\n *\n * @example\n * // app already set up @opentelemetry/sdk-trace-base + a provider\n * collector.use(otelExporter({ tracerName: \"my-app\", system: \"openai\" }));\n */\nexport function otelExporter(options: OtelExporterOptions = {}): ExporterContract {\n loadOtel();\n\n return {\n name: EXPORTER_NAME,\n async export(trace: Trace): Promise<void> {\n await loadOtel();\n\n if (!isModuleExists) {\n throw new Error(OTEL_INSTALL_INSTRUCTIONS);\n }\n\n const tracer = resolveTracer(options);\n emitSpan(tracer, trace.root, undefined, options);\n },\n };\n}\n\n/**\n * Resolve the `Tracer` spans are emitted on — the caller-supplied one,\n * or one fetched from the globally registered provider by name.\n */\nfunction resolveTracer(options: OtelExporterOptions): OtelTracer {\n if (options.tracer) {\n return options.tracer;\n }\n\n return OtelApi.trace.getTracer(\n options.tracerName ?? DEFAULT_TRACER_NAME,\n options.tracerVersion,\n );\n}\n\n/**\n * Recreate one {@link TraceSpan} (and its subtree) as OTel spans. The\n * span is started with the source `startedAt`, parented under\n * `parentContext` so the tree is preserved, annotated with GenAI\n * attributes, given the mapped status, and ended at `endedAt`. Children\n * recurse under this span's context.\n */\nfunction emitSpan(\n tracer: OtelTracer,\n span: TraceSpan,\n parentContext: OtelContext | undefined,\n options: OtelExporterOptions,\n): void {\n const startTime = toEpochMillis(span.startedAt);\n const baseContext = parentContext ?? OtelApi.context.active();\n\n const otelSpan = tracer.startSpan(span.name, { startTime }, baseContext);\n\n applyAttributes(otelSpan, span, options);\n applyStatus(otelSpan, span);\n\n const childContext = OtelApi.trace.setSpan(baseContext, otelSpan);\n\n for (const child of span.children) {\n emitSpan(tracer, child, childContext, options);\n }\n\n otelSpan.end(toEpochMillis(span.endedAt));\n}\n\n/**\n * Set the GenAI + Warlock attributes on the OTel span, defaulting\n * `gen_ai.system` from the exporter options when the span carried none.\n */\nfunction applyAttributes(\n otelSpan: OtelSpan,\n span: TraceSpan,\n options: OtelExporterOptions,\n): void {\n const attributes = toGenAiAttributes(span);\n\n if (options.system !== undefined && attributes[GEN_AI_ATTRIBUTES.system] === undefined) {\n attributes[GEN_AI_ATTRIBUTES.system] = options.system;\n }\n\n // Captured content (only present under `captureContent`) maps onto the\n // GenAI prompt/completion attributes, stringified since OTel attribute\n // values must be primitives.\n if (span.input !== undefined) {\n attributes[GEN_AI_ATTRIBUTES.prompt] = stringifyContent(span.input);\n }\n\n if (span.output !== undefined) {\n attributes[GEN_AI_ATTRIBUTES.completion] = stringifyContent(span.output);\n }\n\n otelSpan.setAttributes(attributes);\n}\n\n/**\n * Coerce a captured content value to a string OTel attribute. Strings\n * pass through; structured values are JSON-encoded (falling back to\n * `String()` if they can't be serialized).\n */\nfunction stringifyContent(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Map the Panoptic span status onto the OTel span status, recording the\n * normalized error as an exception event + ERROR status when present.\n */\nfunction applyStatus(otelSpan: OtelSpan, span: TraceSpan): void {\n const codes: OtelSpanStatusCode = OtelApi.SpanStatusCode;\n\n if (span.error) {\n otelSpan.recordException({\n name: span.error.type,\n message: span.error.message,\n stack: span.error.stack,\n });\n }\n\n if (span.status === \"failed\" || span.status === \"cancelled\") {\n otelSpan.setStatus({\n code: codes.ERROR,\n message: span.error?.message,\n });\n return;\n }\n\n // A capped / paused run is neither a failure nor a clean success.\n // Mapping it to OK would let a hit iteration cap read as a healthy\n // run; leave the OTel status UNSET with a descriptive message so the\n // outcome is visible without being miscounted as an error.\n if (span.status === \"max-iterations\" || span.status === \"awaiting-input\") {\n otelSpan.setStatus({\n code: codes.UNSET,\n message:\n span.status === \"max-iterations\"\n ? \"Run hit the iteration cap without an explicit end\"\n : \"Run is awaiting the next input turn\",\n });\n return;\n }\n\n otelSpan.setStatus({ code: codes.OK });\n}\n\n/**\n * Convert an ISO-8601 timestamp to epoch milliseconds — the `TimeInput`\n * form OTel's `startSpan` / `Span.end` accept directly.\n */\nfunction toEpochMillis(isoTimestamp: string): number {\n return new Date(isoTimestamp).getTime();\n}\n"],"mappings":";;;AAWA,MAAM,gBAAgB;AACtB,MAAM,sBAAsB;AAM5B,IAAI;AACJ,IAAI,iBAAiC;AACrC,IAAI;AAEJ,MAAM,4BAA4B;;;;;;;;;;EAUhC,KAAK;;;;;;;AAQP,SAAS,WAA0B;CACjC,IAAI,mBAAmB,MACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GAMF,UAAW,MAAM,OAAO;GACxB,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,EAAC,CAAE;CAEH,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,aAAa,UAA+B,CAAC,GAAqB;CAChF,SAAS;CAET,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAA6B;GACxC,MAAM,SAAS;GAEf,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,yBAAyB;GAI3C,SADe,cAAc,OACf,GAAG,MAAM,MAAM,QAAW,OAAO;EACjD;CACF;AACF;;;;;AAMA,SAAS,cAAc,SAA0C;CAC/D,IAAI,QAAQ,QACV,OAAO,QAAQ;CAGjB,OAAO,QAAQ,MAAM,UACnB,QAAQ,cAAc,qBACtB,QAAQ,aACV;AACF;;;;;;;;AASA,SAAS,SACP,QACA,MACA,eACA,SACM;CACN,MAAM,YAAY,cAAc,KAAK,SAAS;CAC9C,MAAM,cAAc,iBAAiB,QAAQ,QAAQ,OAAO;CAE5D,MAAM,WAAW,OAAO,UAAU,KAAK,MAAM,EAAE,UAAU,GAAG,WAAW;CAEvE,gBAAgB,UAAU,MAAM,OAAO;CACvC,YAAY,UAAU,IAAI;CAE1B,MAAM,eAAe,QAAQ,MAAM,QAAQ,aAAa,QAAQ;CAEhE,KAAK,MAAM,SAAS,KAAK,UACvB,SAAS,QAAQ,OAAO,cAAc,OAAO;CAG/C,SAAS,IAAI,cAAc,KAAK,OAAO,CAAC;AAC1C;;;;;AAMA,SAAS,gBACP,UACA,MACA,SACM;CACN,MAAM,aAAa,kBAAkB,IAAI;CAEzC,IAAI,QAAQ,WAAW,UAAa,WAAW,kBAAkB,YAAY,QAC3E,WAAW,kBAAkB,UAAU,QAAQ;CAMjD,IAAI,KAAK,UAAU,QACjB,WAAW,kBAAkB,UAAU,iBAAiB,KAAK,KAAK;CAGpE,IAAI,KAAK,WAAW,QAClB,WAAW,kBAAkB,cAAc,iBAAiB,KAAK,MAAM;CAGzE,SAAS,cAAc,UAAU;AACnC;;;;;;AAOA,SAAS,iBAAiB,OAAwB;CAChD,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;CAC9C,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF;;;;;AAMA,SAAS,YAAY,UAAoB,MAAuB;CAC9D,MAAM,QAA4B,QAAQ;CAE1C,IAAI,KAAK,OACP,SAAS,gBAAgB;EACvB,MAAM,KAAK,MAAM;EACjB,SAAS,KAAK,MAAM;EACpB,OAAO,KAAK,MAAM;CACpB,CAAC;CAGH,IAAI,KAAK,WAAW,YAAY,KAAK,WAAW,aAAa;EAC3D,SAAS,UAAU;GACjB,MAAM,MAAM;GACZ,SAAS,KAAK,OAAO;EACvB,CAAC;EACD;CACF;CAMA,IAAI,KAAK,WAAW,oBAAoB,KAAK,WAAW,kBAAkB;EACxE,SAAS,UAAU;GACjB,MAAM,MAAM;GACZ,SACE,KAAK,WAAW,mBACZ,sDACA;EACR,CAAC;EACD;CACF;CAEA,SAAS,UAAU,EAAE,MAAM,MAAM,GAAG,CAAC;AACvC;;;;;AAMA,SAAS,cAAc,cAA8B;CACnD,OAAO,IAAI,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC"}
@@ -1,6 +1,6 @@
1
1
  import { OtelTracer } from "./otel-api.shim.type.mjs";
2
2
 
3
- //#region ../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.type.d.ts
3
+ //#region ../ai-panoptic/src/exporters/otel/otel-exporter.type.d.ts
4
4
  /**
5
5
  * Options for {@link otelExporter}.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"otel-exporter.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.type.ts"],"mappings":";;;;;AAWA;;;;;;;KAAY,mBAAA;EA0BV;;AAAM;;;;;;;EAhBN,MAAA,GAAS,UAAU;;;;;EAKnB,UAAA;;;;;EAKA,aAAA;;;;;;EAMA,MAAA;AAAA"}
1
+ {"version":3,"file":"otel-exporter.type.d.mts","names":[],"sources":["../../../../../../../../ai-panoptic/src/exporters/otel/otel-exporter.type.ts"],"mappings":";;;;;AAWA;;;;;;;KAAY,mBAAA;EA0BV;;AAAM;;;;;;;EAhBN,MAAA,GAAS,UAAU;;;;;EAKnB,UAAA;;;;;EAKA,aAAA;;;;;;EAMA,MAAA;AAAA"}