@warlock.js/ai-panoptic 4.3.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 (124) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/LICENSE +21 -0
  3. package/README.md +111 -0
  4. package/cjs/index.cjs +1370 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/esm/collector/collector.d.mts +19 -0
  7. package/esm/collector/collector.d.mts.map +1 -0
  8. package/esm/collector/collector.mjs +89 -0
  9. package/esm/collector/collector.mjs.map +1 -0
  10. package/esm/collector/extract-span-attributes.d.mts +29 -0
  11. package/esm/collector/extract-span-attributes.d.mts.map +1 -0
  12. package/esm/collector/extract-span-attributes.mjs +76 -0
  13. package/esm/collector/extract-span-attributes.mjs.map +1 -0
  14. package/esm/collector/index.d.mts +5 -0
  15. package/esm/collector/normalize-error.d.mts +27 -0
  16. package/esm/collector/normalize-error.d.mts.map +1 -0
  17. package/esm/collector/normalize-error.mjs +49 -0
  18. package/esm/collector/normalize-error.mjs.map +1 -0
  19. package/esm/collector/report-to-span.d.mts +31 -0
  20. package/esm/collector/report-to-span.d.mts.map +1 -0
  21. package/esm/collector/report-to-span.mjs +53 -0
  22. package/esm/collector/report-to-span.mjs.map +1 -0
  23. package/esm/collector/report-to-trace.d.mts +35 -0
  24. package/esm/collector/report-to-trace.d.mts.map +1 -0
  25. package/esm/collector/report-to-trace.mjs +53 -0
  26. package/esm/collector/report-to-trace.mjs.map +1 -0
  27. package/esm/contracts/collector.contract.d.mts +63 -0
  28. package/esm/contracts/collector.contract.d.mts.map +1 -0
  29. package/esm/contracts/exporter.contract.d.mts +76 -0
  30. package/esm/contracts/exporter.contract.d.mts.map +1 -0
  31. package/esm/contracts/index.d.mts +3 -0
  32. package/esm/contracts/trace.type.d.mts +122 -0
  33. package/esm/contracts/trace.type.d.mts.map +1 -0
  34. package/esm/exporters/console/console-exporter.d.mts +25 -0
  35. package/esm/exporters/console/console-exporter.d.mts.map +1 -0
  36. package/esm/exporters/console/console-exporter.mjs +79 -0
  37. package/esm/exporters/console/console-exporter.mjs.map +1 -0
  38. package/esm/exporters/console/console-exporter.type.d.mts +36 -0
  39. package/esm/exporters/console/console-exporter.type.d.mts.map +1 -0
  40. package/esm/exporters/console/format-span-line.d.mts +17 -0
  41. package/esm/exporters/console/format-span-line.d.mts.map +1 -0
  42. package/esm/exporters/console/format-span-line.mjs +41 -0
  43. package/esm/exporters/console/format-span-line.mjs.map +1 -0
  44. package/esm/exporters/console/index.d.mts +3 -0
  45. package/esm/exporters/file/file-exporter.d.mts +23 -0
  46. package/esm/exporters/file/file-exporter.d.mts.map +1 -0
  47. package/esm/exporters/file/file-exporter.mjs +93 -0
  48. package/esm/exporters/file/file-exporter.mjs.map +1 -0
  49. package/esm/exporters/file/file-exporter.type.d.mts +39 -0
  50. package/esm/exporters/file/file-exporter.type.d.mts.map +1 -0
  51. package/esm/exporters/file/index.d.mts +2 -0
  52. package/esm/exporters/index.d.mts +12 -0
  53. package/esm/exporters/index.mjs +12 -0
  54. package/esm/exporters/langfuse/index.d.mts +2 -0
  55. package/esm/exporters/langfuse/index.mjs +3 -0
  56. package/esm/exporters/langfuse/langfuse-exporter.d.mts +27 -0
  57. package/esm/exporters/langfuse/langfuse-exporter.d.mts.map +1 -0
  58. package/esm/exporters/langfuse/langfuse-exporter.mjs +158 -0
  59. package/esm/exporters/langfuse/langfuse-exporter.mjs.map +1 -0
  60. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts +90 -0
  61. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts.map +1 -0
  62. package/esm/exporters/otel/index.d.mts +2 -0
  63. package/esm/exporters/otel/index.mjs +3 -0
  64. package/esm/exporters/otel/otel-exporter.d.mts +29 -0
  65. package/esm/exporters/otel/otel-exporter.d.mts.map +1 -0
  66. package/esm/exporters/otel/otel-exporter.mjs +142 -0
  67. package/esm/exporters/otel/otel-exporter.mjs.map +1 -0
  68. package/esm/exporters/otel/otel-exporter.type.d.mts +39 -0
  69. package/esm/exporters/otel/otel-exporter.type.d.mts.map +1 -0
  70. package/esm/exporters/utils/gen-ai-attributes.d.mts +65 -0
  71. package/esm/exporters/utils/gen-ai-attributes.d.mts.map +1 -0
  72. package/esm/exporters/utils/gen-ai-attributes.mjs +88 -0
  73. package/esm/exporters/utils/gen-ai-attributes.mjs.map +1 -0
  74. package/esm/exporters/utils/index.d.mts +3 -0
  75. package/esm/exporters/utils/total-cost.d.mts +20 -0
  76. package/esm/exporters/utils/total-cost.d.mts.map +1 -0
  77. package/esm/exporters/utils/total-cost.mjs +23 -0
  78. package/esm/exporters/utils/total-cost.mjs.map +1 -0
  79. package/esm/exporters/utils/walk-spans.d.mts +18 -0
  80. package/esm/exporters/utils/walk-spans.d.mts.map +1 -0
  81. package/esm/exporters/utils/walk-spans.mjs +21 -0
  82. package/esm/exporters/utils/walk-spans.mjs.map +1 -0
  83. package/esm/index.d.mts +31 -0
  84. package/esm/index.mjs +22 -0
  85. package/esm/panoptic/index.d.mts +4 -0
  86. package/esm/panoptic/panoptic-middleware.d.mts +38 -0
  87. package/esm/panoptic/panoptic-middleware.d.mts.map +1 -0
  88. package/esm/panoptic/panoptic-middleware.mjs +68 -0
  89. package/esm/panoptic/panoptic-middleware.mjs.map +1 -0
  90. package/esm/panoptic/panoptic-target.type.d.mts +53 -0
  91. package/esm/panoptic/panoptic-target.type.d.mts.map +1 -0
  92. package/esm/panoptic/panoptic.d.mts +37 -0
  93. package/esm/panoptic/panoptic.d.mts.map +1 -0
  94. package/esm/panoptic/panoptic.mjs +134 -0
  95. package/esm/panoptic/panoptic.mjs.map +1 -0
  96. package/esm/panoptic/panoptic.type.d.mts +100 -0
  97. package/esm/panoptic/panoptic.type.d.mts.map +1 -0
  98. package/esm/store/in-memory-trace-store.d.mts +32 -0
  99. package/esm/store/in-memory-trace-store.d.mts.map +1 -0
  100. package/esm/store/in-memory-trace-store.mjs +134 -0
  101. package/esm/store/in-memory-trace-store.mjs.map +1 -0
  102. package/esm/store/index.d.mts +6 -0
  103. package/esm/store/index.mjs +5 -0
  104. package/esm/store/match-trace.d.mts +21 -0
  105. package/esm/store/match-trace.d.mts.map +1 -0
  106. package/esm/store/match-trace.mjs +44 -0
  107. package/esm/store/match-trace.mjs.map +1 -0
  108. package/esm/store/sum-usage.d.mts +34 -0
  109. package/esm/store/sum-usage.d.mts.map +1 -0
  110. package/esm/store/sum-usage.mjs +65 -0
  111. package/esm/store/sum-usage.mjs.map +1 -0
  112. package/esm/store/trace-aggregate.type.d.mts +51 -0
  113. package/esm/store/trace-aggregate.type.d.mts.map +1 -0
  114. package/esm/store/trace-query.type.d.mts +45 -0
  115. package/esm/store/trace-query.type.d.mts.map +1 -0
  116. package/esm/store/trace-store.contract.d.mts +55 -0
  117. package/esm/store/trace-store.contract.d.mts.map +1 -0
  118. package/llms-full.txt +450 -0
  119. package/llms.txt +11 -0
  120. package/package.json +40 -0
  121. package/skills/README.md +17 -0
  122. package/skills/export-traces/SKILL.md +152 -0
  123. package/skills/observe-with-panoptic/SKILL.md +128 -0
  124. package/skills/query-traces/SKILL.md +152 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["EXPORTER_NAME","EXPORTER_NAME","EXPORTER_NAME","isModuleExists","loadingPromise"],"sources":["../../../../../../@warlock.js/ai-panoptic/src/exporters/utils/walk-spans.ts","../../../../../../@warlock.js/ai-panoptic/src/collector/normalize-error.ts","../../../../../../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts","../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-span.ts","../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-trace.ts","../../../../../../@warlock.js/ai-panoptic/src/collector/collector.ts","../../../../../../@warlock.js/ai-panoptic/src/store/match-trace.ts","../../../../../../@warlock.js/ai-panoptic/src/store/sum-usage.ts","../../../../../../@warlock.js/ai-panoptic/src/store/in-memory-trace-store.ts","../../../../../../@warlock.js/ai-panoptic/src/exporters/utils/total-cost.ts","../../../../../../@warlock.js/ai-panoptic/src/exporters/utils/gen-ai-attributes.ts","../../../../../../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.ts","../../../../../../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.ts","../../../../../../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts","../../../../../../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts","../../../../../../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.ts","../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic-middleware.ts","../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic.ts"],"sourcesContent":["import type { TraceSpan } from \"../../contracts\";\n\n/**\n * Depth-first pre-order traversal of a {@link TraceSpan} tree, yielding\n * the root first and then each descendant in `children` (invocation)\n * order. Exporters that emit a flat span stream — OpenTelemetry, the\n * console table — walk the tree once with this instead of re-writing the\n * recursion in every exporter.\n *\n * @example\n * for (const span of walkSpans(trace.root)) {\n * emit(span);\n * }\n */\nexport function* walkSpans(root: TraceSpan): Generator<TraceSpan> {\n yield root;\n\n for (const child of root.children) {\n yield* walkSpans(child);\n }\n}\n","import type { TraceSpanError } from \"../contracts/trace.type\";\n\n/**\n * Project a captured execution error onto the structural\n * {@link TraceSpanError} shape used on a failed / cancelled span.\n *\n * Source errors are typically `AIError` instances (every error surfaced\n * by `@warlock.js/ai` is one), but the collector never depends on the\n * concrete class — it reads only the structural surface (`name` / `code`\n * / `message` / `stack`) so a plain `Error`, an `AIError`, or any\n * thrown value all normalize identically. The result is a JSON-safe\n * plain object so it survives serialization to a backend collector\n * unchanged.\n *\n * The error `type` prefers the stable `code` (e.g. `\"RATE_LIMIT\"`) over\n * the class `name`, falling back to `name` and finally to the generic\n * `\"Error\"` so the field is always populated.\n *\n * @example\n * const spanError = normalizeError(report.error);\n * // { type: \"RATE_LIMIT\", message: \"429 Too Many Requests\", stack: \"...\" }\n */\nexport function normalizeError(error: unknown): TraceSpanError | undefined {\n if (error === undefined || error === null) {\n return undefined;\n }\n\n if (typeof error !== \"object\") {\n return {\n type: \"Error\",\n message: String(error),\n };\n }\n\n const candidate = error as {\n code?: unknown;\n name?: unknown;\n message?: unknown;\n stack?: unknown;\n };\n\n const type = pickString(candidate.code) ?? pickString(candidate.name) ?? \"Error\";\n const message = pickString(candidate.message) ?? \"\";\n const stack = pickString(candidate.stack);\n\n const normalized: TraceSpanError = {\n type,\n message,\n };\n\n if (stack !== undefined) {\n normalized.stack = stack;\n }\n\n return normalized;\n}\n\n/**\n * Return the value when it is a non-empty string, otherwise `undefined`.\n * Keeps `normalizeError` from promoting empty / non-string fields.\n */\nfunction pickString(value: unknown): string | undefined {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n\n return undefined;\n}\n","import type { BaseReport } from \"@warlock.js/ai\";\n\n/**\n * Per-primitive `BaseReport` extension fields the collector surfaces as\n * span attributes. Declared as a structural superset (every field\n * optional) so a single reader can pull whatever the concrete report\n * carried without narrowing on `type` first — a `BaseReport` widened to\n * this shape exposes `undefined` for the fields its primitive doesn't\n * set, and the extractor simply skips those.\n *\n * Mirrors the public per-primitive report types in `@warlock.js/ai`\n * (`AgentReport`, `WorkflowReport`, `SupervisorReport`,\n * `OrchestratorReport`, `ToolCall`) — kept structural rather than a\n * union import so adding a new optional report field upstream is a\n * non-breaking read here.\n */\ntype ReportExtensions = {\n model?: { name?: string; provider?: string };\n trips?: unknown[];\n workflowName?: string;\n supervisorName?: string;\n signature?: string;\n terminatedBy?: string;\n iterations?: number;\n steps?: Record<string, unknown>;\n turns?: unknown[];\n turnIndex?: number;\n tripIndex?: number;\n recoveredFrom?: string;\n};\n\n/**\n * Build the free-form `TraceSpan.attributes` bag for one report node.\n *\n * The collector keeps the first-class span fields (identity, timing,\n * status, usage, error) on the span itself and routes everything\n * primitive-specific here — trip/step/iteration counts, the model\n * identity an agent ran against, the tool's originating trip index, a\n * supervisor's termination reason. Exporters forward this verbatim as\n * backend span attributes (OTel attributes, Langfuse metadata).\n *\n * Only populated keys are emitted; the function returns `undefined`\n * when the node carried no extra detail, so the optional\n * `TraceSpan.attributes` field stays absent rather than holding an\n * empty object (matches the contract's \"absent when empty\" note).\n *\n * Retry count is surfaced from the shared `BaseReport.attempts` for\n * every primitive so cost dashboards see the real call count.\n *\n * @example\n * const attributes = extractSpanAttributes(agentReport);\n * // { \"agent.trips\": 3, \"agent.model.name\": \"gpt-4o\", \"agent.model.provider\": \"openai\" }\n */\nexport function extractSpanAttributes(report: BaseReport): Record<string, unknown> | undefined {\n const extensions = report as BaseReport & ReportExtensions;\n const attributes: Record<string, unknown> = {};\n\n if (report.attempts !== undefined && report.attempts.length > 0) {\n attributes[\"retries\"] = report.attempts.length;\n }\n\n switch (report.type) {\n case \"agent\": {\n addAgentAttributes(attributes, extensions);\n break;\n }\n\n case \"workflow\": {\n addWorkflowAttributes(attributes, extensions);\n break;\n }\n\n case \"supervisor\": {\n addSupervisorAttributes(attributes, extensions);\n break;\n }\n\n case \"orchestrator\": {\n addOrchestratorAttributes(attributes, extensions);\n break;\n }\n\n case \"tool\": {\n addToolAttributes(attributes, extensions);\n break;\n }\n\n default: {\n break;\n }\n }\n\n if (Object.keys(attributes).length === 0) {\n return undefined;\n }\n\n return attributes;\n}\n\nfunction addAgentAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (Array.isArray(extensions.trips)) {\n attributes[\"agent.trips\"] = extensions.trips.length;\n }\n\n if (extensions.model?.name !== undefined) {\n attributes[\"agent.model.name\"] = extensions.model.name;\n }\n\n if (extensions.model?.provider !== undefined) {\n attributes[\"agent.model.provider\"] = extensions.model.provider;\n }\n}\n\nfunction addWorkflowAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.workflowName !== undefined) {\n attributes[\"workflow.name\"] = extensions.workflowName;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"workflow.signature\"] = extensions.signature;\n }\n\n if (extensions.steps !== undefined) {\n attributes[\"workflow.steps\"] = Object.keys(extensions.steps).length;\n }\n}\n\nfunction addSupervisorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.supervisorName !== undefined) {\n attributes[\"supervisor.name\"] = extensions.supervisorName;\n }\n\n if (extensions.terminatedBy !== undefined) {\n attributes[\"supervisor.terminatedBy\"] = extensions.terminatedBy;\n }\n\n if (extensions.iterations !== undefined) {\n attributes[\"supervisor.iterations\"] = extensions.iterations;\n }\n}\n\nfunction addOrchestratorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.turnIndex !== undefined) {\n attributes[\"orchestrator.turnIndex\"] = extensions.turnIndex;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"orchestrator.signature\"] = extensions.signature;\n }\n\n if (Array.isArray(extensions.turns)) {\n attributes[\"orchestrator.turns\"] = extensions.turns.length;\n }\n}\n\nfunction addToolAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.tripIndex !== undefined) {\n attributes[\"tool.tripIndex\"] = extensions.tripIndex;\n }\n\n if (extensions.recoveredFrom !== undefined) {\n attributes[\"tool.recoveredFrom\"] = extensions.recoveredFrom;\n }\n}\n","import type { BaseReport } from \"@warlock.js/ai\";\nimport type { TraceSpan } from \"../contracts/trace.type\";\nimport { extractSpanAttributes } from \"./extract-span-attributes\";\nimport { normalizeError } from \"./normalize-error\";\n\n/**\n * Project one {@link BaseReport} node — and its entire subtree — into a\n * {@link TraceSpan}. Pure and recursive: identity, timing, status, and\n * the rolled-up `usage` map across 1:1 from the report; lineage maps\n * `runId → spanId`, `parentRunId → parentSpanId`, `rootRunId → traceId`;\n * children recurse in invocation order so the span tree mirrors the\n * report tree exactly.\n *\n * The error is normalized to the JSON-safe {@link\n * import(\"../contracts/trace.type\").TraceSpanError} shape only when the\n * node carried one (failed / cancelled). Primitive-specific detail that\n * has no first-class span field (trip / step / iteration counts, model\n * identity, tool trip index) is routed into the optional `attributes`\n * bag via {@link extractSpanAttributes}.\n *\n * No external lookup is needed — a `BaseReport` already carries\n * everything a span requires, so a collector can flatten a tree without\n * consulting any other source.\n *\n * @example\n * const root = reportToSpan(result.report);\n * console.log(root.spanId, root.traceId, root.children.length);\n */\nexport function reportToSpan(report: BaseReport): TraceSpan {\n const span: TraceSpan = {\n spanId: report.runId,\n traceId: report.rootRunId,\n name: report.name,\n type: report.type,\n status: report.status,\n startedAt: report.startedAt,\n endedAt: report.endedAt,\n duration: report.duration,\n usage: report.usage,\n children: report.children.map(reportToSpan),\n };\n\n if (report.parentRunId !== undefined) {\n span.parentSpanId = report.parentRunId;\n }\n\n if (report.sessionId !== undefined) {\n span.sessionId = report.sessionId;\n }\n\n if (report.version !== undefined) {\n span.version = report.version;\n }\n\n const error = normalizeError((report as { error?: unknown }).error);\n if (error !== undefined) {\n span.error = error;\n }\n\n const attributes = extractSpanAttributes(report);\n if (attributes !== undefined) {\n span.attributes = attributes;\n }\n\n return span;\n}\n","import type { BaseReport } from \"@warlock.js/ai\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { normalizeError } from \"./normalize-error\";\nimport { reportToSpan } from \"./report-to-span\";\n\n/**\n * Project an outermost {@link BaseReport} (one whole `.execute()` /\n * `.invoke()` run) into a {@link Trace} — the root {@link\n * import(\"../contracts/trace.type\").TraceSpan} plus the trace-wide\n * rollups exporters need without re-walking the tree.\n *\n * The trace-level identity and rollups all read off the root span the\n * projection already built (`traceId`, `usage`, timing), so the trace\n * envelope never disagrees with its own root. `reportSchemaVersion` is\n * mirrored from the root report when present (it is stamped only on\n * root nodes upstream) so exporters can branch on the source shape.\n *\n * Pure — the same input always yields the same trace. The collector\n * exposes this as `toTrace` so callers can inspect the normalized shape\n * without dispatching to exporters.\n *\n * The optional `rootError` threads the failing run's envelope error\n * (`BaseResult.error`) onto the root span. The error lives on the result\n * envelope, never on the `BaseReport` tree, so without this a failed root\n * would export `status` with no error type/message. Applied only when the\n * root span carries none of its own; the subtree projection stays pure\n * (each child surfaces its own report-level error, if any).\n *\n * @example\n * const trace = reportToTrace(result.report, result.error);\n * console.log(trace.traceId, trace.usage.total, trace.duration);\n */\nexport function reportToTrace(report: BaseReport, rootError?: unknown): Trace {\n const root = reportToSpan(report);\n\n if (root.error === undefined) {\n const error = normalizeError(rootError);\n\n if (error !== undefined) {\n root.error = error;\n }\n }\n\n const trace: Trace = {\n traceId: root.traceId,\n root,\n startedAt: root.startedAt,\n endedAt: root.endedAt,\n duration: root.duration,\n usage: root.usage,\n };\n\n if (root.sessionId !== undefined) {\n trace.sessionId = root.sessionId;\n }\n\n if (report.reportSchemaVersion !== undefined) {\n trace.reportSchemaVersion = report.reportSchemaVersion;\n }\n\n return trace;\n}\n","import type { BaseReport } from \"@warlock.js/ai\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { walkSpans } from \"../exporters/utils/walk-spans\";\nimport { reportToTrace } from \"./report-to-trace\";\n\n/**\n * Drive the source end of the Panoptic pipeline: ingest core\n * `@warlock.js/ai` {@link BaseReport} trees, project them into {@link\n * Trace}s, and fan each trace out to every registered exporter.\n *\n * Owns exporter registration (deduped by `ExporterContract.name`), the\n * report→trace projection, and graceful shutdown so exporters drain\n * before exit. Instantiated fresh per collector via {@link\n * createCollector}; callers never see `new`.\n *\n * **Failure isolation.** An exporter that throws never propagates back\n * into the originating run — `collect`, `flush`, and `shutdown` settle\n * every exporter independently (mirrors how the core event hooks\n * swallow consumer errors). One broken exporter can neither crash the\n * agent loop nor stop sibling exporters from receiving the trace.\n */\nclass Collector implements CollectorContract {\n /**\n * Registered exporters in insertion order. A `Map` keyed by\n * `ExporterContract.name` gives O(1) dedupe on `use` while preserving\n * registration order for deterministic fan-out.\n */\n private readonly exporters = new Map<string, ExporterContract>();\n\n public use(exporter: ExporterContract): this {\n if (!this.exporters.has(exporter.name)) {\n this.exporters.set(exporter.name, exporter);\n }\n\n return this;\n }\n\n public toTrace(report: BaseReport, rootError?: unknown): Trace {\n return reportToTrace(report, rootError);\n }\n\n public async collect(report: BaseReport, rootError?: unknown): Promise<void> {\n const trace = this.toTrace(report, rootError);\n\n await this.dispatch(trace);\n }\n\n public async flush(): Promise<void> {\n await this.settleAll((exporter) => exporter.flush?.());\n }\n\n public async shutdown(): Promise<void> {\n await this.flush();\n\n await this.settleAll((exporter) => exporter.shutdown?.());\n\n this.exporters.clear();\n }\n\n /**\n * Fan one trace out to every exporter and, when an exporter advertises\n * the per-span hook, deliver every span in the finalized tree to it as\n * well. `exportSpan` is a post-completion per-span hook (not a live /\n * streaming feed — the trace is already finalized): we walk the tree in\n * pre-order with {@link walkSpans} so the exporter sees the root and\n * every descendant exactly once. Every invocation is isolated so a\n * throwing exporter can't abort the dispatch to its siblings or escape\n * into the originating run.\n */\n private async dispatch(trace: Trace): Promise<void> {\n await this.settleAll(async (exporter) => {\n await exporter.export(trace);\n\n if (exporter.exportSpan !== undefined) {\n for (const span of walkSpans(trace.root)) {\n await exporter.exportSpan(span);\n }\n }\n });\n }\n\n /**\n * Run `task` against every registered exporter and wait for all of\n * them to settle, swallowing individual rejections. `Promise.allSettled`\n * guarantees one failure neither rejects the batch nor blocks the\n * others — the contract's failure-isolation requirement.\n */\n private async settleAll(\n task: (exporter: ExporterContract) => void | Promise<void>,\n ): Promise<void> {\n const runs = [...this.exporters.values()].map((exporter) => Promise.resolve().then(() => task(exporter)));\n\n await Promise.allSettled(runs);\n }\n}\n\n/**\n * Create a Panoptic collector — the single integration point an app\n * wires into its agents/workflows (typically via the `onComplete`\n * report hook). Register exporters with `use`, then feed finalized root\n * reports to `collect`.\n *\n * @example\n * const collector = createCollector().use(otelExporter).use(langfuseExporter);\n * agent.on(\"onComplete\", ({ result }) => collector.collect(result.report));\n * // on shutdown:\n * await collector.shutdown();\n */\nexport function createCollector(): CollectorContract {\n return new Collector();\n}\n","import type { ReportStatus } from \"@warlock.js/ai\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport type { TraceQuery } from \"./trace-query.type\";\n\n/**\n * Test whether one trace satisfies a {@link TraceQuery}. Every declared\n * filter field must match (logical AND); absent fields are ignored, so\n * an empty / undefined filter matches every trace.\n *\n * Time bounds compare against the trace's root `startedAt`, parsed to\n * an epoch once per call, inclusive on both ends. Status accepts a\n * single value or an array (membership test). Identity fields are exact\n * string equality.\n *\n * Pure — used by the store's `query` and `aggregate` so both share one\n * matching definition.\n */\nexport function matchTrace(trace: Trace, filter?: TraceQuery): boolean {\n if (!filter) {\n return true;\n }\n\n if (filter.traceId !== undefined && trace.traceId !== filter.traceId) {\n return false;\n }\n\n if (filter.sessionId !== undefined && trace.sessionId !== filter.sessionId) {\n return false;\n }\n\n if (filter.status !== undefined && !statusMatches(trace.root.status, filter.status)) {\n return false;\n }\n\n const startedAt = Date.parse(trace.startedAt);\n\n if (filter.startedAfter !== undefined && startedAt < toEpoch(filter.startedAfter)) {\n return false;\n }\n\n if (filter.startedBefore !== undefined && startedAt > toEpoch(filter.startedBefore)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Membership test for the status filter — true when `status` equals the\n * single wanted value, or is one of the wanted array.\n */\nfunction statusMatches(status: ReportStatus, wanted: ReportStatus | ReportStatus[]): boolean {\n if (Array.isArray(wanted)) {\n return wanted.includes(status);\n }\n\n return status === wanted;\n}\n\n/**\n * Normalize a time bound (ISO string or `Date`) to epoch milliseconds\n * for comparison against a parsed `startedAt`.\n */\nfunction toEpoch(bound: string | Date): number {\n if (bound instanceof Date) {\n return bound.getTime();\n }\n\n return Date.parse(bound);\n}\n","import { accumulateCost, type Usage } from \"@warlock.js/ai\";\n\n/**\n * Fold a child {@link Usage} into a running accumulator. Token channels\n * (`input` / `output` / `total`) always sum; the optional cache /\n * reasoning channels (`cachedTokens` / `cacheWriteTokens` /\n * `reasoningTokens`) sum only when at least one side reported them, so\n * a provider that never meters a channel doesn't fabricate a `0` for\n * it. The `cost` breakdown is merged with the core framework's\n * {@link accumulateCost}, keeping cost-rollup semantics identical to a\n * native report tree — an unpriced contributor never erases a priced\n * one.\n *\n * Pure: returns a fresh `Usage`, never mutates either argument. Seed an\n * aggregation with {@link emptyUsage}.\n *\n * @example\n * let total = emptyUsage();\n * for (const trace of traces) {\n * total = sumUsage(total, trace.usage);\n * }\n */\nexport function sumUsage(accumulator: Usage, next: Usage): Usage {\n const merged: Usage = {\n input: accumulator.input + next.input,\n output: accumulator.output + next.output,\n total: accumulator.total + next.total,\n };\n\n const cachedTokens = sumOptional(accumulator.cachedTokens, next.cachedTokens);\n if (cachedTokens !== undefined) {\n merged.cachedTokens = cachedTokens;\n }\n\n const cacheWriteTokens = sumOptional(accumulator.cacheWriteTokens, next.cacheWriteTokens);\n if (cacheWriteTokens !== undefined) {\n merged.cacheWriteTokens = cacheWriteTokens;\n }\n\n const reasoningTokens = sumOptional(accumulator.reasoningTokens, next.reasoningTokens);\n if (reasoningTokens !== undefined) {\n merged.reasoningTokens = reasoningTokens;\n }\n\n const cost = accumulateCost(accumulator.cost, next.cost);\n if (cost !== undefined) {\n merged.cost = cost;\n }\n\n return merged;\n}\n\n/**\n * A zero-valued {@link Usage} to seed an aggregation. Only the required\n * token channels are set; optional channels stay absent until a\n * contributor reports them, preserving the \"never reported\" vs\n * \"reported as 0\" distinction.\n */\nexport function emptyUsage(): Usage {\n return {\n input: 0,\n output: 0,\n total: 0,\n };\n}\n\n/**\n * Add two optional token counts, treating either side's `undefined` as\n * zero — but return `undefined` when both are absent, so an unreported\n * channel stays unreported rather than collapsing to `0`.\n */\nfunction sumOptional(accumulator: number | undefined, next: number | undefined): number | undefined {\n if (accumulator === undefined && next === undefined) {\n return undefined;\n }\n\n return (accumulator ?? 0) + (next ?? 0);\n}\n","import type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { matchTrace } from \"./match-trace\";\nimport { emptyUsage, sumUsage } from \"./sum-usage\";\nimport type { TraceAggregate } from \"./trace-aggregate.type\";\nimport type { TraceQuery } from \"./trace-query.type\";\nimport type { TraceStoreContract } from \"./trace-store.contract\";\n\n/**\n * Options for {@link createInMemoryTraceStore}.\n */\nexport type InMemoryTraceStoreOptions = {\n /**\n * Maximum number of traces to retain. When set and exceeded, the\n * oldest-ingested trace is evicted (insertion-order FIFO) so the\n * store stays bounded for long-lived processes. Absent / `0` =\n * unbounded (keep everything until `clear`).\n */\n capacity?: number;\n};\n\n/**\n * In-memory {@link TraceStoreContract} that doubles as an\n * {@link ExporterContract} — register it on a collector\n * (`collector.use(store)`) and it fills as traces complete, then query\n * or aggregate it after the fact.\n *\n * Backed by an insertion-ordered `Map` keyed by `traceId`, giving O(1)\n * `get` / `add` / overwrite and O(n) scans for `query` / `aggregate`\n * (the price of an in-memory store with no secondary indexes — fine for\n * the dev/test and modest-volume runtime use this targets). When a\n * `capacity` is configured, ingesting past the cap evicts the oldest\n * trace.\n *\n * Instantiated fresh per store via {@link createInMemoryTraceStore};\n * callers never see `new`.\n */\nclass InMemoryTraceStore implements TraceStoreContract, ExporterContract {\n /** Stable exporter id so a collector can dedupe / log this sink. */\n public readonly name = \"in-memory-trace-store\";\n\n /**\n * Retained traces keyed by `traceId`. A `Map` preserves insertion\n * order, which is what FIFO eviction and newest-first `query` ordering\n * both rely on.\n */\n private readonly traces = new Map<string, Trace>();\n\n private readonly capacity: number;\n\n public constructor(options?: InMemoryTraceStoreOptions) {\n this.capacity = options?.capacity ?? 0;\n }\n\n public get size(): number {\n return this.traces.size;\n }\n\n public add(trace: Trace): void {\n // Re-insert so an overwrite also refreshes insertion position —\n // keeps \"oldest\" honest for FIFO eviction.\n this.traces.delete(trace.traceId);\n this.traces.set(trace.traceId, trace);\n\n this.evictOverflow();\n }\n\n /**\n * `ExporterContract.export` — a collector dispatches a completed\n * trace here, which is exactly an `add`. Lets the store be wired into\n * a collector as a sink without an adapter.\n */\n public export(trace: Trace): void {\n this.add(trace);\n }\n\n public get(traceId: string): Trace | undefined {\n return this.traces.get(traceId);\n }\n\n public query(filter?: TraceQuery): Trace[] {\n const matched: Trace[] = [];\n\n for (const trace of this.traces.values()) {\n if (matchTrace(trace, filter)) {\n matched.push(trace);\n }\n }\n\n return this.sortNewestFirst(matched);\n }\n\n public aggregate(filter?: TraceQuery): TraceAggregate {\n const aggregate: TraceAggregate = {\n traces: 0,\n completed: 0,\n failed: 0,\n cancelled: 0,\n usage: emptyUsage(),\n totalDuration: 0,\n };\n\n for (const trace of this.traces.values()) {\n if (!matchTrace(trace, filter)) {\n continue;\n }\n\n aggregate.traces += 1;\n aggregate.totalDuration += trace.duration;\n aggregate.usage = sumUsage(aggregate.usage, trace.usage);\n\n this.countStatus(aggregate, trace);\n }\n\n if (aggregate.usage.cost !== undefined) {\n aggregate.cost = aggregate.usage.cost;\n }\n\n return aggregate;\n }\n\n public clear(): void {\n this.traces.clear();\n }\n\n /**\n * Increment the matching terminal-status counter for one trace.\n * Non-terminal statuses (`awaiting-input`, `max-iterations`) are\n * counted in `traces` but tracked by none of the three headline\n * counters — intentional, those three answer the common\n * \"succeeded / errored / aborted\" question.\n */\n private countStatus(aggregate: TraceAggregate, trace: Trace): void {\n switch (trace.root.status) {\n case \"completed\": {\n aggregate.completed += 1;\n break;\n }\n\n case \"failed\": {\n aggregate.failed += 1;\n break;\n }\n\n case \"cancelled\": {\n aggregate.cancelled += 1;\n break;\n }\n\n default: {\n break;\n }\n }\n }\n\n /**\n * Sort matched traces newest-started first. A copy is sorted so the\n * underlying insertion order (which eviction depends on) is never\n * disturbed.\n */\n private sortNewestFirst(traces: Trace[]): Trace[] {\n return traces.sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));\n }\n\n /**\n * Evict oldest-inserted traces until the store is within `capacity`.\n * No-op when unbounded. The `Map` iterator yields keys in insertion\n * order, so the first key is always the oldest.\n */\n private evictOverflow(): void {\n if (this.capacity <= 0) {\n return;\n }\n\n while (this.traces.size > this.capacity) {\n const oldest = this.traces.keys().next().value;\n\n if (oldest === undefined) {\n return;\n }\n\n this.traces.delete(oldest);\n }\n }\n}\n\n/**\n * Create an in-memory trace store. Optionally bound it with `capacity`\n * for long-lived processes; leave it unset for dev/test where you want\n * every trace retained.\n *\n * @example\n * const store = createInMemoryTraceStore({ capacity: 1000 });\n * collector.use(store);\n * // later:\n * const recentFailures = store.query({ status: \"failed\" });\n * const sessionSpend = store.aggregate({ sessionId });\n */\nexport function createInMemoryTraceStore(options?: InMemoryTraceStoreOptions): TraceStoreContract & ExporterContract {\n return new InMemoryTraceStore(options);\n}\n","import type { Usage } from \"@warlock.js/ai\";\n\n/**\n * Collapse a {@link Usage.cost} breakdown into a single USD scalar by\n * summing every populated field. Mirrors the formula documented on\n * `Usage.cost` (input + output + cachedInput + cachedOutput), and also\n * folds in `reasoning` for forward-safety when a provider prices\n * reasoning tokens as a separate channel. Returns `undefined` when no\n * pricing was attached, so exporters can omit the cost attribute\n * entirely rather than reporting a misleading `0`.\n *\n * @example\n * totalCostUsd({ input: 1, output: 2, total: 3, cost: { input: 0.01, output: 0.04 } });\n * // => 0.05\n */\nexport function totalCostUsd(usage: Usage): number | undefined {\n const cost = usage.cost;\n\n if (!cost) {\n return undefined;\n }\n\n return (\n (cost.input ?? 0) +\n (cost.output ?? 0) +\n (cost.cachedInput ?? 0) +\n (cost.cachedOutput ?? 0) +\n (cost.reasoning ?? 0)\n );\n}\n","import type { TraceSpan } from \"../../contracts\";\nimport { totalCostUsd } from \"./total-cost\";\n\n/**\n * Subset of the OpenTelemetry GenAI semantic-convention attribute keys\n * Panoptic emits. Kept as a named constant map (not inline string\n * literals scattered through the mapper) so the convention names live in\n * one place and a convention bump is a single edit.\n *\n * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/\n */\nexport const GEN_AI_ATTRIBUTES = {\n operationName: \"gen_ai.operation.name\",\n system: \"gen_ai.system\",\n requestModel: \"gen_ai.request.model\",\n responseModel: \"gen_ai.response.model\",\n usageInputTokens: \"gen_ai.usage.input_tokens\",\n usageOutputTokens: \"gen_ai.usage.output_tokens\",\n conversationId: \"gen_ai.conversation.id\",\n} as const;\n\n/**\n * Panoptic-specific attribute keys that have no GenAI-convention\n * equivalent. Namespaced under `warlock.*` so they never collide with a\n * future `gen_ai.*` key the spec might add.\n */\nexport const WARLOCK_ATTRIBUTES = {\n reportType: \"warlock.report.type\",\n version: \"warlock.version\",\n durationMs: \"warlock.duration_ms\",\n totalTokens: \"gen_ai.usage.total_tokens\",\n cachedTokens: \"gen_ai.usage.cached_tokens\",\n reasoningTokens: \"gen_ai.usage.reasoning_tokens\",\n costUsd: \"warlock.cost.usd\",\n} as const;\n\n/**\n * Span attribute values an OpenTelemetry / Langfuse backend accepts.\n * GenAI attributes are scalars; the framework's free-form\n * `TraceSpan.attributes` may also carry these.\n */\nexport type AttributeValue = string | number | boolean;\n\n/**\n * Project a {@link TraceSpan} onto the OpenTelemetry GenAI\n * semantic-convention attribute set.\n *\n * The vendor-neutral {@link TraceSpan} carries identity, timing, status,\n * and rolled-up `usage` as first-class fields; model identity and other\n * provider detail live in the free-form `attributes` bag the collector\n * populated. This mapper folds both into a flat `gen_ai.*` /\n * `warlock.*` attribute map ready to set on an OTel span or hand to a\n * Langfuse generation.\n *\n * - `gen_ai.operation.name` / `gen_ai.system` / `gen_ai.request.model`\n * are forwarded from the span's `attributes` when the collector set\n * them; never invented here.\n * - Token counts come from the span's typed `usage` rollup.\n * - The free-form `attributes` are merged last so an explicit collector\n * value wins over a derived one.\n *\n * @example\n * const attributes = toGenAiAttributes(span);\n * // { \"gen_ai.usage.input_tokens\": 150, \"gen_ai.usage.output_tokens\": 320, ... }\n */\nexport function toGenAiAttributes(span: TraceSpan): Record<string, AttributeValue> {\n const attributes: Record<string, AttributeValue> = {\n [WARLOCK_ATTRIBUTES.reportType]: span.type,\n [WARLOCK_ATTRIBUTES.durationMs]: span.duration,\n [WARLOCK_ATTRIBUTES.totalTokens]: span.usage.total,\n [GEN_AI_ATTRIBUTES.usageInputTokens]: span.usage.input,\n [GEN_AI_ATTRIBUTES.usageOutputTokens]: span.usage.output,\n };\n\n if (span.version !== undefined) {\n attributes[WARLOCK_ATTRIBUTES.version] = span.version;\n }\n\n if (span.sessionId !== undefined) {\n attributes[GEN_AI_ATTRIBUTES.conversationId] = span.sessionId;\n }\n\n if (span.usage.cachedTokens !== undefined) {\n attributes[WARLOCK_ATTRIBUTES.cachedTokens] = span.usage.cachedTokens;\n }\n\n if (span.usage.reasoningTokens !== undefined) {\n attributes[WARLOCK_ATTRIBUTES.reasoningTokens] = span.usage.reasoningTokens;\n }\n\n const cost = totalCostUsd(span.usage);\n\n if (cost !== undefined) {\n attributes[WARLOCK_ATTRIBUTES.costUsd] = cost;\n }\n\n mergeScalarAttributes(attributes, span.attributes);\n\n return attributes;\n}\n\n/**\n * Copy the scalar entries of a free-form attribute bag onto the target\n * map. Non-scalar values (objects, arrays, functions) are skipped — OTel\n * and Langfuse attribute values must be primitives, and the collector's\n * bag may legitimately hold nested digests that don't belong on a span\n * attribute. Explicit collector values overwrite derived ones.\n */\nfunction mergeScalarAttributes(\n target: Record<string, AttributeValue>,\n source: Record<string, unknown> | undefined,\n): void {\n if (!source) {\n return;\n }\n\n for (const [key, value] of Object.entries(source)) {\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n target[key] = value;\n }\n }\n}\n","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","import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { walkSpans } from \"../utils\";\nimport type { ConsoleExporterOptions, ConsoleLike } from \"./console-exporter.type\";\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, and\n * `streaming: true` to also print each span the moment it finalizes\n * (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 */\nexport function consoleExporter(options: ConsoleExporterOptions = {}): ExporterContract {\n const sink: ConsoleLike = options.console ?? console;\n const tree = options.tree ?? false;\n\n const exporter: ExporterContract = {\n name: EXPORTER_NAME,\n export(trace: Trace): void {\n writeTrace(sink, trace, tree);\n },\n };\n\n if (options.streaming) {\n exporter.exportSpan = (span: TraceSpan): void => {\n sink.log(formatSpanLine(span));\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. Failed traces are routed to `console.error` so\n * they surface at the right severity in log aggregators.\n */\nfunction writeTrace(sink: ConsoleLike, trace: Trace, tree: boolean): void {\n if (!tree) {\n const line = formatSpanLine(trace.root);\n writeAtSeverity(sink, trace.root.status, line);\n return;\n }\n\n for (const span of walkSpans(trace.root)) {\n const depth = spanDepth(trace.root, span.spanId);\n const line = formatSpanLine(span, depth);\n writeAtSeverity(sink, span.status, line);\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","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","import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { toGenAiAttributes } from \"../utils\";\nimport type {\n LangfuseClientLike,\n LangfuseExporterOptions,\n LangfuseObservationBody,\n LangfuseObservationLevel,\n LangfuseObservationLike,\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; each descendant\n * becomes a nested observation — a `generation` when it produced tokens\n * (LLM-backed agents, supervisors), otherwise a plain `span` (tools,\n * callbacks). Timing, status, version, and rolled-up `usage` are mapped\n * 1:1; the rest of the GenAI attributes ride along as observation\n * metadata.\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 langfuseTrace = client.trace({\n id: root.traceId,\n name: root.name,\n sessionId: trace.sessionId,\n version: root.version,\n timestamp: new Date(root.startedAt),\n metadata: toGenAiAttributes(root),\n });\n\n for (const child of root.children) {\n emitObservation(langfuseTrace, child);\n }\n}\n\n/**\n * Map one {@link TraceSpan} onto a Langfuse observation under `parent`,\n * then recurse its children. Token-producing spans become\n * `generation`s; everything else 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: toGenAiAttributes(span),\n };\n\n let observation: LangfuseObservationLike;\n\n if (producedTokens(span)) {\n body.usage = {\n input: span.usage.input,\n output: span.usage.output,\n total: span.usage.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 * A span counts as an LLM `generation` when it metered any tokens. Pure\n * tools/callbacks contribute zero own-cost and map to plain spans.\n *\n * **Known limitation.** `TraceSpan.usage` is the ROLLED-UP usage (this\n * node's own cost plus the sum of its children), not own-usage — the\n * core `BaseReport` exposes no separate own-usage field, so a composite\n * node (e.g. an agent that itself made no model call but whose tool\n * children did) is classified as a `generation` on its children's\n * tokens. We cannot classify on own-usage until the report shape carries\n * it; the rolled-up `usage` block emitted on such a generation therefore\n * double-counts tokens already attributed to descendant observations.\n */\nfunction producedTokens(span: TraceSpan): boolean {\n return span.usage.total > 0;\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","import type {\n Context,\n Span,\n SpanStatusCode as SpanStatusCodeEnum,\n Tracer,\n} from \"@opentelemetry/api\";\nimport type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { toGenAiAttributes, GEN_AI_ATTRIBUTES } from \"../utils\";\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: typeof import(\"@opentelemetry/api\");\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 OtelApi = await import(\"@opentelemetry/api\");\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): Tracer {\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: Tracer,\n span: TraceSpan,\n parentContext: Context | 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: Span,\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 otelSpan.setAttributes(attributes);\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: Span, span: TraceSpan): void {\n const codes: typeof SpanStatusCodeEnum = 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","import type { AgentMiddleware } from \"@warlock.js/ai\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\n\n/**\n * Build an {@link AgentMiddleware} that feeds a collector from the\n * `execute`- and `supervisor`-level hooks. An alternative wiring to event\n * subscription for apps that already compose cross-cutting concerns\n * through the agent middleware pipeline (`[cache, budget, guardrail,\n * observability]`). Declaring both hook maps lets a single middleware\n * object work uniformly on agents (which fire the `execute` map) and\n * supervisors (which fire the `supervisor` map) — registering it on a\n * supervisor would otherwise install and collect nothing silently.\n *\n * Both terminal paths are covered on each surface:\n * - `after` — fires on a run that produced a result. A run can complete\n * with `result.error` populated (the engine still calls `after`), so\n * the report AND the envelope error are collected; the error type and\n * message land on the root span.\n * - `onError` — fires when the run threw before assembling a result. The\n * error carries the partial result's report on its envelope; when\n * present it is collected, with the error itself threaded onto the\n * root span so failed runs still produce a trace.\n *\n * The hooks never return a value, so they never mutate the agent's /\n * supervisor's result. The `collect` call is fire-and-forget relative to\n * the run — the collector isolates exporter failures internally, and we\n * additionally swallow any rejection here so an observability fault can\n * never surface on the run's hot path.\n *\n * @param collector - the collector traces are fed into.\n * @param name - stable middleware name (kebab-case). Defaults to\n * `\"panoptic\"`.\n */\nexport function createPanopticMiddleware(\n collector: CollectorContract,\n name = \"panoptic\",\n): AgentMiddleware {\n const collectReport = (report: unknown, rootError?: unknown): void => {\n if (!isReport(report)) {\n return;\n }\n\n void collector.collect(report, rootError).catch(() => {\n // Swallow — the collector already isolates exporter failures; this\n // guard keeps an observability fault off the run's hot path.\n });\n };\n\n const onResult = (result: unknown): void => {\n // A run can complete with `result.error` populated (`after` still\n // fires); thread that envelope error onto the root span.\n collectReport(\n (result as { report?: unknown }).report,\n (result as { error?: unknown }).error,\n );\n };\n\n const onError = (error: unknown): void => {\n // A failed run's report rides on the error envelope when the engine\n // built one before throwing; collect it so failures trace, threading\n // the error itself onto the root span.\n collectReport((error as { report?: unknown }).report, error);\n };\n\n const terminalHooks = {\n after(_ctx: unknown, result: unknown) {\n onResult(result);\n },\n onError(_ctx: unknown, error: unknown) {\n onError(error);\n },\n };\n\n return {\n name,\n execute: terminalHooks as AgentMiddleware[\"execute\"],\n supervisor: terminalHooks as AgentMiddleware[\"supervisor\"],\n };\n}\n\n/**\n * Narrow an unknown value to a `BaseReport`-shaped object. Structural\n * (checks the lineage fields the collector reads) so it accepts any\n * primitive's report subtype without importing each concrete type.\n */\nfunction isReport(value: unknown): value is import(\"@warlock.js/ai\").BaseReport {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { runId?: unknown }).runId === \"string\" &&\n typeof (value as { rootRunId?: unknown }).rootRunId === \"string\"\n );\n}\n","import type { AgentMiddleware, BaseReport } from \"@warlock.js/ai\";\nimport { createCollector } from \"../collector/collector\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { createPanopticMiddleware } from \"./panoptic-middleware\";\nimport type { CompletedEventPayload, PanopticTarget } from \"./panoptic-target.type\";\nimport type { Panoptic, PanopticOptions } from \"./panoptic.type\";\n\n/**\n * Terminal `*.completed` events of every core primitive that carries the\n * finalized `result` (and therefore the `report` tree). These fire once\n * per run regardless of outcome — the matching `*.error` event fires\n * first on failure, then `*.completed` still fires — so subscribing here\n * captures completed, failed, and cancelled runs alike.\n *\n * The orchestrator is intentionally absent: its `orchestrator.turn.*`\n * events carry only session identity, not a result. Collect an\n * orchestrator turn via {@link Panoptic.collect} with\n * `result.report` instead.\n */\nconst DEFAULT_COMPLETED_EVENTS = [\n \"agent.completed\",\n \"workflow.completed\",\n \"supervisor.completed\",\n] as const;\n\n/**\n * The Panoptic subscriber — binds a collector + its exporters to the\n * three feed paths (events, middleware, direct). Instantiated via\n * {@link panoptic}; callers never see `new`.\n */\nclass PanopticSubscriber implements Panoptic {\n public readonly collector: CollectorContract;\n\n private readonly completedEvents: string[];\n\n private readonly middlewareName: string;\n\n public constructor(options: PanopticOptions = {}) {\n this.collector = options.collector ?? createCollector();\n\n for (const exporter of options.exporters ?? []) {\n this.collector.use(exporter);\n }\n\n this.completedEvents =\n options.completedEvents ?? [...DEFAULT_COMPLETED_EVENTS];\n this.middlewareName = options.middlewareName ?? \"panoptic\";\n }\n\n public use(exporter: ExporterContract): Panoptic {\n this.collector.use(exporter);\n\n return this;\n }\n\n public attach(target: PanopticTarget): () => void {\n const unsubscribes: Array<() => void> = [];\n\n for (const event of this.completedEvents) {\n const unsubscribe = target.on(event, (payload) => {\n this.handleCompleted(payload);\n });\n\n unsubscribes.push(unsubscribe);\n }\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe();\n }\n };\n }\n\n public middleware(): AgentMiddleware {\n return createPanopticMiddleware(this.collector, this.middlewareName);\n }\n\n public async collect(report: BaseReport): Promise<void> {\n await this.collector.collect(report);\n }\n\n public toTrace(report: BaseReport): Trace {\n return this.collector.toTrace(report);\n }\n\n public async flush(): Promise<void> {\n await this.collector.flush();\n }\n\n public async shutdown(): Promise<void> {\n await this.collector.shutdown();\n }\n\n /**\n * Project one terminal `*.completed` payload's report into the\n * collector. The fan-out is fire-and-forget relative to the emitting\n * run: the core swallows handler errors, the collector isolates\n * exporter failures, and we additionally guard the rejection here so an\n * observability fault never escapes the event handler.\n */\n private handleCompleted(payload: unknown): void {\n const report = readReport(payload);\n\n if (!report) {\n return;\n }\n\n // The failing run's typed error lives on the result envelope\n // (`BaseResult.error`), never on the report tree — thread it so a\n // failed root span carries its error type/message.\n const rootError = readResultError(payload);\n\n void this.collector.collect(report, rootError).catch(() => {\n // Swallow — see the JSDoc above. Never surface on the run.\n });\n }\n}\n\n/**\n * Read the envelope error off a primitive's completed-event payload\n * (`{ result: { error } }`). The error rides on the result envelope, not\n * the report tree, so the collector needs it separately to populate a\n * failed root span. Returns `undefined` when the run succeeded.\n */\nfunction readResultError(payload: unknown): unknown {\n const result = (payload as Partial<CompletedEventPayload>)?.result;\n\n return (result as { error?: unknown })?.error;\n}\n\n/**\n * Read the `report` tree off a primitive's completed-event payload.\n * Structural (no concrete-type import) so it accepts every primitive's\n * result subtype; returns `undefined` when the payload isn't the\n * expected `{ result: { report } }` shape.\n */\nfunction readReport(payload: unknown): BaseReport | undefined {\n const result = (payload as Partial<CompletedEventPayload>)?.result;\n const report = (result as { report?: unknown })?.report;\n\n if (\n typeof report === \"object\" &&\n report !== null &&\n typeof (report as { runId?: unknown }).runId === \"string\" &&\n typeof (report as { rootRunId?: unknown }).rootRunId === \"string\"\n ) {\n return report as BaseReport;\n }\n\n return undefined;\n}\n\n/**\n * Create a Panoptic subscriber — the one-call entry point that wires the\n * observability pipeline. Pass the exporters you want and Panoptic\n * builds a collector, registers them, and hands back a subscriber you can\n * `attach()` to any agent/workflow/supervisor, install as agent\n * `middleware()`, or feed reports to directly with `collect()`.\n *\n * @example\n * // Attach to a primitive's event stream (captures every run):\n * const observe = panoptic({\n * exporters: [consoleExporter(), otelExporter({ tracerName: \"app\" })],\n * });\n *\n * const agent = ai.agent({ model });\n * const detach = observe.attach(agent);\n *\n * await agent.execute(\"Summarize this\");\n * // ...later, on shutdown:\n * await observe.shutdown();\n *\n * @example\n * // Or wire it through the agent middleware pipeline:\n * const observe = panoptic({ exporters: [langfuseExporter({ ... })] });\n * const agent = ai.agent({ model, middleware: [observe.middleware()] });\n *\n * @example\n * // Orchestrator turns carry no result-bearing event — collect directly:\n * const result = await orchestrator.execute(input, { sessionId });\n * await observe.collect(result.report);\n */\nexport function panoptic(options: PanopticOptions = {}): Panoptic {\n return new PanopticSubscriber(options);\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { CollectorContract } from "../contracts/collector.contract.mjs";
2
+
3
+ //#region ../@warlock.js/ai-panoptic/src/collector/collector.d.ts
4
+ /**
5
+ * Create a Panoptic collector — the single integration point an app
6
+ * wires into its agents/workflows (typically via the `onComplete`
7
+ * report hook). Register exporters with `use`, then feed finalized root
8
+ * reports to `collect`.
9
+ *
10
+ * @example
11
+ * const collector = createCollector().use(otelExporter).use(langfuseExporter);
12
+ * agent.on("onComplete", ({ result }) => collector.collect(result.report));
13
+ * // on shutdown:
14
+ * await collector.shutdown();
15
+ */
16
+ declare function createCollector(): CollectorContract;
17
+ //#endregion
18
+ export { createCollector };
19
+ //# sourceMappingURL=collector.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/collector.ts"],"mappings":";;;;;AA8GA;;;;AAAoD;;;;;;iBAApC,eAAA,IAAmB,iBAAiB"}
@@ -0,0 +1,89 @@
1
+ import { walkSpans } from "../exporters/utils/walk-spans.mjs";
2
+ import { reportToTrace } from "./report-to-trace.mjs";
3
+
4
+ //#region ../@warlock.js/ai-panoptic/src/collector/collector.ts
5
+ /**
6
+ * Drive the source end of the Panoptic pipeline: ingest core
7
+ * `@warlock.js/ai` {@link BaseReport} trees, project them into {@link
8
+ * Trace}s, and fan each trace out to every registered exporter.
9
+ *
10
+ * Owns exporter registration (deduped by `ExporterContract.name`), the
11
+ * report→trace projection, and graceful shutdown so exporters drain
12
+ * before exit. Instantiated fresh per collector via {@link
13
+ * createCollector}; callers never see `new`.
14
+ *
15
+ * **Failure isolation.** An exporter that throws never propagates back
16
+ * into the originating run — `collect`, `flush`, and `shutdown` settle
17
+ * every exporter independently (mirrors how the core event hooks
18
+ * swallow consumer errors). One broken exporter can neither crash the
19
+ * agent loop nor stop sibling exporters from receiving the trace.
20
+ */
21
+ var Collector = class {
22
+ constructor() {
23
+ this.exporters = /* @__PURE__ */ new Map();
24
+ }
25
+ use(exporter) {
26
+ if (!this.exporters.has(exporter.name)) this.exporters.set(exporter.name, exporter);
27
+ return this;
28
+ }
29
+ toTrace(report, rootError) {
30
+ return reportToTrace(report, rootError);
31
+ }
32
+ async collect(report, rootError) {
33
+ const trace = this.toTrace(report, rootError);
34
+ await this.dispatch(trace);
35
+ }
36
+ async flush() {
37
+ await this.settleAll((exporter) => exporter.flush?.());
38
+ }
39
+ async shutdown() {
40
+ await this.flush();
41
+ await this.settleAll((exporter) => exporter.shutdown?.());
42
+ this.exporters.clear();
43
+ }
44
+ /**
45
+ * Fan one trace out to every exporter and, when an exporter advertises
46
+ * the per-span hook, deliver every span in the finalized tree to it as
47
+ * well. `exportSpan` is a post-completion per-span hook (not a live /
48
+ * streaming feed — the trace is already finalized): we walk the tree in
49
+ * pre-order with {@link walkSpans} so the exporter sees the root and
50
+ * every descendant exactly once. Every invocation is isolated so a
51
+ * throwing exporter can't abort the dispatch to its siblings or escape
52
+ * into the originating run.
53
+ */
54
+ async dispatch(trace) {
55
+ await this.settleAll(async (exporter) => {
56
+ await exporter.export(trace);
57
+ if (exporter.exportSpan !== void 0) for (const span of walkSpans(trace.root)) await exporter.exportSpan(span);
58
+ });
59
+ }
60
+ /**
61
+ * Run `task` against every registered exporter and wait for all of
62
+ * them to settle, swallowing individual rejections. `Promise.allSettled`
63
+ * guarantees one failure neither rejects the batch nor blocks the
64
+ * others — the contract's failure-isolation requirement.
65
+ */
66
+ async settleAll(task) {
67
+ const runs = [...this.exporters.values()].map((exporter) => Promise.resolve().then(() => task(exporter)));
68
+ await Promise.allSettled(runs);
69
+ }
70
+ };
71
+ /**
72
+ * Create a Panoptic collector — the single integration point an app
73
+ * wires into its agents/workflows (typically via the `onComplete`
74
+ * report hook). Register exporters with `use`, then feed finalized root
75
+ * reports to `collect`.
76
+ *
77
+ * @example
78
+ * const collector = createCollector().use(otelExporter).use(langfuseExporter);
79
+ * agent.on("onComplete", ({ result }) => collector.collect(result.report));
80
+ * // on shutdown:
81
+ * await collector.shutdown();
82
+ */
83
+ function createCollector() {
84
+ return new Collector();
85
+ }
86
+
87
+ //#endregion
88
+ export { createCollector };
89
+ //# sourceMappingURL=collector.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/collector.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { walkSpans } from \"../exporters/utils/walk-spans\";\nimport { reportToTrace } from \"./report-to-trace\";\n\n/**\n * Drive the source end of the Panoptic pipeline: ingest core\n * `@warlock.js/ai` {@link BaseReport} trees, project them into {@link\n * Trace}s, and fan each trace out to every registered exporter.\n *\n * Owns exporter registration (deduped by `ExporterContract.name`), the\n * report→trace projection, and graceful shutdown so exporters drain\n * before exit. Instantiated fresh per collector via {@link\n * createCollector}; callers never see `new`.\n *\n * **Failure isolation.** An exporter that throws never propagates back\n * into the originating run — `collect`, `flush`, and `shutdown` settle\n * every exporter independently (mirrors how the core event hooks\n * swallow consumer errors). One broken exporter can neither crash the\n * agent loop nor stop sibling exporters from receiving the trace.\n */\nclass Collector implements CollectorContract {\n /**\n * Registered exporters in insertion order. A `Map` keyed by\n * `ExporterContract.name` gives O(1) dedupe on `use` while preserving\n * registration order for deterministic fan-out.\n */\n private readonly exporters = new Map<string, ExporterContract>();\n\n public use(exporter: ExporterContract): this {\n if (!this.exporters.has(exporter.name)) {\n this.exporters.set(exporter.name, exporter);\n }\n\n return this;\n }\n\n public toTrace(report: BaseReport, rootError?: unknown): Trace {\n return reportToTrace(report, rootError);\n }\n\n public async collect(report: BaseReport, rootError?: unknown): Promise<void> {\n const trace = this.toTrace(report, rootError);\n\n await this.dispatch(trace);\n }\n\n public async flush(): Promise<void> {\n await this.settleAll((exporter) => exporter.flush?.());\n }\n\n public async shutdown(): Promise<void> {\n await this.flush();\n\n await this.settleAll((exporter) => exporter.shutdown?.());\n\n this.exporters.clear();\n }\n\n /**\n * Fan one trace out to every exporter and, when an exporter advertises\n * the per-span hook, deliver every span in the finalized tree to it as\n * well. `exportSpan` is a post-completion per-span hook (not a live /\n * streaming feed — the trace is already finalized): we walk the tree in\n * pre-order with {@link walkSpans} so the exporter sees the root and\n * every descendant exactly once. Every invocation is isolated so a\n * throwing exporter can't abort the dispatch to its siblings or escape\n * into the originating run.\n */\n private async dispatch(trace: Trace): Promise<void> {\n await this.settleAll(async (exporter) => {\n await exporter.export(trace);\n\n if (exporter.exportSpan !== undefined) {\n for (const span of walkSpans(trace.root)) {\n await exporter.exportSpan(span);\n }\n }\n });\n }\n\n /**\n * Run `task` against every registered exporter and wait for all of\n * them to settle, swallowing individual rejections. `Promise.allSettled`\n * guarantees one failure neither rejects the batch nor blocks the\n * others — the contract's failure-isolation requirement.\n */\n private async settleAll(\n task: (exporter: ExporterContract) => void | Promise<void>,\n ): Promise<void> {\n const runs = [...this.exporters.values()].map((exporter) => Promise.resolve().then(() => task(exporter)));\n\n await Promise.allSettled(runs);\n }\n}\n\n/**\n * Create a Panoptic collector — the single integration point an app\n * wires into its agents/workflows (typically via the `onComplete`\n * report hook). Register exporters with `use`, then feed finalized root\n * reports to `collect`.\n *\n * @example\n * const collector = createCollector().use(otelExporter).use(langfuseExporter);\n * agent.on(\"onComplete\", ({ result }) => collector.collect(result.report));\n * // on shutdown:\n * await collector.shutdown();\n */\nexport function createCollector(): CollectorContract {\n return new Collector();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,IAAM,YAAN,MAA6C;;mCAMd,IAAI,IAA8B;;CAE/D,AAAO,IAAI,UAAkC;EAC3C,IAAI,CAAC,KAAK,UAAU,IAAI,SAAS,IAAI,GACnC,KAAK,UAAU,IAAI,SAAS,MAAM,QAAQ;EAG5C,OAAO;CACT;CAEA,AAAO,QAAQ,QAAoB,WAA4B;EAC7D,OAAO,cAAc,QAAQ,SAAS;CACxC;CAEA,MAAa,QAAQ,QAAoB,WAAoC;EAC3E,MAAM,QAAQ,KAAK,QAAQ,QAAQ,SAAS;EAE5C,MAAM,KAAK,SAAS,KAAK;CAC3B;CAEA,MAAa,QAAuB;EAClC,MAAM,KAAK,WAAW,aAAa,SAAS,QAAQ,CAAC;CACvD;CAEA,MAAa,WAA0B;EACrC,MAAM,KAAK,MAAM;EAEjB,MAAM,KAAK,WAAW,aAAa,SAAS,WAAW,CAAC;EAExD,KAAK,UAAU,MAAM;CACvB;;;;;;;;;;;CAYA,MAAc,SAAS,OAA6B;EAClD,MAAM,KAAK,UAAU,OAAO,aAAa;GACvC,MAAM,SAAS,OAAO,KAAK;GAE3B,IAAI,SAAS,eAAe,QAC1B,KAAK,MAAM,QAAQ,UAAU,MAAM,IAAI,GACrC,MAAM,SAAS,WAAW,IAAI;EAGpC,CAAC;CACH;;;;;;;CAQA,MAAc,UACZ,MACe;EACf,MAAM,OAAO,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC,KAAK,aAAa,QAAQ,QAAQ,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC;EAExG,MAAM,QAAQ,WAAW,IAAI;CAC/B;AACF;;;;;;;;;;;;;AAcA,SAAgB,kBAAqC;CACnD,OAAO,IAAI,UAAU;AACvB"}
@@ -0,0 +1,29 @@
1
+ import { BaseReport } from "@warlock.js/ai";
2
+
3
+ //#region ../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.d.ts
4
+ /**
5
+ * Build the free-form `TraceSpan.attributes` bag for one report node.
6
+ *
7
+ * The collector keeps the first-class span fields (identity, timing,
8
+ * status, usage, error) on the span itself and routes everything
9
+ * primitive-specific here — trip/step/iteration counts, the model
10
+ * identity an agent ran against, the tool's originating trip index, a
11
+ * supervisor's termination reason. Exporters forward this verbatim as
12
+ * backend span attributes (OTel attributes, Langfuse metadata).
13
+ *
14
+ * Only populated keys are emitted; the function returns `undefined`
15
+ * when the node carried no extra detail, so the optional
16
+ * `TraceSpan.attributes` field stays absent rather than holding an
17
+ * empty object (matches the contract's "absent when empty" note).
18
+ *
19
+ * Retry count is surfaced from the shared `BaseReport.attempts` for
20
+ * every primitive so cost dashboards see the real call count.
21
+ *
22
+ * @example
23
+ * const attributes = extractSpanAttributes(agentReport);
24
+ * // { "agent.trips": 3, "agent.model.name": "gpt-4o", "agent.model.provider": "openai" }
25
+ */
26
+ declare function extractSpanAttributes(report: BaseReport): Record<string, unknown> | undefined;
27
+ //#endregion
28
+ export { extractSpanAttributes };
29
+ //# sourceMappingURL=extract-span-attributes.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-span-attributes.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts"],"mappings":";;;;;AAqDA;;;;;;;;AAAiE;;;;;;;;;;;;iBAAjD,qBAAA,CAAsB,MAAA,EAAQ,UAAA,GAAa,MAAM"}
@@ -0,0 +1,76 @@
1
+ //#region ../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts
2
+ /**
3
+ * Build the free-form `TraceSpan.attributes` bag for one report node.
4
+ *
5
+ * The collector keeps the first-class span fields (identity, timing,
6
+ * status, usage, error) on the span itself and routes everything
7
+ * primitive-specific here — trip/step/iteration counts, the model
8
+ * identity an agent ran against, the tool's originating trip index, a
9
+ * supervisor's termination reason. Exporters forward this verbatim as
10
+ * backend span attributes (OTel attributes, Langfuse metadata).
11
+ *
12
+ * Only populated keys are emitted; the function returns `undefined`
13
+ * when the node carried no extra detail, so the optional
14
+ * `TraceSpan.attributes` field stays absent rather than holding an
15
+ * empty object (matches the contract's "absent when empty" note).
16
+ *
17
+ * Retry count is surfaced from the shared `BaseReport.attempts` for
18
+ * every primitive so cost dashboards see the real call count.
19
+ *
20
+ * @example
21
+ * const attributes = extractSpanAttributes(agentReport);
22
+ * // { "agent.trips": 3, "agent.model.name": "gpt-4o", "agent.model.provider": "openai" }
23
+ */
24
+ function extractSpanAttributes(report) {
25
+ const extensions = report;
26
+ const attributes = {};
27
+ if (report.attempts !== void 0 && report.attempts.length > 0) attributes["retries"] = report.attempts.length;
28
+ switch (report.type) {
29
+ case "agent":
30
+ addAgentAttributes(attributes, extensions);
31
+ break;
32
+ case "workflow":
33
+ addWorkflowAttributes(attributes, extensions);
34
+ break;
35
+ case "supervisor":
36
+ addSupervisorAttributes(attributes, extensions);
37
+ break;
38
+ case "orchestrator":
39
+ addOrchestratorAttributes(attributes, extensions);
40
+ break;
41
+ case "tool":
42
+ addToolAttributes(attributes, extensions);
43
+ break;
44
+ default: break;
45
+ }
46
+ if (Object.keys(attributes).length === 0) return;
47
+ return attributes;
48
+ }
49
+ function addAgentAttributes(attributes, extensions) {
50
+ if (Array.isArray(extensions.trips)) attributes["agent.trips"] = extensions.trips.length;
51
+ if (extensions.model?.name !== void 0) attributes["agent.model.name"] = extensions.model.name;
52
+ if (extensions.model?.provider !== void 0) attributes["agent.model.provider"] = extensions.model.provider;
53
+ }
54
+ function addWorkflowAttributes(attributes, extensions) {
55
+ if (extensions.workflowName !== void 0) attributes["workflow.name"] = extensions.workflowName;
56
+ if (extensions.signature !== void 0) attributes["workflow.signature"] = extensions.signature;
57
+ if (extensions.steps !== void 0) attributes["workflow.steps"] = Object.keys(extensions.steps).length;
58
+ }
59
+ function addSupervisorAttributes(attributes, extensions) {
60
+ if (extensions.supervisorName !== void 0) attributes["supervisor.name"] = extensions.supervisorName;
61
+ if (extensions.terminatedBy !== void 0) attributes["supervisor.terminatedBy"] = extensions.terminatedBy;
62
+ if (extensions.iterations !== void 0) attributes["supervisor.iterations"] = extensions.iterations;
63
+ }
64
+ function addOrchestratorAttributes(attributes, extensions) {
65
+ if (extensions.turnIndex !== void 0) attributes["orchestrator.turnIndex"] = extensions.turnIndex;
66
+ if (extensions.signature !== void 0) attributes["orchestrator.signature"] = extensions.signature;
67
+ if (Array.isArray(extensions.turns)) attributes["orchestrator.turns"] = extensions.turns.length;
68
+ }
69
+ function addToolAttributes(attributes, extensions) {
70
+ if (extensions.tripIndex !== void 0) attributes["tool.tripIndex"] = extensions.tripIndex;
71
+ if (extensions.recoveredFrom !== void 0) attributes["tool.recoveredFrom"] = extensions.recoveredFrom;
72
+ }
73
+
74
+ //#endregion
75
+ export { extractSpanAttributes };
76
+ //# sourceMappingURL=extract-span-attributes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-span-attributes.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\n\n/**\n * Per-primitive `BaseReport` extension fields the collector surfaces as\n * span attributes. Declared as a structural superset (every field\n * optional) so a single reader can pull whatever the concrete report\n * carried without narrowing on `type` first — a `BaseReport` widened to\n * this shape exposes `undefined` for the fields its primitive doesn't\n * set, and the extractor simply skips those.\n *\n * Mirrors the public per-primitive report types in `@warlock.js/ai`\n * (`AgentReport`, `WorkflowReport`, `SupervisorReport`,\n * `OrchestratorReport`, `ToolCall`) — kept structural rather than a\n * union import so adding a new optional report field upstream is a\n * non-breaking read here.\n */\ntype ReportExtensions = {\n model?: { name?: string; provider?: string };\n trips?: unknown[];\n workflowName?: string;\n supervisorName?: string;\n signature?: string;\n terminatedBy?: string;\n iterations?: number;\n steps?: Record<string, unknown>;\n turns?: unknown[];\n turnIndex?: number;\n tripIndex?: number;\n recoveredFrom?: string;\n};\n\n/**\n * Build the free-form `TraceSpan.attributes` bag for one report node.\n *\n * The collector keeps the first-class span fields (identity, timing,\n * status, usage, error) on the span itself and routes everything\n * primitive-specific here — trip/step/iteration counts, the model\n * identity an agent ran against, the tool's originating trip index, a\n * supervisor's termination reason. Exporters forward this verbatim as\n * backend span attributes (OTel attributes, Langfuse metadata).\n *\n * Only populated keys are emitted; the function returns `undefined`\n * when the node carried no extra detail, so the optional\n * `TraceSpan.attributes` field stays absent rather than holding an\n * empty object (matches the contract's \"absent when empty\" note).\n *\n * Retry count is surfaced from the shared `BaseReport.attempts` for\n * every primitive so cost dashboards see the real call count.\n *\n * @example\n * const attributes = extractSpanAttributes(agentReport);\n * // { \"agent.trips\": 3, \"agent.model.name\": \"gpt-4o\", \"agent.model.provider\": \"openai\" }\n */\nexport function extractSpanAttributes(report: BaseReport): Record<string, unknown> | undefined {\n const extensions = report as BaseReport & ReportExtensions;\n const attributes: Record<string, unknown> = {};\n\n if (report.attempts !== undefined && report.attempts.length > 0) {\n attributes[\"retries\"] = report.attempts.length;\n }\n\n switch (report.type) {\n case \"agent\": {\n addAgentAttributes(attributes, extensions);\n break;\n }\n\n case \"workflow\": {\n addWorkflowAttributes(attributes, extensions);\n break;\n }\n\n case \"supervisor\": {\n addSupervisorAttributes(attributes, extensions);\n break;\n }\n\n case \"orchestrator\": {\n addOrchestratorAttributes(attributes, extensions);\n break;\n }\n\n case \"tool\": {\n addToolAttributes(attributes, extensions);\n break;\n }\n\n default: {\n break;\n }\n }\n\n if (Object.keys(attributes).length === 0) {\n return undefined;\n }\n\n return attributes;\n}\n\nfunction addAgentAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (Array.isArray(extensions.trips)) {\n attributes[\"agent.trips\"] = extensions.trips.length;\n }\n\n if (extensions.model?.name !== undefined) {\n attributes[\"agent.model.name\"] = extensions.model.name;\n }\n\n if (extensions.model?.provider !== undefined) {\n attributes[\"agent.model.provider\"] = extensions.model.provider;\n }\n}\n\nfunction addWorkflowAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.workflowName !== undefined) {\n attributes[\"workflow.name\"] = extensions.workflowName;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"workflow.signature\"] = extensions.signature;\n }\n\n if (extensions.steps !== undefined) {\n attributes[\"workflow.steps\"] = Object.keys(extensions.steps).length;\n }\n}\n\nfunction addSupervisorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.supervisorName !== undefined) {\n attributes[\"supervisor.name\"] = extensions.supervisorName;\n }\n\n if (extensions.terminatedBy !== undefined) {\n attributes[\"supervisor.terminatedBy\"] = extensions.terminatedBy;\n }\n\n if (extensions.iterations !== undefined) {\n attributes[\"supervisor.iterations\"] = extensions.iterations;\n }\n}\n\nfunction addOrchestratorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.turnIndex !== undefined) {\n attributes[\"orchestrator.turnIndex\"] = extensions.turnIndex;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"orchestrator.signature\"] = extensions.signature;\n }\n\n if (Array.isArray(extensions.turns)) {\n attributes[\"orchestrator.turns\"] = extensions.turns.length;\n }\n}\n\nfunction addToolAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.tripIndex !== undefined) {\n attributes[\"tool.tripIndex\"] = extensions.tripIndex;\n }\n\n if (extensions.recoveredFrom !== undefined) {\n attributes[\"tool.recoveredFrom\"] = extensions.recoveredFrom;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,sBAAsB,QAAyD;CAC7F,MAAM,aAAa;CACnB,MAAM,aAAsC,CAAC;CAE7C,IAAI,OAAO,aAAa,UAAa,OAAO,SAAS,SAAS,GAC5D,WAAW,aAAa,OAAO,SAAS;CAG1C,QAAQ,OAAO,MAAf;EACE,KAAK;GACH,mBAAmB,YAAY,UAAU;GACzC;EAGF,KAAK;GACH,sBAAsB,YAAY,UAAU;GAC5C;EAGF,KAAK;GACH,wBAAwB,YAAY,UAAU;GAC9C;EAGF,KAAK;GACH,0BAA0B,YAAY,UAAU;GAChD;EAGF,KAAK;GACH,kBAAkB,YAAY,UAAU;GACxC;EAGF,SACE;CAEJ;CAEA,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,WAAW,GACrC;CAGF,OAAO;AACT;AAEA,SAAS,mBAAmB,YAAqC,YAAoC;CACnG,IAAI,MAAM,QAAQ,WAAW,KAAK,GAChC,WAAW,iBAAiB,WAAW,MAAM;CAG/C,IAAI,WAAW,OAAO,SAAS,QAC7B,WAAW,sBAAsB,WAAW,MAAM;CAGpD,IAAI,WAAW,OAAO,aAAa,QACjC,WAAW,0BAA0B,WAAW,MAAM;AAE1D;AAEA,SAAS,sBAAsB,YAAqC,YAAoC;CACtG,IAAI,WAAW,iBAAiB,QAC9B,WAAW,mBAAmB,WAAW;CAG3C,IAAI,WAAW,cAAc,QAC3B,WAAW,wBAAwB,WAAW;CAGhD,IAAI,WAAW,UAAU,QACvB,WAAW,oBAAoB,OAAO,KAAK,WAAW,KAAK,CAAC,CAAC;AAEjE;AAEA,SAAS,wBAAwB,YAAqC,YAAoC;CACxG,IAAI,WAAW,mBAAmB,QAChC,WAAW,qBAAqB,WAAW;CAG7C,IAAI,WAAW,iBAAiB,QAC9B,WAAW,6BAA6B,WAAW;CAGrD,IAAI,WAAW,eAAe,QAC5B,WAAW,2BAA2B,WAAW;AAErD;AAEA,SAAS,0BAA0B,YAAqC,YAAoC;CAC1G,IAAI,WAAW,cAAc,QAC3B,WAAW,4BAA4B,WAAW;CAGpD,IAAI,WAAW,cAAc,QAC3B,WAAW,4BAA4B,WAAW;CAGpD,IAAI,MAAM,QAAQ,WAAW,KAAK,GAChC,WAAW,wBAAwB,WAAW,MAAM;AAExD;AAEA,SAAS,kBAAkB,YAAqC,YAAoC;CAClG,IAAI,WAAW,cAAc,QAC3B,WAAW,oBAAoB,WAAW;CAG5C,IAAI,WAAW,kBAAkB,QAC/B,WAAW,wBAAwB,WAAW;AAElD"}
@@ -0,0 +1,5 @@
1
+ import { createCollector } from "./collector.mjs";
2
+ import { reportToTrace } from "./report-to-trace.mjs";
3
+ import { reportToSpan } from "./report-to-span.mjs";
4
+ import { extractSpanAttributes } from "./extract-span-attributes.mjs";
5
+ import { normalizeError } from "./normalize-error.mjs";
@@ -0,0 +1,27 @@
1
+ import { TraceSpanError } from "../contracts/trace.type.mjs";
2
+
3
+ //#region ../@warlock.js/ai-panoptic/src/collector/normalize-error.d.ts
4
+ /**
5
+ * Project a captured execution error onto the structural
6
+ * {@link TraceSpanError} shape used on a failed / cancelled span.
7
+ *
8
+ * Source errors are typically `AIError` instances (every error surfaced
9
+ * by `@warlock.js/ai` is one), but the collector never depends on the
10
+ * concrete class — it reads only the structural surface (`name` / `code`
11
+ * / `message` / `stack`) so a plain `Error`, an `AIError`, or any
12
+ * thrown value all normalize identically. The result is a JSON-safe
13
+ * plain object so it survives serialization to a backend collector
14
+ * unchanged.
15
+ *
16
+ * The error `type` prefers the stable `code` (e.g. `"RATE_LIMIT"`) over
17
+ * the class `name`, falling back to `name` and finally to the generic
18
+ * `"Error"` so the field is always populated.
19
+ *
20
+ * @example
21
+ * const spanError = normalizeError(report.error);
22
+ * // { type: "RATE_LIMIT", message: "429 Too Many Requests", stack: "..." }
23
+ */
24
+ declare function normalizeError(error: unknown): TraceSpanError | undefined;
25
+ //#endregion
26
+ export { normalizeError };
27
+ //# sourceMappingURL=normalize-error.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-error.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/normalize-error.ts"],"mappings":";;;;;AAsBA;;;;AAA8D;;;;;;;;;;;;;;iBAA9C,cAAA,CAAe,KAAA,YAAiB,cAAc"}
@@ -0,0 +1,49 @@
1
+ //#region ../@warlock.js/ai-panoptic/src/collector/normalize-error.ts
2
+ /**
3
+ * Project a captured execution error onto the structural
4
+ * {@link TraceSpanError} shape used on a failed / cancelled span.
5
+ *
6
+ * Source errors are typically `AIError` instances (every error surfaced
7
+ * by `@warlock.js/ai` is one), but the collector never depends on the
8
+ * concrete class — it reads only the structural surface (`name` / `code`
9
+ * / `message` / `stack`) so a plain `Error`, an `AIError`, or any
10
+ * thrown value all normalize identically. The result is a JSON-safe
11
+ * plain object so it survives serialization to a backend collector
12
+ * unchanged.
13
+ *
14
+ * The error `type` prefers the stable `code` (e.g. `"RATE_LIMIT"`) over
15
+ * the class `name`, falling back to `name` and finally to the generic
16
+ * `"Error"` so the field is always populated.
17
+ *
18
+ * @example
19
+ * const spanError = normalizeError(report.error);
20
+ * // { type: "RATE_LIMIT", message: "429 Too Many Requests", stack: "..." }
21
+ */
22
+ function normalizeError(error) {
23
+ if (error === void 0 || error === null) return;
24
+ if (typeof error !== "object") return {
25
+ type: "Error",
26
+ message: String(error)
27
+ };
28
+ const candidate = error;
29
+ const type = pickString(candidate.code) ?? pickString(candidate.name) ?? "Error";
30
+ const message = pickString(candidate.message) ?? "";
31
+ const stack = pickString(candidate.stack);
32
+ const normalized = {
33
+ type,
34
+ message
35
+ };
36
+ if (stack !== void 0) normalized.stack = stack;
37
+ return normalized;
38
+ }
39
+ /**
40
+ * Return the value when it is a non-empty string, otherwise `undefined`.
41
+ * Keeps `normalizeError` from promoting empty / non-string fields.
42
+ */
43
+ function pickString(value) {
44
+ if (typeof value === "string" && value.length > 0) return value;
45
+ }
46
+
47
+ //#endregion
48
+ export { normalizeError };
49
+ //# sourceMappingURL=normalize-error.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-error.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/normalize-error.ts"],"sourcesContent":["import type { TraceSpanError } from \"../contracts/trace.type\";\n\n/**\n * Project a captured execution error onto the structural\n * {@link TraceSpanError} shape used on a failed / cancelled span.\n *\n * Source errors are typically `AIError` instances (every error surfaced\n * by `@warlock.js/ai` is one), but the collector never depends on the\n * concrete class — it reads only the structural surface (`name` / `code`\n * / `message` / `stack`) so a plain `Error`, an `AIError`, or any\n * thrown value all normalize identically. The result is a JSON-safe\n * plain object so it survives serialization to a backend collector\n * unchanged.\n *\n * The error `type` prefers the stable `code` (e.g. `\"RATE_LIMIT\"`) over\n * the class `name`, falling back to `name` and finally to the generic\n * `\"Error\"` so the field is always populated.\n *\n * @example\n * const spanError = normalizeError(report.error);\n * // { type: \"RATE_LIMIT\", message: \"429 Too Many Requests\", stack: \"...\" }\n */\nexport function normalizeError(error: unknown): TraceSpanError | undefined {\n if (error === undefined || error === null) {\n return undefined;\n }\n\n if (typeof error !== \"object\") {\n return {\n type: \"Error\",\n message: String(error),\n };\n }\n\n const candidate = error as {\n code?: unknown;\n name?: unknown;\n message?: unknown;\n stack?: unknown;\n };\n\n const type = pickString(candidate.code) ?? pickString(candidate.name) ?? \"Error\";\n const message = pickString(candidate.message) ?? \"\";\n const stack = pickString(candidate.stack);\n\n const normalized: TraceSpanError = {\n type,\n message,\n };\n\n if (stack !== undefined) {\n normalized.stack = stack;\n }\n\n return normalized;\n}\n\n/**\n * Return the value when it is a non-empty string, otherwise `undefined`.\n * Keeps `normalizeError` from promoting empty / non-string fields.\n */\nfunction pickString(value: unknown): string | undefined {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,eAAe,OAA4C;CACzE,IAAI,UAAU,UAAa,UAAU,MACnC;CAGF,IAAI,OAAO,UAAU,UACnB,OAAO;EACL,MAAM;EACN,SAAS,OAAO,KAAK;CACvB;CAGF,MAAM,YAAY;CAOlB,MAAM,OAAO,WAAW,UAAU,IAAI,KAAK,WAAW,UAAU,IAAI,KAAK;CACzE,MAAM,UAAU,WAAW,UAAU,OAAO,KAAK;CACjD,MAAM,QAAQ,WAAW,UAAU,KAAK;CAExC,MAAM,aAA6B;EACjC;EACA;CACF;CAEA,IAAI,UAAU,QACZ,WAAW,QAAQ;CAGrB,OAAO;AACT;;;;;AAMA,SAAS,WAAW,OAAoC;CACtD,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC9C,OAAO;AAIX"}
@@ -0,0 +1,31 @@
1
+ import { TraceSpan } from "../contracts/trace.type.mjs";
2
+ import { BaseReport } from "@warlock.js/ai";
3
+
4
+ //#region ../@warlock.js/ai-panoptic/src/collector/report-to-span.d.ts
5
+ /**
6
+ * Project one {@link BaseReport} node — and its entire subtree — into a
7
+ * {@link TraceSpan}. Pure and recursive: identity, timing, status, and
8
+ * the rolled-up `usage` map across 1:1 from the report; lineage maps
9
+ * `runId → spanId`, `parentRunId → parentSpanId`, `rootRunId → traceId`;
10
+ * children recurse in invocation order so the span tree mirrors the
11
+ * report tree exactly.
12
+ *
13
+ * The error is normalized to the JSON-safe {@link
14
+ * import("../contracts/trace.type").TraceSpanError} shape only when the
15
+ * node carried one (failed / cancelled). Primitive-specific detail that
16
+ * has no first-class span field (trip / step / iteration counts, model
17
+ * identity, tool trip index) is routed into the optional `attributes`
18
+ * bag via {@link extractSpanAttributes}.
19
+ *
20
+ * No external lookup is needed — a `BaseReport` already carries
21
+ * everything a span requires, so a collector can flatten a tree without
22
+ * consulting any other source.
23
+ *
24
+ * @example
25
+ * const root = reportToSpan(result.report);
26
+ * console.log(root.spanId, root.traceId, root.children.length);
27
+ */
28
+ declare function reportToSpan(report: BaseReport): TraceSpan;
29
+ //#endregion
30
+ export { reportToSpan };
31
+ //# sourceMappingURL=report-to-span.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-to-span.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-span.ts"],"mappings":";;;;;;AA4BA;;;;;;;;AAA2D;;;;;;;;;;;;;iBAA3C,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,SAAS"}