@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.
- package/CHANGELOG.md +47 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/cjs/index.cjs +1370 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/collector/collector.d.mts +19 -0
- package/esm/collector/collector.d.mts.map +1 -0
- package/esm/collector/collector.mjs +89 -0
- package/esm/collector/collector.mjs.map +1 -0
- package/esm/collector/extract-span-attributes.d.mts +29 -0
- package/esm/collector/extract-span-attributes.d.mts.map +1 -0
- package/esm/collector/extract-span-attributes.mjs +76 -0
- package/esm/collector/extract-span-attributes.mjs.map +1 -0
- package/esm/collector/index.d.mts +5 -0
- package/esm/collector/normalize-error.d.mts +27 -0
- package/esm/collector/normalize-error.d.mts.map +1 -0
- package/esm/collector/normalize-error.mjs +49 -0
- package/esm/collector/normalize-error.mjs.map +1 -0
- package/esm/collector/report-to-span.d.mts +31 -0
- package/esm/collector/report-to-span.d.mts.map +1 -0
- package/esm/collector/report-to-span.mjs +53 -0
- package/esm/collector/report-to-span.mjs.map +1 -0
- package/esm/collector/report-to-trace.d.mts +35 -0
- package/esm/collector/report-to-trace.d.mts.map +1 -0
- package/esm/collector/report-to-trace.mjs +53 -0
- package/esm/collector/report-to-trace.mjs.map +1 -0
- package/esm/contracts/collector.contract.d.mts +63 -0
- package/esm/contracts/collector.contract.d.mts.map +1 -0
- package/esm/contracts/exporter.contract.d.mts +76 -0
- package/esm/contracts/exporter.contract.d.mts.map +1 -0
- package/esm/contracts/index.d.mts +3 -0
- package/esm/contracts/trace.type.d.mts +122 -0
- package/esm/contracts/trace.type.d.mts.map +1 -0
- package/esm/exporters/console/console-exporter.d.mts +25 -0
- package/esm/exporters/console/console-exporter.d.mts.map +1 -0
- package/esm/exporters/console/console-exporter.mjs +79 -0
- package/esm/exporters/console/console-exporter.mjs.map +1 -0
- package/esm/exporters/console/console-exporter.type.d.mts +36 -0
- package/esm/exporters/console/console-exporter.type.d.mts.map +1 -0
- package/esm/exporters/console/format-span-line.d.mts +17 -0
- package/esm/exporters/console/format-span-line.d.mts.map +1 -0
- package/esm/exporters/console/format-span-line.mjs +41 -0
- package/esm/exporters/console/format-span-line.mjs.map +1 -0
- package/esm/exporters/console/index.d.mts +3 -0
- package/esm/exporters/file/file-exporter.d.mts +23 -0
- package/esm/exporters/file/file-exporter.d.mts.map +1 -0
- package/esm/exporters/file/file-exporter.mjs +93 -0
- package/esm/exporters/file/file-exporter.mjs.map +1 -0
- package/esm/exporters/file/file-exporter.type.d.mts +39 -0
- package/esm/exporters/file/file-exporter.type.d.mts.map +1 -0
- package/esm/exporters/file/index.d.mts +2 -0
- package/esm/exporters/index.d.mts +12 -0
- package/esm/exporters/index.mjs +12 -0
- package/esm/exporters/langfuse/index.d.mts +2 -0
- package/esm/exporters/langfuse/index.mjs +3 -0
- package/esm/exporters/langfuse/langfuse-exporter.d.mts +27 -0
- package/esm/exporters/langfuse/langfuse-exporter.d.mts.map +1 -0
- package/esm/exporters/langfuse/langfuse-exporter.mjs +158 -0
- package/esm/exporters/langfuse/langfuse-exporter.mjs.map +1 -0
- package/esm/exporters/langfuse/langfuse-exporter.type.d.mts +90 -0
- package/esm/exporters/langfuse/langfuse-exporter.type.d.mts.map +1 -0
- package/esm/exporters/otel/index.d.mts +2 -0
- package/esm/exporters/otel/index.mjs +3 -0
- package/esm/exporters/otel/otel-exporter.d.mts +29 -0
- package/esm/exporters/otel/otel-exporter.d.mts.map +1 -0
- package/esm/exporters/otel/otel-exporter.mjs +142 -0
- package/esm/exporters/otel/otel-exporter.mjs.map +1 -0
- package/esm/exporters/otel/otel-exporter.type.d.mts +39 -0
- package/esm/exporters/otel/otel-exporter.type.d.mts.map +1 -0
- package/esm/exporters/utils/gen-ai-attributes.d.mts +65 -0
- package/esm/exporters/utils/gen-ai-attributes.d.mts.map +1 -0
- package/esm/exporters/utils/gen-ai-attributes.mjs +88 -0
- package/esm/exporters/utils/gen-ai-attributes.mjs.map +1 -0
- package/esm/exporters/utils/index.d.mts +3 -0
- package/esm/exporters/utils/total-cost.d.mts +20 -0
- package/esm/exporters/utils/total-cost.d.mts.map +1 -0
- package/esm/exporters/utils/total-cost.mjs +23 -0
- package/esm/exporters/utils/total-cost.mjs.map +1 -0
- package/esm/exporters/utils/walk-spans.d.mts +18 -0
- package/esm/exporters/utils/walk-spans.d.mts.map +1 -0
- package/esm/exporters/utils/walk-spans.mjs +21 -0
- package/esm/exporters/utils/walk-spans.mjs.map +1 -0
- package/esm/index.d.mts +31 -0
- package/esm/index.mjs +22 -0
- package/esm/panoptic/index.d.mts +4 -0
- package/esm/panoptic/panoptic-middleware.d.mts +38 -0
- package/esm/panoptic/panoptic-middleware.d.mts.map +1 -0
- package/esm/panoptic/panoptic-middleware.mjs +68 -0
- package/esm/panoptic/panoptic-middleware.mjs.map +1 -0
- package/esm/panoptic/panoptic-target.type.d.mts +53 -0
- package/esm/panoptic/panoptic-target.type.d.mts.map +1 -0
- package/esm/panoptic/panoptic.d.mts +37 -0
- package/esm/panoptic/panoptic.d.mts.map +1 -0
- package/esm/panoptic/panoptic.mjs +134 -0
- package/esm/panoptic/panoptic.mjs.map +1 -0
- package/esm/panoptic/panoptic.type.d.mts +100 -0
- package/esm/panoptic/panoptic.type.d.mts.map +1 -0
- package/esm/store/in-memory-trace-store.d.mts +32 -0
- package/esm/store/in-memory-trace-store.d.mts.map +1 -0
- package/esm/store/in-memory-trace-store.mjs +134 -0
- package/esm/store/in-memory-trace-store.mjs.map +1 -0
- package/esm/store/index.d.mts +6 -0
- package/esm/store/index.mjs +5 -0
- package/esm/store/match-trace.d.mts +21 -0
- package/esm/store/match-trace.d.mts.map +1 -0
- package/esm/store/match-trace.mjs +44 -0
- package/esm/store/match-trace.mjs.map +1 -0
- package/esm/store/sum-usage.d.mts +34 -0
- package/esm/store/sum-usage.d.mts.map +1 -0
- package/esm/store/sum-usage.mjs +65 -0
- package/esm/store/sum-usage.mjs.map +1 -0
- package/esm/store/trace-aggregate.type.d.mts +51 -0
- package/esm/store/trace-aggregate.type.d.mts.map +1 -0
- package/esm/store/trace-query.type.d.mts +45 -0
- package/esm/store/trace-query.type.d.mts.map +1 -0
- package/esm/store/trace-store.contract.d.mts +55 -0
- package/esm/store/trace-store.contract.d.mts.map +1 -0
- package/llms-full.txt +450 -0
- package/llms.txt +11 -0
- package/package.json +40 -0
- package/skills/README.md +17 -0
- package/skills/export-traces/SKILL.md +152 -0
- package/skills/observe-with-panoptic/SKILL.md +128 -0
- package/skills/query-traces/SKILL.md +152 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { totalCostUsd } from "../utils/total-cost.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.ts
|
|
4
|
+
/**
|
|
5
|
+
* Render a single {@link TraceSpan} as one scannable console line:
|
|
6
|
+
* `<status> <type> "<name>" — <duration>ms, <tokens> tok[, $<cost>]`.
|
|
7
|
+
* `depth` controls leading indentation when printing a tree. Pure — no
|
|
8
|
+
* side effects — so it is trivially testable and reused by both the
|
|
9
|
+
* per-trace summary and the per-span streaming line.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* formatSpanLine(span, 1);
|
|
13
|
+
* // ' ok agent "router" — 1240ms, 470 tok, $0.0021'
|
|
14
|
+
*/
|
|
15
|
+
function formatSpanLine(span, depth = 0) {
|
|
16
|
+
const indent = " ".repeat(depth);
|
|
17
|
+
const marker = statusMarker(span.status);
|
|
18
|
+
const cost = totalCostUsd(span.usage);
|
|
19
|
+
const costSuffix = cost === void 0 ? "" : `, $${cost.toFixed(4)}`;
|
|
20
|
+
let line = `${indent}${marker} ${span.type} "${span.name}" — ${span.duration}ms, ${span.usage.total} tok${costSuffix}`;
|
|
21
|
+
if (span.error) line += ` [${span.error.type}: ${span.error.message}]`;
|
|
22
|
+
return line;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Short ASCII marker for a span's terminal status. Plain ASCII (no
|
|
26
|
+
* emoji/color codes) so output stays clean in log aggregators and CI.
|
|
27
|
+
*/
|
|
28
|
+
function statusMarker(status) {
|
|
29
|
+
switch (status) {
|
|
30
|
+
case "completed": return "ok";
|
|
31
|
+
case "failed": return "ERR";
|
|
32
|
+
case "cancelled": return "cancel";
|
|
33
|
+
case "max-iterations": return "max-iter";
|
|
34
|
+
case "awaiting-input": return "await";
|
|
35
|
+
default: return status;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { formatSpanLine };
|
|
41
|
+
//# sourceMappingURL=format-span-line.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-span-line.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.ts"],"sourcesContent":["import type { TraceSpan } from \"../../contracts\";\nimport { totalCostUsd } from \"../utils\";\n\n/**\n * Render a single {@link TraceSpan} as one scannable console line:\n * `<status> <type> \"<name>\" — <duration>ms, <tokens> tok[, $<cost>]`.\n * `depth` controls leading indentation when printing a tree. Pure — no\n * side effects — so it is trivially testable and reused by both the\n * per-trace summary and the per-span streaming line.\n *\n * @example\n * formatSpanLine(span, 1);\n * // ' ok agent \"router\" — 1240ms, 470 tok, $0.0021'\n */\nexport function formatSpanLine(span: TraceSpan, depth = 0): string {\n const indent = \" \".repeat(depth);\n const marker = statusMarker(span.status);\n const cost = totalCostUsd(span.usage);\n const costSuffix = cost === undefined ? \"\" : `, $${cost.toFixed(4)}`;\n\n let line = `${indent}${marker} ${span.type} \"${span.name}\" — ${span.duration}ms, ${span.usage.total} tok${costSuffix}`;\n\n if (span.error) {\n line += ` [${span.error.type}: ${span.error.message}]`;\n }\n\n return line;\n}\n\n/**\n * Short ASCII marker for a span's terminal status. Plain ASCII (no\n * emoji/color codes) so output stays clean in log aggregators and CI.\n */\nfunction statusMarker(status: TraceSpan[\"status\"]): string {\n switch (status) {\n case \"completed\":\n return \"ok\";\n case \"failed\":\n return \"ERR\";\n case \"cancelled\":\n return \"cancel\";\n case \"max-iterations\":\n return \"max-iter\";\n case \"awaiting-input\":\n return \"await\";\n default:\n return status;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,eAAe,MAAiB,QAAQ,GAAW;CACjE,MAAM,SAAS,KAAK,OAAO,KAAK;CAChC,MAAM,SAAS,aAAa,KAAK,MAAM;CACvC,MAAM,OAAO,aAAa,KAAK,KAAK;CACpC,MAAM,aAAa,SAAS,SAAY,KAAK,MAAM,KAAK,QAAQ,CAAC;CAEjE,IAAI,OAAO,GAAG,SAAS,OAAO,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,SAAS,MAAM,KAAK,MAAM,MAAM,MAAM;CAE1G,IAAI,KAAK,OACP,QAAQ,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,QAAQ;CAGtD,OAAO;AACT;;;;;AAMA,SAAS,aAAa,QAAqC;CACzD,QAAQ,QAAR;EACE,KAAK,aACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,kBACH,OAAO;EACT,KAAK,kBACH,OAAO;EACT,SACE,OAAO;CACX;AACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ExporterContract } from "../../contracts/exporter.contract.mjs";
|
|
2
|
+
import { FileExporterOptions } from "./file-exporter.type.mjs";
|
|
3
|
+
|
|
4
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Zero-dependency {@link ExporterContract} that appends completed traces
|
|
7
|
+
* to a JSON-Lines file (one JSON record per line by default). Buffers in
|
|
8
|
+
* memory and flushes either every `flushEvery` traces or on an explicit
|
|
9
|
+
* `flush()` / `shutdown()`, so a batch of traces costs one append.
|
|
10
|
+
*
|
|
11
|
+
* Useful as a durable local sink (replay traces later, ship the file to
|
|
12
|
+
* a backend out of band) and as a test fixture for the pipeline without
|
|
13
|
+
* a vendor SDK.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* collector.use(fileExporter({ path: "storage/traces.jsonl" }));
|
|
17
|
+
* // on shutdown:
|
|
18
|
+
* await collector.shutdown(); // drains the buffer
|
|
19
|
+
*/
|
|
20
|
+
declare function fileExporter(options: FileExporterOptions): ExporterContract;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { fileExporter };
|
|
23
|
+
//# sourceMappingURL=file-exporter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-exporter.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts"],"mappings":";;;;;;;AAsBA;;;;;;;;AAA4E;;;;iBAA5D,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,gBAAgB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts
|
|
5
|
+
const EXPORTER_NAME = "file";
|
|
6
|
+
/**
|
|
7
|
+
* Zero-dependency {@link ExporterContract} that appends completed traces
|
|
8
|
+
* to a JSON-Lines file (one JSON record per line by default). Buffers in
|
|
9
|
+
* memory and flushes either every `flushEvery` traces or on an explicit
|
|
10
|
+
* `flush()` / `shutdown()`, so a batch of traces costs one append.
|
|
11
|
+
*
|
|
12
|
+
* Useful as a durable local sink (replay traces later, ship the file to
|
|
13
|
+
* a backend out of band) and as a test fixture for the pipeline without
|
|
14
|
+
* a vendor SDK.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* collector.use(fileExporter({ path: "storage/traces.jsonl" }));
|
|
18
|
+
* // on shutdown:
|
|
19
|
+
* await collector.shutdown(); // drains the buffer
|
|
20
|
+
*/
|
|
21
|
+
function fileExporter(options) {
|
|
22
|
+
const writer = new FileTraceWriter(options);
|
|
23
|
+
return {
|
|
24
|
+
name: EXPORTER_NAME,
|
|
25
|
+
async export(trace) {
|
|
26
|
+
await writer.add(trace);
|
|
27
|
+
},
|
|
28
|
+
async flush() {
|
|
29
|
+
await writer.flush();
|
|
30
|
+
},
|
|
31
|
+
async shutdown() {
|
|
32
|
+
await writer.flush();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Internal buffered writer for {@link fileExporter}. Owns the pending
|
|
38
|
+
* trace buffer and the directory-created guard across the exporter's
|
|
39
|
+
* lifetime; kept unexported so callers only ever see the factory.
|
|
40
|
+
*/
|
|
41
|
+
var FileTraceWriter = class {
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.buffer = [];
|
|
44
|
+
this.directoryReady = false;
|
|
45
|
+
this.path = options.path;
|
|
46
|
+
this.flushEvery = Math.max(1, options.flushEvery ?? 1);
|
|
47
|
+
this.pretty = options.pretty ?? false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Buffer one trace and flush when the buffer reaches `flushEvery`.
|
|
51
|
+
*/
|
|
52
|
+
async add(trace) {
|
|
53
|
+
this.buffer.push({
|
|
54
|
+
type: "trace",
|
|
55
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
56
|
+
trace
|
|
57
|
+
});
|
|
58
|
+
if (this.buffer.length >= this.flushEvery) await this.flush();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Serialize and append every buffered record, then clear the buffer.
|
|
62
|
+
* No-op when nothing is pending so callers can flush defensively.
|
|
63
|
+
*/
|
|
64
|
+
async flush() {
|
|
65
|
+
if (this.buffer.length === 0) return;
|
|
66
|
+
const pending = this.buffer;
|
|
67
|
+
this.buffer = [];
|
|
68
|
+
await this.ensureDirectory();
|
|
69
|
+
const payload = pending.map((record) => this.serialize(record)).join("");
|
|
70
|
+
await appendFile(this.path, payload, "utf8");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create the parent directory once, lazily, on the first write. Stores
|
|
74
|
+
* a guard so subsequent flushes skip the syscall.
|
|
75
|
+
*/
|
|
76
|
+
async ensureDirectory() {
|
|
77
|
+
if (this.directoryReady) return;
|
|
78
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
79
|
+
this.directoryReady = true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Render one record as a newline-terminated JSON string. Pretty mode
|
|
83
|
+
* indents for human reading; compact mode keeps the file valid JSON
|
|
84
|
+
* Lines (exactly one record per physical line).
|
|
85
|
+
*/
|
|
86
|
+
serialize(record) {
|
|
87
|
+
return `${this.pretty ? JSON.stringify(record, void 0, 2) : JSON.stringify(record)}\n`;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
export { fileExporter };
|
|
93
|
+
//# sourceMappingURL=file-exporter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-exporter.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts"],"sourcesContent":["import { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { ExporterContract, Trace } from \"../../contracts\";\nimport type { FileExporterOptions, TraceRecord } from \"./file-exporter.type\";\n\nconst EXPORTER_NAME = \"file\";\n\n/**\n * Zero-dependency {@link ExporterContract} that appends completed traces\n * to a JSON-Lines file (one JSON record per line by default). Buffers in\n * memory and flushes either every `flushEvery` traces or on an explicit\n * `flush()` / `shutdown()`, so a batch of traces costs one append.\n *\n * Useful as a durable local sink (replay traces later, ship the file to\n * a backend out of band) and as a test fixture for the pipeline without\n * a vendor SDK.\n *\n * @example\n * collector.use(fileExporter({ path: \"storage/traces.jsonl\" }));\n * // on shutdown:\n * await collector.shutdown(); // drains the buffer\n */\nexport function fileExporter(options: FileExporterOptions): ExporterContract {\n const writer = new FileTraceWriter(options);\n\n return {\n name: EXPORTER_NAME,\n async export(trace: Trace): Promise<void> {\n await writer.add(trace);\n },\n async flush(): Promise<void> {\n await writer.flush();\n },\n async shutdown(): Promise<void> {\n await writer.flush();\n },\n };\n}\n\n/**\n * Internal buffered writer for {@link fileExporter}. Owns the pending\n * trace buffer and the directory-created guard across the exporter's\n * lifetime; kept unexported so callers only ever see the factory.\n */\nclass FileTraceWriter {\n private readonly path: string;\n private readonly flushEvery: number;\n private readonly pretty: boolean;\n private buffer: TraceRecord[] = [];\n private directoryReady = false;\n\n public constructor(options: FileExporterOptions) {\n this.path = options.path;\n this.flushEvery = Math.max(1, options.flushEvery ?? 1);\n this.pretty = options.pretty ?? false;\n }\n\n /**\n * Buffer one trace and flush when the buffer reaches `flushEvery`.\n */\n public async add(trace: Trace): Promise<void> {\n this.buffer.push({\n type: \"trace\",\n exportedAt: new Date().toISOString(),\n trace,\n });\n\n if (this.buffer.length >= this.flushEvery) {\n await this.flush();\n }\n }\n\n /**\n * Serialize and append every buffered record, then clear the buffer.\n * No-op when nothing is pending so callers can flush defensively.\n */\n public async flush(): Promise<void> {\n if (this.buffer.length === 0) {\n return;\n }\n\n const pending = this.buffer;\n this.buffer = [];\n\n await this.ensureDirectory();\n\n const payload = pending.map((record) => this.serialize(record)).join(\"\");\n\n await appendFile(this.path, payload, \"utf8\");\n }\n\n /**\n * Create the parent directory once, lazily, on the first write. Stores\n * a guard so subsequent flushes skip the syscall.\n */\n private async ensureDirectory(): Promise<void> {\n if (this.directoryReady) {\n return;\n }\n\n await mkdir(dirname(this.path), { recursive: true });\n this.directoryReady = true;\n }\n\n /**\n * Render one record as a newline-terminated JSON string. Pretty mode\n * indents for human reading; compact mode keeps the file valid JSON\n * Lines (exactly one record per physical line).\n */\n private serialize(record: TraceRecord): string {\n const json = this.pretty\n ? JSON.stringify(record, undefined, 2)\n : JSON.stringify(record);\n\n return `${json}\\n`;\n }\n}\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;;;;;;;;;;;;;;;;AAiBtB,SAAgB,aAAa,SAAgD;CAC3E,MAAM,SAAS,IAAI,gBAAgB,OAAO;CAE1C,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAA6B;GACxC,MAAM,OAAO,IAAI,KAAK;EACxB;EACA,MAAM,QAAuB;GAC3B,MAAM,OAAO,MAAM;EACrB;EACA,MAAM,WAA0B;GAC9B,MAAM,OAAO,MAAM;EACrB;CACF;AACF;;;;;;AAOA,IAAM,kBAAN,MAAsB;CAOpB,AAAO,YAAY,SAA8B;gBAHjB,CAAC;wBACR;EAGvB,KAAK,OAAO,QAAQ;EACpB,KAAK,aAAa,KAAK,IAAI,GAAG,QAAQ,cAAc,CAAC;EACrD,KAAK,SAAS,QAAQ,UAAU;CAClC;;;;CAKA,MAAa,IAAI,OAA6B;EAC5C,KAAK,OAAO,KAAK;GACf,MAAM;GACN,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY;GACnC;EACF,CAAC;EAED,IAAI,KAAK,OAAO,UAAU,KAAK,YAC7B,MAAM,KAAK,MAAM;CAErB;;;;;CAMA,MAAa,QAAuB;EAClC,IAAI,KAAK,OAAO,WAAW,GACzB;EAGF,MAAM,UAAU,KAAK;EACrB,KAAK,SAAS,CAAC;EAEf,MAAM,KAAK,gBAAgB;EAE3B,MAAM,UAAU,QAAQ,KAAK,WAAW,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE;EAEvE,MAAM,WAAW,KAAK,MAAM,SAAS,MAAM;CAC7C;;;;;CAMA,MAAc,kBAAiC;EAC7C,IAAI,KAAK,gBACP;EAGF,MAAM,MAAM,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EACnD,KAAK,iBAAiB;CACxB;;;;;;CAOA,AAAQ,UAAU,QAA6B;EAK7C,OAAO,GAJM,KAAK,SACd,KAAK,UAAU,QAAQ,QAAW,CAAC,IACnC,KAAK,UAAU,MAAM,EAEV;CACjB;AACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Trace } from "../../contracts/trace.type.mjs";
|
|
2
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.type.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* One JSON-Lines record the {@link fileExporter} appends per trace. A
|
|
5
|
+
* thin envelope around {@link Trace} carrying the wire-format `type` and
|
|
6
|
+
* the time the line was written, so a downstream reader can tell records
|
|
7
|
+
* apart and order them without re-deriving anything from the payload.
|
|
8
|
+
*/
|
|
9
|
+
type TraceRecord = {
|
|
10
|
+
/** Discriminator for the record format — always `"trace"` today. */type: "trace"; /** ISO-8601 timestamp the exporter wrote this record. */
|
|
11
|
+
exportedAt: string; /** The completed trace, verbatim. */
|
|
12
|
+
trace: Trace;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Options for {@link fileExporter}.
|
|
16
|
+
*/
|
|
17
|
+
type FileExporterOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* Absolute or cwd-relative path of the JSON-Lines file traces are
|
|
20
|
+
* appended to. The parent directory is created on first write if
|
|
21
|
+
* missing. Required.
|
|
22
|
+
*/
|
|
23
|
+
path: string;
|
|
24
|
+
/**
|
|
25
|
+
* Flush the in-memory buffer to disk once it reaches this many traces.
|
|
26
|
+
* Lower values mean more frequent, smaller writes; higher values batch
|
|
27
|
+
* for throughput. Defaults to `1` (write every trace immediately).
|
|
28
|
+
*/
|
|
29
|
+
flushEvery?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Pretty-print each record across multiple lines instead of one
|
|
32
|
+
* compact JSON line. Defaults to `false` — compact keeps the file
|
|
33
|
+
* valid JSON Lines (one record per line).
|
|
34
|
+
*/
|
|
35
|
+
pretty?: boolean;
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { FileExporterOptions, TraceRecord };
|
|
39
|
+
//# sourceMappingURL=file-exporter.type.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-exporter.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.type.ts"],"mappings":";;;;;AAQA;;;KAAY,WAAA;EAEV,oEAAA,IAAA,WAIA;EAFA,UAAA,UAEY;EAAZ,KAAA,EAAO,KAAK;AAAA;;;;KAMF,mBAAA;EAYV;;;AAMM;;EAZN,IAAA;;;;;;EAMA,UAAA;;;;;;EAMA,MAAA;AAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ConsoleExporterOptions, ConsoleLike } from "./console/console-exporter.type.mjs";
|
|
2
|
+
import { consoleExporter } from "./console/console-exporter.mjs";
|
|
3
|
+
import { formatSpanLine } from "./console/format-span-line.mjs";
|
|
4
|
+
import { FileExporterOptions, TraceRecord } from "./file/file-exporter.type.mjs";
|
|
5
|
+
import { fileExporter } from "./file/file-exporter.mjs";
|
|
6
|
+
import { LangfuseClientLike, LangfuseExporterOptions, LangfuseObservationBody, LangfuseObservationEndBody, LangfuseObservationLevel, LangfuseObservationLike, LangfuseTraceBody, LangfuseTraceLike, LangfuseUsageBody } from "./langfuse/langfuse-exporter.type.mjs";
|
|
7
|
+
import { langfuseExporter } from "./langfuse/langfuse-exporter.mjs";
|
|
8
|
+
import { OtelExporterOptions } from "./otel/otel-exporter.type.mjs";
|
|
9
|
+
import { otelExporter } from "./otel/otel-exporter.mjs";
|
|
10
|
+
import { AttributeValue, GEN_AI_ATTRIBUTES, WARLOCK_ATTRIBUTES, toGenAiAttributes } from "./utils/gen-ai-attributes.mjs";
|
|
11
|
+
import { totalCostUsd } from "./utils/total-cost.mjs";
|
|
12
|
+
import { walkSpans } from "./utils/walk-spans.mjs";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { walkSpans } from "./utils/walk-spans.mjs";
|
|
2
|
+
import { totalCostUsd } from "./utils/total-cost.mjs";
|
|
3
|
+
import { GEN_AI_ATTRIBUTES, WARLOCK_ATTRIBUTES, toGenAiAttributes } from "./utils/gen-ai-attributes.mjs";
|
|
4
|
+
import { formatSpanLine } from "./console/format-span-line.mjs";
|
|
5
|
+
import { consoleExporter } from "./console/console-exporter.mjs";
|
|
6
|
+
import { fileExporter } from "./file/file-exporter.mjs";
|
|
7
|
+
import { langfuseExporter } from "./langfuse/langfuse-exporter.mjs";
|
|
8
|
+
import "./langfuse/index.mjs";
|
|
9
|
+
import { otelExporter } from "./otel/otel-exporter.mjs";
|
|
10
|
+
import "./otel/index.mjs";
|
|
11
|
+
|
|
12
|
+
export { };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { LangfuseClientLike, LangfuseExporterOptions, LangfuseObservationBody, LangfuseObservationEndBody, LangfuseObservationLevel, LangfuseObservationLike, LangfuseTraceBody, LangfuseTraceLike, LangfuseUsageBody } from "./langfuse-exporter.type.mjs";
|
|
2
|
+
import { langfuseExporter } from "./langfuse-exporter.mjs";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ExporterContract } from "../../contracts/exporter.contract.mjs";
|
|
2
|
+
import { LangfuseExporterOptions } from "./langfuse-exporter.type.mjs";
|
|
3
|
+
|
|
4
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* {@link ExporterContract} that maps Panoptic traces onto Langfuse
|
|
7
|
+
* traces and observations. Lazily imports `langfuse` so it stays an
|
|
8
|
+
* OPTIONAL peer — importing this module never forces the SDK to be
|
|
9
|
+
* installed, and a missing SDK surfaces as a curated "install this"
|
|
10
|
+
* error when the exporter first needs to build a client.
|
|
11
|
+
*
|
|
12
|
+
* The root {@link TraceSpan} becomes a Langfuse trace; each descendant
|
|
13
|
+
* becomes a nested observation — a `generation` when it produced tokens
|
|
14
|
+
* (LLM-backed agents, supervisors), otherwise a plain `span` (tools,
|
|
15
|
+
* callbacks). Timing, status, version, and rolled-up `usage` are mapped
|
|
16
|
+
* 1:1; the rest of the GenAI attributes ride along as observation
|
|
17
|
+
* metadata.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* collector.use(langfuseExporter({ publicKey: "pk-...", secretKey: "sk-..." }));
|
|
21
|
+
* // or reuse an existing client:
|
|
22
|
+
* collector.use(langfuseExporter({ client: myLangfuse }));
|
|
23
|
+
*/
|
|
24
|
+
declare function langfuseExporter(options: LangfuseExporterOptions): ExporterContract;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { langfuseExporter };
|
|
27
|
+
//# sourceMappingURL=langfuse-exporter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuse-exporter.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts"],"mappings":";;;;;;;AA+EA;;;;;;;;AAAoF;;;;;;;;iBAApE,gBAAA,CAAiB,OAAA,EAAS,uBAAA,GAA0B,gBAAgB"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { toGenAiAttributes } from "../utils/gen-ai-attributes.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts
|
|
4
|
+
const EXPORTER_NAME = "langfuse";
|
|
5
|
+
let LangfuseSdk;
|
|
6
|
+
let isModuleExists = null;
|
|
7
|
+
let loadingPromise;
|
|
8
|
+
const LANGFUSE_INSTALL_INSTRUCTIONS = `
|
|
9
|
+
The Panoptic Langfuse exporter requires the langfuse package.
|
|
10
|
+
Install it with:
|
|
11
|
+
|
|
12
|
+
npm install langfuse
|
|
13
|
+
|
|
14
|
+
Or with your preferred package manager:
|
|
15
|
+
|
|
16
|
+
pnpm add langfuse
|
|
17
|
+
yarn add langfuse
|
|
18
|
+
`.trim();
|
|
19
|
+
/**
|
|
20
|
+
* Settle the lazy import of `langfuse` once, concurrency-safe. Only
|
|
21
|
+
* needed when the caller did not pass a ready `client`. A bare `catch`
|
|
22
|
+
* flips the flag to `false`; the curated install string surfaces at use
|
|
23
|
+
* time, never a raw module-resolution stack trace.
|
|
24
|
+
*/
|
|
25
|
+
function loadLangfuse() {
|
|
26
|
+
if (isModuleExists !== null) return Promise.resolve();
|
|
27
|
+
if (loadingPromise) return loadingPromise;
|
|
28
|
+
loadingPromise = (async () => {
|
|
29
|
+
try {
|
|
30
|
+
LangfuseSdk = await import("langfuse");
|
|
31
|
+
isModuleExists = true;
|
|
32
|
+
} catch {
|
|
33
|
+
isModuleExists = false;
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
return loadingPromise;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* {@link ExporterContract} that maps Panoptic traces onto Langfuse
|
|
40
|
+
* traces and observations. Lazily imports `langfuse` so it stays an
|
|
41
|
+
* OPTIONAL peer — importing this module never forces the SDK to be
|
|
42
|
+
* installed, and a missing SDK surfaces as a curated "install this"
|
|
43
|
+
* error when the exporter first needs to build a client.
|
|
44
|
+
*
|
|
45
|
+
* The root {@link TraceSpan} becomes a Langfuse trace; each descendant
|
|
46
|
+
* becomes a nested observation — a `generation` when it produced tokens
|
|
47
|
+
* (LLM-backed agents, supervisors), otherwise a plain `span` (tools,
|
|
48
|
+
* callbacks). Timing, status, version, and rolled-up `usage` are mapped
|
|
49
|
+
* 1:1; the rest of the GenAI attributes ride along as observation
|
|
50
|
+
* metadata.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* collector.use(langfuseExporter({ publicKey: "pk-...", secretKey: "sk-..." }));
|
|
54
|
+
* // or reuse an existing client:
|
|
55
|
+
* collector.use(langfuseExporter({ client: myLangfuse }));
|
|
56
|
+
*/
|
|
57
|
+
function langfuseExporter(options) {
|
|
58
|
+
let client = options.client;
|
|
59
|
+
if (!client) loadLangfuse();
|
|
60
|
+
const resolveClient = async () => {
|
|
61
|
+
if (client) return client;
|
|
62
|
+
await loadLangfuse();
|
|
63
|
+
if (!isModuleExists) throw new Error(LANGFUSE_INSTALL_INSTRUCTIONS);
|
|
64
|
+
client = new LangfuseSdk.Langfuse({
|
|
65
|
+
publicKey: options.publicKey,
|
|
66
|
+
secretKey: options.secretKey,
|
|
67
|
+
baseUrl: options.baseUrl
|
|
68
|
+
});
|
|
69
|
+
return client;
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
name: EXPORTER_NAME,
|
|
73
|
+
async export(trace) {
|
|
74
|
+
emitTrace(await resolveClient(), trace);
|
|
75
|
+
},
|
|
76
|
+
async flush() {
|
|
77
|
+
if (!client) return;
|
|
78
|
+
await client.flushAsync();
|
|
79
|
+
},
|
|
80
|
+
async shutdown() {
|
|
81
|
+
if (!client) return;
|
|
82
|
+
await client.shutdownAsync();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create the Langfuse trace from the root span, then recurse the
|
|
88
|
+
* children into nested observations.
|
|
89
|
+
*/
|
|
90
|
+
function emitTrace(client, trace) {
|
|
91
|
+
const root = trace.root;
|
|
92
|
+
const langfuseTrace = client.trace({
|
|
93
|
+
id: root.traceId,
|
|
94
|
+
name: root.name,
|
|
95
|
+
sessionId: trace.sessionId,
|
|
96
|
+
version: root.version,
|
|
97
|
+
timestamp: new Date(root.startedAt),
|
|
98
|
+
metadata: toGenAiAttributes(root)
|
|
99
|
+
});
|
|
100
|
+
for (const child of root.children) emitObservation(langfuseTrace, child);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Map one {@link TraceSpan} onto a Langfuse observation under `parent`,
|
|
104
|
+
* then recurse its children. Token-producing spans become
|
|
105
|
+
* `generation`s; everything else becomes a plain `span`.
|
|
106
|
+
*/
|
|
107
|
+
function emitObservation(parent, span) {
|
|
108
|
+
const body = {
|
|
109
|
+
id: span.spanId,
|
|
110
|
+
name: span.name,
|
|
111
|
+
startTime: new Date(span.startedAt),
|
|
112
|
+
endTime: new Date(span.endedAt),
|
|
113
|
+
level: toLevel(span),
|
|
114
|
+
statusMessage: span.error?.message,
|
|
115
|
+
version: span.version,
|
|
116
|
+
metadata: toGenAiAttributes(span)
|
|
117
|
+
};
|
|
118
|
+
let observation;
|
|
119
|
+
if (producedTokens(span)) {
|
|
120
|
+
body.usage = {
|
|
121
|
+
input: span.usage.input,
|
|
122
|
+
output: span.usage.output,
|
|
123
|
+
total: span.usage.total,
|
|
124
|
+
unit: "TOKENS"
|
|
125
|
+
};
|
|
126
|
+
observation = parent.generation(body);
|
|
127
|
+
} else observation = parent.span(body);
|
|
128
|
+
for (const child of span.children) emitObservation(observation, child);
|
|
129
|
+
observation.end({ endTime: body.endTime });
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A span counts as an LLM `generation` when it metered any tokens. Pure
|
|
133
|
+
* tools/callbacks contribute zero own-cost and map to plain spans.
|
|
134
|
+
*
|
|
135
|
+
* **Known limitation.** `TraceSpan.usage` is the ROLLED-UP usage (this
|
|
136
|
+
* node's own cost plus the sum of its children), not own-usage — the
|
|
137
|
+
* core `BaseReport` exposes no separate own-usage field, so a composite
|
|
138
|
+
* node (e.g. an agent that itself made no model call but whose tool
|
|
139
|
+
* children did) is classified as a `generation` on its children's
|
|
140
|
+
* tokens. We cannot classify on own-usage until the report shape carries
|
|
141
|
+
* it; the rolled-up `usage` block emitted on such a generation therefore
|
|
142
|
+
* double-counts tokens already attributed to descendant observations.
|
|
143
|
+
*/
|
|
144
|
+
function producedTokens(span) {
|
|
145
|
+
return span.usage.total > 0;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Map the Panoptic span status onto a Langfuse observation level —
|
|
149
|
+
* failed/cancelled spans surface as `ERROR`, everything else `DEFAULT`.
|
|
150
|
+
*/
|
|
151
|
+
function toLevel(span) {
|
|
152
|
+
if (span.status === "failed" || span.status === "cancelled") return "ERROR";
|
|
153
|
+
return "DEFAULT";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
export { langfuseExporter };
|
|
158
|
+
//# sourceMappingURL=langfuse-exporter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuse-exporter.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts"],"sourcesContent":["import type { ExporterContract, Trace, TraceSpan } from \"../../contracts\";\nimport { 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"],"mappings":";;;AAWA,MAAM,gBAAgB;AAMtB,IAAI;AACJ,IAAI,iBAAiC;AACrC,IAAI;AAEJ,MAAM,gCAAgC;;;;;;;;;;EAUpC,KAAK;;;;;;;AAQP,SAAS,eAA8B;CACrC,IAAI,mBAAmB,MACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GACF,cAAc,MAAM,OAAO;GAC3B,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,EAAC,CAAE;CAEH,OAAO;AACT;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,iBAAiB,SAAoD;CACnF,IAAI,SAAyC,QAAQ;CAErD,IAAI,CAAC,QACH,aAAa;CAGf,MAAM,gBAAgB,YAAyC;EAC7D,IAAI,QACF,OAAO;EAGT,MAAM,aAAa;EAEnB,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,6BAA6B;EAG/C,SAAS,IAAI,YAAY,SAAS;GAChC,WAAW,QAAQ;GACnB,WAAW,QAAQ;GACnB,SAAS,QAAQ;EACnB,CAAC;EAED,OAAO;CACT;CAEA,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAA6B;GAExC,UAAU,MADiB,cAAc,GACjB,KAAK;EAC/B;EACA,MAAM,QAAuB;GAC3B,IAAI,CAAC,QACH;GAGF,MAAM,OAAO,WAAW;EAC1B;EACA,MAAM,WAA0B;GAC9B,IAAI,CAAC,QACH;GAGF,MAAM,OAAO,cAAc;EAC7B;CACF;AACF;;;;;AAMA,SAAS,UAAU,QAA4B,OAAoB;CACjE,MAAM,OAAO,MAAM;CAEnB,MAAM,gBAAgB,OAAO,MAAM;EACjC,IAAI,KAAK;EACT,MAAM,KAAK;EACX,WAAW,MAAM;EACjB,SAAS,KAAK;EACd,WAAW,IAAI,KAAK,KAAK,SAAS;EAClC,UAAU,kBAAkB,IAAI;CAClC,CAAC;CAED,KAAK,MAAM,SAAS,KAAK,UACvB,gBAAgB,eAAe,KAAK;AAExC;;;;;;AAOA,SAAS,gBACP,QACA,MACM;CACN,MAAM,OAAgC;EACpC,IAAI,KAAK;EACT,MAAM,KAAK;EACX,WAAW,IAAI,KAAK,KAAK,SAAS;EAClC,SAAS,IAAI,KAAK,KAAK,OAAO;EAC9B,OAAO,QAAQ,IAAI;EACnB,eAAe,KAAK,OAAO;EAC3B,SAAS,KAAK;EACd,UAAU,kBAAkB,IAAI;CAClC;CAEA,IAAI;CAEJ,IAAI,eAAe,IAAI,GAAG;EACxB,KAAK,QAAQ;GACX,OAAO,KAAK,MAAM;GAClB,QAAQ,KAAK,MAAM;GACnB,OAAO,KAAK,MAAM;GAClB,MAAM;EACR;EACA,cAAc,OAAO,WAAW,IAAI;CACtC,OACE,cAAc,OAAO,KAAK,IAAI;CAGhC,KAAK,MAAM,SAAS,KAAK,UACvB,gBAAgB,aAAa,KAAK;CAQpC,YAAY,IAAI,EAAE,SAAS,KAAK,QAAQ,CAAC;AAC3C;;;;;;;;;;;;;;AAeA,SAAS,eAAe,MAA0B;CAChD,OAAO,KAAK,MAAM,QAAQ;AAC5B;;;;;AAMA,SAAS,QAAQ,MAA2C;CAC1D,IAAI,KAAK,WAAW,YAAY,KAAK,WAAW,aAC9C,OAAO;CAGT,OAAO;AACT"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.type.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural view of the parts of the `langfuse` SDK the
|
|
4
|
+
* exporter actually calls. Declared locally (rather than importing the
|
|
5
|
+
* SDK's own types) so this module type-checks even when `langfuse` is
|
|
6
|
+
* not installed — it is an OPTIONAL peer, lazily imported at runtime.
|
|
7
|
+
* The shape tracks Langfuse SDK v3: a client creates a trace, a trace
|
|
8
|
+
* creates nested `span` / `generation` observations, and the client is
|
|
9
|
+
* drained with `flushAsync` / `shutdownAsync`.
|
|
10
|
+
*/
|
|
11
|
+
type LangfuseClientLike = {
|
|
12
|
+
trace(body: LangfuseTraceBody): LangfuseTraceLike;
|
|
13
|
+
flushAsync(): Promise<unknown>;
|
|
14
|
+
shutdownAsync(): Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A Langfuse trace handle — creates nested observations and carries the
|
|
18
|
+
* trace-level metadata Panoptic maps the root span onto.
|
|
19
|
+
*/
|
|
20
|
+
type LangfuseTraceLike = {
|
|
21
|
+
span(body: LangfuseObservationBody): LangfuseObservationLike;
|
|
22
|
+
generation(body: LangfuseObservationBody): LangfuseObservationLike;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* A Langfuse observation handle (span or generation) — nests further
|
|
26
|
+
* children and is closed with `end()`.
|
|
27
|
+
*/
|
|
28
|
+
type LangfuseObservationLike = {
|
|
29
|
+
span(body: LangfuseObservationBody): LangfuseObservationLike;
|
|
30
|
+
generation(body: LangfuseObservationBody): LangfuseObservationLike;
|
|
31
|
+
end(body?: LangfuseObservationEndBody): unknown;
|
|
32
|
+
};
|
|
33
|
+
/** Subset of the Langfuse trace-creation body Panoptic populates. */
|
|
34
|
+
type LangfuseTraceBody = {
|
|
35
|
+
id?: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
version?: string;
|
|
39
|
+
timestamp?: Date;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
};
|
|
42
|
+
/** Subset of the Langfuse observation-creation body Panoptic populates. */
|
|
43
|
+
type LangfuseObservationBody = {
|
|
44
|
+
id?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
startTime?: Date;
|
|
47
|
+
endTime?: Date;
|
|
48
|
+
level?: LangfuseObservationLevel;
|
|
49
|
+
statusMessage?: string;
|
|
50
|
+
version?: string;
|
|
51
|
+
usage?: LangfuseUsageBody;
|
|
52
|
+
metadata?: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
/** Body accepted by `observation.end()`. */
|
|
55
|
+
type LangfuseObservationEndBody = {
|
|
56
|
+
endTime?: Date;
|
|
57
|
+
level?: LangfuseObservationLevel;
|
|
58
|
+
statusMessage?: string;
|
|
59
|
+
};
|
|
60
|
+
/** Langfuse usage block (`input`/`output`/`total` token counts). */
|
|
61
|
+
type LangfuseUsageBody = {
|
|
62
|
+
input?: number;
|
|
63
|
+
output?: number;
|
|
64
|
+
total?: number;
|
|
65
|
+
unit?: "TOKENS";
|
|
66
|
+
};
|
|
67
|
+
/** Langfuse observation severity levels. */
|
|
68
|
+
type LangfuseObservationLevel = "DEFAULT" | "DEBUG" | "WARNING" | "ERROR";
|
|
69
|
+
/**
|
|
70
|
+
* Options for {@link langfuseExporter}.
|
|
71
|
+
*
|
|
72
|
+
* Either pass an already-constructed Langfuse `client`, or pass the
|
|
73
|
+
* credentials and let the exporter construct one lazily (so `langfuse`
|
|
74
|
+
* stays an optional peer that is only imported when this exporter is
|
|
75
|
+
* actually used).
|
|
76
|
+
*/
|
|
77
|
+
type LangfuseExporterOptions = {
|
|
78
|
+
/**
|
|
79
|
+
* A pre-built Langfuse client (any object matching
|
|
80
|
+
* {@link LangfuseClientLike}, including a real `Langfuse` instance).
|
|
81
|
+
* When supplied, the exporter never imports the SDK itself.
|
|
82
|
+
*/
|
|
83
|
+
client?: LangfuseClientLike; /** Langfuse public key — used to construct a client when `client` is omitted. */
|
|
84
|
+
publicKey?: string; /** Langfuse secret key — used to construct a client when `client` is omitted. */
|
|
85
|
+
secretKey?: string; /** Langfuse host base URL. Optional; defaults to the SDK's own default. */
|
|
86
|
+
baseUrl?: string;
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
export { LangfuseClientLike, LangfuseExporterOptions, LangfuseObservationBody, LangfuseObservationEndBody, LangfuseObservationLevel, LangfuseObservationLike, LangfuseTraceBody, LangfuseTraceLike, LangfuseUsageBody };
|
|
90
|
+
//# sourceMappingURL=langfuse-exporter.type.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuse-exporter.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.type.ts"],"mappings":";;AASA;;;;;;;;KAAY,kBAAA;EACV,KAAA,CAAM,IAAA,EAAM,iBAAA,GAAoB,iBAAA;EAChC,UAAA,IAAc,OAAA;EACd,aAAA,IAAiB,OAAA;AAAA;;;;;KAOP,iBAAA;EACV,IAAA,CAAK,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EACrC,UAAA,CAAW,IAAA,EAAM,uBAAA,GAA0B,uBAAA;AAAA;;;;;KAOjC,uBAAA;EACV,IAAA,CAAK,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EACrC,UAAA,CAAW,IAAA,EAAM,uBAAA,GAA0B,uBAAA;EAC3C,GAAA,CAAI,IAAA,GAAO,0BAAA;AAAA;;KAID,iBAAA;EACV,EAAA;EACA,IAAA;EACA,SAAA;EACA,OAAA;EACA,SAAA,GAAY,IAAA;EACZ,QAAA,GAAW,MAAM;AAAA;;KAIP,uBAAA;EACV,EAAA;EACA,IAAA;EACA,SAAA,GAAY,IAAA;EACZ,OAAA,GAAU,IAAA;EACV,KAAA,GAAQ,wBAAA;EACR,aAAA;EACA,OAAA;EACA,KAAA,GAAQ,iBAAA;EACR,QAAA,GAAW,MAAA;AAAA;;KAID,0BAAA;EACV,OAAA,GAAU,IAAA;EACV,KAAA,GAAQ,wBAAwB;EAChC,aAAA;AAAA;;KAIU,iBAAA;EACV,KAAA;EACA,MAAA;EACA,KAAA;EACA,IAAA;AAAA;;KAIU,wBAAA;;;;;;;;;KAUA,uBAAA;EAtCA;;;;;EA4CV,MAAA,GAAS,kBAAkB,EApCnB;EAsCR,SAAA,WArCiB;EAuCjB,SAAA,WA/CA;EAiDA,OAAA;AAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ExporterContract } from "../../contracts/exporter.contract.mjs";
|
|
2
|
+
import { OtelExporterOptions } from "./otel-exporter.type.mjs";
|
|
3
|
+
|
|
4
|
+
//#region ../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* {@link ExporterContract} that maps Panoptic traces onto OpenTelemetry
|
|
7
|
+
* spans following the GenAI semantic conventions (`gen_ai.*`
|
|
8
|
+
* attributes). Lazily imports `@opentelemetry/api` so it stays an
|
|
9
|
+
* OPTIONAL peer — importing this module never forces the SDK to be
|
|
10
|
+
* installed, and a missing SDK surfaces as a curated "install this"
|
|
11
|
+
* error on first `export`, not a boot-time stack trace.
|
|
12
|
+
*
|
|
13
|
+
* The exporter emits onto a `Tracer` you supply (or fetches one from the
|
|
14
|
+
* globally registered provider). It never configures the SDK — wiring a
|
|
15
|
+
* `TracerProvider`, processors, and span exporters is the host app's
|
|
16
|
+
* job, exactly as with any other OTel instrumentation.
|
|
17
|
+
*
|
|
18
|
+
* Each {@link TraceSpan} becomes one OTel span with the source span's
|
|
19
|
+
* start/end times and parent relationship reconstructed, so the emitted
|
|
20
|
+
* tree matches the original execution tree.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // app already set up @opentelemetry/sdk-trace-base + a provider
|
|
24
|
+
* collector.use(otelExporter({ tracerName: "my-app", system: "openai" }));
|
|
25
|
+
*/
|
|
26
|
+
declare function otelExporter(options?: OtelExporterOptions): ExporterContract;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { otelExporter };
|
|
29
|
+
//# sourceMappingURL=otel-exporter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel-exporter.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.ts"],"mappings":";;;;;;;AAiFA;;;;;;;;AAAiF;;;;;;;;;;iBAAjE,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,gBAAgB"}
|