@m_keshav/agenttrace 0.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/dist/cjs/client.d.ts +11 -0
- package/dist/cjs/client.js +129 -0
- package/dist/cjs/contracts.d.ts +36 -0
- package/dist/cjs/contracts.js +95 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +11 -0
- package/dist/cjs/trace.d.ts +37 -0
- package/dist/cjs/trace.js +134 -0
- package/dist/esm/client.d.ts +11 -0
- package/dist/esm/client.js +125 -0
- package/dist/esm/contracts.d.ts +36 -0
- package/dist/esm/contracts.js +89 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/trace.d.ts +37 -0
- package/dist/esm/trace.js +129 -0
- package/package.json +31 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class IngestClient {
|
|
2
|
+
private readonly apiUrl;
|
|
3
|
+
private readonly apiKey;
|
|
4
|
+
private readonly timeoutMs;
|
|
5
|
+
private readonly otlpExporterUrl;
|
|
6
|
+
constructor(apiUrl: string, apiKey?: string, timeoutMs?: number, otlpExporterUrl?: string);
|
|
7
|
+
static fromEnv(): IngestClient | null;
|
|
8
|
+
ingest(payload: Record<string, unknown>): Promise<void>;
|
|
9
|
+
private _ingestCustom;
|
|
10
|
+
private _ingestOtlp;
|
|
11
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IngestClient = void 0;
|
|
4
|
+
function _dtToNs(dt) {
|
|
5
|
+
return String(Math.floor(dt.getTime() * 1_000_000));
|
|
6
|
+
}
|
|
7
|
+
function _uuidToHex(u) {
|
|
8
|
+
return u.replace(/-/g, "");
|
|
9
|
+
}
|
|
10
|
+
function _buildOtlpEnvelope(runs, spans, serviceName = "agenttrace") {
|
|
11
|
+
const runMap = new Map(runs.map((r) => [r["run_id"], r]));
|
|
12
|
+
const traceSpans = new Map();
|
|
13
|
+
for (const span of spans) {
|
|
14
|
+
const traceId = _uuidToHex(span["run_id"]);
|
|
15
|
+
if (!traceSpans.has(traceId)) {
|
|
16
|
+
traceSpans.set(traceId, []);
|
|
17
|
+
}
|
|
18
|
+
const attrs = [
|
|
19
|
+
{ key: "span.type", value: { stringValue: span["span_type"] } },
|
|
20
|
+
{
|
|
21
|
+
key: "input.value",
|
|
22
|
+
value: { stringValue: span["input_json"] ? JSON.stringify(span["input_json"]) : "" },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "output.value",
|
|
26
|
+
value: { stringValue: span["output_json"] ? JSON.stringify(span["output_json"]) : "" },
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
const meta = span["metadata_json"];
|
|
30
|
+
if (meta) {
|
|
31
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
32
|
+
attrs.push({ key: k, value: { stringValue: String(v) } });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (span["error"]) {
|
|
36
|
+
attrs.push({ key: "error.message", value: { stringValue: span["error"] } });
|
|
37
|
+
}
|
|
38
|
+
traceSpans.get(traceId).push({
|
|
39
|
+
traceId,
|
|
40
|
+
spanId: _uuidToHex(span["span_id"]),
|
|
41
|
+
parentSpanId: span["parent_span_id"] ? _uuidToHex(span["parent_span_id"]) : "",
|
|
42
|
+
name: span["name"],
|
|
43
|
+
startTimeUnixNano: _dtToNs(new Date(span["start_time"])),
|
|
44
|
+
endTimeUnixNano: _dtToNs(new Date(span["end_time"])),
|
|
45
|
+
attributes: attrs,
|
|
46
|
+
status: span["error"] ? { code: 2 } : {},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const resourceSpans = [];
|
|
50
|
+
for (const [traceId, spanList] of traceSpans) {
|
|
51
|
+
const run = runMap.get(traceId);
|
|
52
|
+
resourceSpans.push({
|
|
53
|
+
resource: {
|
|
54
|
+
attributes: [{ key: "service.name", value: { stringValue: serviceName } }],
|
|
55
|
+
},
|
|
56
|
+
scopeSpans: [{ spans: spanList }],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return { resourceSpans };
|
|
60
|
+
}
|
|
61
|
+
class IngestClient {
|
|
62
|
+
apiUrl;
|
|
63
|
+
apiKey;
|
|
64
|
+
timeoutMs;
|
|
65
|
+
otlpExporterUrl;
|
|
66
|
+
constructor(apiUrl, apiKey, timeoutMs = 5000, otlpExporterUrl) {
|
|
67
|
+
this.apiUrl = apiUrl.replace(/\/+$/, "");
|
|
68
|
+
this.apiKey = apiKey;
|
|
69
|
+
this.timeoutMs = timeoutMs;
|
|
70
|
+
this.otlpExporterUrl = otlpExporterUrl;
|
|
71
|
+
}
|
|
72
|
+
static fromEnv() {
|
|
73
|
+
const apiUrl = process.env.AGENTTRACE_API_URL;
|
|
74
|
+
if (!apiUrl) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const apiKey = process.env.AGENTTRACE_API_KEY;
|
|
78
|
+
const otlpExporterUrl = process.env.AGENTTRACE_OTLP_EXPORTER_URL;
|
|
79
|
+
return new IngestClient(apiUrl, apiKey || undefined, 5000, otlpExporterUrl || undefined);
|
|
80
|
+
}
|
|
81
|
+
async ingest(payload) {
|
|
82
|
+
if (this.otlpExporterUrl) {
|
|
83
|
+
await this._ingestOtlp(payload);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
await this._ingestCustom(payload);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async _ingestCustom(payload) {
|
|
90
|
+
const url = `${this.apiUrl}/v1/ingest`;
|
|
91
|
+
const headers = {
|
|
92
|
+
"Content-Type": "application/json",
|
|
93
|
+
};
|
|
94
|
+
if (this.apiKey) {
|
|
95
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
96
|
+
}
|
|
97
|
+
const response = await fetch(url, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers,
|
|
100
|
+
body: JSON.stringify(payload),
|
|
101
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw new Error(`Ingest failed: ${response.status} ${response.statusText}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async _ingestOtlp(payload) {
|
|
108
|
+
const runs = payload["runs"] ?? [];
|
|
109
|
+
const spans = payload["spans"] ?? [];
|
|
110
|
+
const serviceName = runs[0]?.["name"] ?? "agenttrace";
|
|
111
|
+
const otlpPayload = _buildOtlpEnvelope(runs, spans, serviceName);
|
|
112
|
+
const headers = {
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
};
|
|
115
|
+
if (this.apiKey) {
|
|
116
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
117
|
+
}
|
|
118
|
+
const response = await fetch(this.otlpExporterUrl, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers,
|
|
121
|
+
body: JSON.stringify(otlpPayload),
|
|
122
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
throw new Error(`OTLP ingest failed: ${response.status} ${response.statusText}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.IngestClient = IngestClient;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type SpanType = "llm" | "tool" | "retrieval" | "chain" | "agent";
|
|
2
|
+
export interface Run {
|
|
3
|
+
runId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
createdAt: Date;
|
|
6
|
+
metadataJson: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface Span {
|
|
9
|
+
spanId: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
parentSpanId: string | null;
|
|
12
|
+
spanType: SpanType;
|
|
13
|
+
name: string;
|
|
14
|
+
startTime: Date;
|
|
15
|
+
endTime: Date;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
inputJson: unknown;
|
|
18
|
+
outputJson: unknown;
|
|
19
|
+
error: string | null;
|
|
20
|
+
metadataJson: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export declare function validateRun(run: Run): void;
|
|
23
|
+
export declare function validateSpan(span: Span): void;
|
|
24
|
+
/**
|
|
25
|
+
* Builds a payload for streaming use: skips cross-reference validation so
|
|
26
|
+
* individual spans can be sent without their run or parent spans in the same
|
|
27
|
+
* payload (they were already sent in earlier calls).
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildStreamingPayload(run: Run | null, spans: Span[]): {
|
|
30
|
+
runs: Record<string, unknown>[];
|
|
31
|
+
spans: Record<string, unknown>[];
|
|
32
|
+
};
|
|
33
|
+
export declare function buildIngestPayload(runs: Run[], spans: Span[]): {
|
|
34
|
+
runs: Record<string, unknown>[];
|
|
35
|
+
spans: Record<string, unknown>[];
|
|
36
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateRun = validateRun;
|
|
4
|
+
exports.validateSpan = validateSpan;
|
|
5
|
+
exports.buildStreamingPayload = buildStreamingPayload;
|
|
6
|
+
exports.buildIngestPayload = buildIngestPayload;
|
|
7
|
+
function ensureJsonSerializable(value, fieldName) {
|
|
8
|
+
try {
|
|
9
|
+
JSON.stringify(value);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new Error(`${fieldName} must be JSON serializable`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function validateRun(run) {
|
|
16
|
+
if (!run.name) {
|
|
17
|
+
throw new Error("run.name must be non-empty");
|
|
18
|
+
}
|
|
19
|
+
ensureJsonSerializable(run.metadataJson, "run.metadataJson");
|
|
20
|
+
}
|
|
21
|
+
function validateSpan(span) {
|
|
22
|
+
if (!span.name) {
|
|
23
|
+
throw new Error("span.name must be non-empty");
|
|
24
|
+
}
|
|
25
|
+
if (span.durationMs < 0) {
|
|
26
|
+
throw new Error("span.durationMs must be >= 0");
|
|
27
|
+
}
|
|
28
|
+
ensureJsonSerializable(span.inputJson, "span.inputJson");
|
|
29
|
+
ensureJsonSerializable(span.outputJson, "span.outputJson");
|
|
30
|
+
ensureJsonSerializable(span.metadataJson, "span.metadataJson");
|
|
31
|
+
if (span.startTime > span.endTime) {
|
|
32
|
+
throw new Error("span.startTime must be <= span.endTime");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function runToDict(run) {
|
|
36
|
+
validateRun(run);
|
|
37
|
+
return {
|
|
38
|
+
run_id: run.runId,
|
|
39
|
+
name: run.name,
|
|
40
|
+
created_at: run.createdAt.toISOString(),
|
|
41
|
+
metadata_json: run.metadataJson,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function spanToDict(span) {
|
|
45
|
+
validateSpan(span);
|
|
46
|
+
return {
|
|
47
|
+
span_id: span.spanId,
|
|
48
|
+
run_id: span.runId,
|
|
49
|
+
parent_span_id: span.parentSpanId,
|
|
50
|
+
span_type: span.spanType,
|
|
51
|
+
name: span.name,
|
|
52
|
+
start_time: span.startTime.toISOString(),
|
|
53
|
+
end_time: span.endTime.toISOString(),
|
|
54
|
+
duration_ms: span.durationMs,
|
|
55
|
+
input_json: span.inputJson,
|
|
56
|
+
output_json: span.outputJson,
|
|
57
|
+
error: span.error,
|
|
58
|
+
metadata_json: span.metadataJson,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Builds a payload for streaming use: skips cross-reference validation so
|
|
63
|
+
* individual spans can be sent without their run or parent spans in the same
|
|
64
|
+
* payload (they were already sent in earlier calls).
|
|
65
|
+
*/
|
|
66
|
+
function buildStreamingPayload(run, spans) {
|
|
67
|
+
return {
|
|
68
|
+
runs: run ? [runToDict(run)] : [],
|
|
69
|
+
spans: spans.map(spanToDict),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function buildIngestPayload(runs, spans) {
|
|
73
|
+
const runIds = new Set(runs.map((r) => r.runId));
|
|
74
|
+
const spanIdsByRun = new Map();
|
|
75
|
+
for (const span of spans) {
|
|
76
|
+
validateSpan(span);
|
|
77
|
+
if (!spanIdsByRun.has(span.runId)) {
|
|
78
|
+
spanIdsByRun.set(span.runId, new Set());
|
|
79
|
+
}
|
|
80
|
+
spanIdsByRun.get(span.runId).add(span.spanId);
|
|
81
|
+
}
|
|
82
|
+
for (const span of spans) {
|
|
83
|
+
if (!runIds.has(span.runId)) {
|
|
84
|
+
throw new Error(`span ${span.spanId} references unknown run_id ${span.runId}`);
|
|
85
|
+
}
|
|
86
|
+
if (span.parentSpanId &&
|
|
87
|
+
!spanIdsByRun.get(span.runId)?.has(span.parentSpanId)) {
|
|
88
|
+
throw new Error(`span ${span.spanId} has parent_span_id ${span.parentSpanId} not found in run ${span.runId}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
runs: runs.map(runToDict),
|
|
93
|
+
spans: spans.map(spanToDict),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trace = exports.TraceAPI = exports.RunTraceContext = exports.buildIngestPayload = exports.IngestClient = void 0;
|
|
4
|
+
var client_js_1 = require("./client.js");
|
|
5
|
+
Object.defineProperty(exports, "IngestClient", { enumerable: true, get: function () { return client_js_1.IngestClient; } });
|
|
6
|
+
var contracts_js_1 = require("./contracts.js");
|
|
7
|
+
Object.defineProperty(exports, "buildIngestPayload", { enumerable: true, get: function () { return contracts_js_1.buildIngestPayload; } });
|
|
8
|
+
var trace_js_1 = require("./trace.js");
|
|
9
|
+
Object.defineProperty(exports, "RunTraceContext", { enumerable: true, get: function () { return trace_js_1.RunTraceContext; } });
|
|
10
|
+
Object.defineProperty(exports, "TraceAPI", { enumerable: true, get: function () { return trace_js_1.TraceAPI; } });
|
|
11
|
+
Object.defineProperty(exports, "trace", { enumerable: true, get: function () { return trace_js_1.trace; } });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Span, type SpanType } from "./contracts.js";
|
|
2
|
+
export declare class RunTraceContext {
|
|
3
|
+
private readonly _run;
|
|
4
|
+
private readonly _spans;
|
|
5
|
+
private _flushed;
|
|
6
|
+
private _flushError;
|
|
7
|
+
private _streamClient;
|
|
8
|
+
private _runStreamed;
|
|
9
|
+
constructor(name: string, metadata?: Record<string, unknown>);
|
|
10
|
+
get runId(): string;
|
|
11
|
+
get spans(): ReadonlyArray<Span>;
|
|
12
|
+
get flushError(): Error | null;
|
|
13
|
+
addSpan(opts: {
|
|
14
|
+
spanType: SpanType;
|
|
15
|
+
name: string;
|
|
16
|
+
startTime: Date;
|
|
17
|
+
endTime: Date;
|
|
18
|
+
inputJson: unknown;
|
|
19
|
+
outputJson: unknown;
|
|
20
|
+
error?: string | null;
|
|
21
|
+
metadataJson?: Record<string, unknown>;
|
|
22
|
+
parentSpanId?: string | null;
|
|
23
|
+
spanId?: string;
|
|
24
|
+
}): Span;
|
|
25
|
+
private _streamSpan;
|
|
26
|
+
buildPayload(): {
|
|
27
|
+
runs: Record<string, unknown>[];
|
|
28
|
+
spans: Record<string, unknown>[];
|
|
29
|
+
};
|
|
30
|
+
flush(): Promise<void>;
|
|
31
|
+
run<T>(callback: (ctx: RunTraceContext) => T | Promise<T>): Promise<T>;
|
|
32
|
+
}
|
|
33
|
+
export declare class TraceAPI {
|
|
34
|
+
run<T>(name: string, callback: (ctx: RunTraceContext) => T | Promise<T>, metadata?: Record<string, unknown>): Promise<T>;
|
|
35
|
+
current(): RunTraceContext | null;
|
|
36
|
+
}
|
|
37
|
+
export declare const trace: TraceAPI;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trace = exports.TraceAPI = exports.RunTraceContext = void 0;
|
|
4
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const client_js_1 = require("./client.js");
|
|
7
|
+
const contracts_js_1 = require("./contracts.js");
|
|
8
|
+
const asyncLocalStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
9
|
+
class RunTraceContext {
|
|
10
|
+
_run;
|
|
11
|
+
_spans = [];
|
|
12
|
+
_flushed = false;
|
|
13
|
+
_flushError = null;
|
|
14
|
+
_streamClient = undefined; // undefined = not yet resolved
|
|
15
|
+
_runStreamed = false;
|
|
16
|
+
constructor(name, metadata) {
|
|
17
|
+
this._run = {
|
|
18
|
+
runId: (0, node_crypto_1.randomUUID)(),
|
|
19
|
+
name,
|
|
20
|
+
createdAt: new Date(),
|
|
21
|
+
metadataJson: metadata ?? {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
get runId() {
|
|
25
|
+
return this._run.runId;
|
|
26
|
+
}
|
|
27
|
+
get spans() {
|
|
28
|
+
return [...this._spans];
|
|
29
|
+
}
|
|
30
|
+
get flushError() {
|
|
31
|
+
return this._flushError;
|
|
32
|
+
}
|
|
33
|
+
addSpan(opts) {
|
|
34
|
+
const spanId = opts.spanId ?? (0, node_crypto_1.randomUUID)();
|
|
35
|
+
const parentSpanId = opts.parentSpanId ?? null;
|
|
36
|
+
const durationMs = opts.endTime.getTime() - opts.startTime.getTime();
|
|
37
|
+
const span = {
|
|
38
|
+
spanId,
|
|
39
|
+
runId: this._run.runId,
|
|
40
|
+
parentSpanId,
|
|
41
|
+
spanType: opts.spanType,
|
|
42
|
+
name: opts.name,
|
|
43
|
+
startTime: opts.startTime,
|
|
44
|
+
endTime: opts.endTime,
|
|
45
|
+
durationMs,
|
|
46
|
+
inputJson: opts.inputJson,
|
|
47
|
+
outputJson: opts.outputJson,
|
|
48
|
+
error: opts.error ?? null,
|
|
49
|
+
metadataJson: opts.metadataJson ?? {},
|
|
50
|
+
};
|
|
51
|
+
(0, contracts_js_1.validateSpan)(span);
|
|
52
|
+
this._spans.push(span);
|
|
53
|
+
this._streamSpan(span);
|
|
54
|
+
return span;
|
|
55
|
+
}
|
|
56
|
+
_streamSpan(span) {
|
|
57
|
+
if (this._streamClient === undefined) {
|
|
58
|
+
this._streamClient = client_js_1.IngestClient.fromEnv();
|
|
59
|
+
}
|
|
60
|
+
if (!this._streamClient)
|
|
61
|
+
return;
|
|
62
|
+
// Include the run only on the first streaming call so the backend can
|
|
63
|
+
// commit the run row before any spans arrive. Subsequent calls omit it.
|
|
64
|
+
const runToSend = this._runStreamed ? null : this._run;
|
|
65
|
+
this._runStreamed = true;
|
|
66
|
+
const payload = (0, contracts_js_1.buildStreamingPayload)(runToSend, [span]);
|
|
67
|
+
this._streamClient.ingest(payload).catch(() => {
|
|
68
|
+
// Streaming is best-effort; errors are silently dropped.
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
buildPayload() {
|
|
72
|
+
return (0, contracts_js_1.buildIngestPayload)([this._run], this._spans);
|
|
73
|
+
}
|
|
74
|
+
async flush() {
|
|
75
|
+
if (this._flushed)
|
|
76
|
+
return;
|
|
77
|
+
const client = client_js_1.IngestClient.fromEnv();
|
|
78
|
+
if (!client) {
|
|
79
|
+
this._flushed = true;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const payload = this.buildPayload();
|
|
83
|
+
await client.ingest(payload);
|
|
84
|
+
this._flushed = true;
|
|
85
|
+
}
|
|
86
|
+
async run(callback) {
|
|
87
|
+
return asyncLocalStorage.run(this, async () => {
|
|
88
|
+
let result;
|
|
89
|
+
try {
|
|
90
|
+
result = await callback(this);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const now = new Date();
|
|
94
|
+
this.addSpan({
|
|
95
|
+
spanType: "agent",
|
|
96
|
+
name: "run_error",
|
|
97
|
+
startTime: now,
|
|
98
|
+
endTime: now,
|
|
99
|
+
inputJson: {},
|
|
100
|
+
outputJson: {},
|
|
101
|
+
error: err instanceof Error ? err.message : String(err),
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
await this.flush();
|
|
105
|
+
}
|
|
106
|
+
catch (flushErr) {
|
|
107
|
+
this._flushError =
|
|
108
|
+
flushErr instanceof Error ? flushErr : new Error(String(flushErr));
|
|
109
|
+
}
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
await this.flush();
|
|
114
|
+
}
|
|
115
|
+
catch (flushErr) {
|
|
116
|
+
this._flushError =
|
|
117
|
+
flushErr instanceof Error ? flushErr : new Error(String(flushErr));
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.RunTraceContext = RunTraceContext;
|
|
124
|
+
class TraceAPI {
|
|
125
|
+
async run(name, callback, metadata) {
|
|
126
|
+
const ctx = new RunTraceContext(name, metadata);
|
|
127
|
+
return ctx.run(callback);
|
|
128
|
+
}
|
|
129
|
+
current() {
|
|
130
|
+
return asyncLocalStorage.getStore() ?? null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.TraceAPI = TraceAPI;
|
|
134
|
+
exports.trace = new TraceAPI();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class IngestClient {
|
|
2
|
+
private readonly apiUrl;
|
|
3
|
+
private readonly apiKey;
|
|
4
|
+
private readonly timeoutMs;
|
|
5
|
+
private readonly otlpExporterUrl;
|
|
6
|
+
constructor(apiUrl: string, apiKey?: string, timeoutMs?: number, otlpExporterUrl?: string);
|
|
7
|
+
static fromEnv(): IngestClient | null;
|
|
8
|
+
ingest(payload: Record<string, unknown>): Promise<void>;
|
|
9
|
+
private _ingestCustom;
|
|
10
|
+
private _ingestOtlp;
|
|
11
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
function _dtToNs(dt) {
|
|
2
|
+
return String(Math.floor(dt.getTime() * 1_000_000));
|
|
3
|
+
}
|
|
4
|
+
function _uuidToHex(u) {
|
|
5
|
+
return u.replace(/-/g, "");
|
|
6
|
+
}
|
|
7
|
+
function _buildOtlpEnvelope(runs, spans, serviceName = "agenttrace") {
|
|
8
|
+
const runMap = new Map(runs.map((r) => [r["run_id"], r]));
|
|
9
|
+
const traceSpans = new Map();
|
|
10
|
+
for (const span of spans) {
|
|
11
|
+
const traceId = _uuidToHex(span["run_id"]);
|
|
12
|
+
if (!traceSpans.has(traceId)) {
|
|
13
|
+
traceSpans.set(traceId, []);
|
|
14
|
+
}
|
|
15
|
+
const attrs = [
|
|
16
|
+
{ key: "span.type", value: { stringValue: span["span_type"] } },
|
|
17
|
+
{
|
|
18
|
+
key: "input.value",
|
|
19
|
+
value: { stringValue: span["input_json"] ? JSON.stringify(span["input_json"]) : "" },
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: "output.value",
|
|
23
|
+
value: { stringValue: span["output_json"] ? JSON.stringify(span["output_json"]) : "" },
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
const meta = span["metadata_json"];
|
|
27
|
+
if (meta) {
|
|
28
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
29
|
+
attrs.push({ key: k, value: { stringValue: String(v) } });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (span["error"]) {
|
|
33
|
+
attrs.push({ key: "error.message", value: { stringValue: span["error"] } });
|
|
34
|
+
}
|
|
35
|
+
traceSpans.get(traceId).push({
|
|
36
|
+
traceId,
|
|
37
|
+
spanId: _uuidToHex(span["span_id"]),
|
|
38
|
+
parentSpanId: span["parent_span_id"] ? _uuidToHex(span["parent_span_id"]) : "",
|
|
39
|
+
name: span["name"],
|
|
40
|
+
startTimeUnixNano: _dtToNs(new Date(span["start_time"])),
|
|
41
|
+
endTimeUnixNano: _dtToNs(new Date(span["end_time"])),
|
|
42
|
+
attributes: attrs,
|
|
43
|
+
status: span["error"] ? { code: 2 } : {},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const resourceSpans = [];
|
|
47
|
+
for (const [traceId, spanList] of traceSpans) {
|
|
48
|
+
const run = runMap.get(traceId);
|
|
49
|
+
resourceSpans.push({
|
|
50
|
+
resource: {
|
|
51
|
+
attributes: [{ key: "service.name", value: { stringValue: serviceName } }],
|
|
52
|
+
},
|
|
53
|
+
scopeSpans: [{ spans: spanList }],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { resourceSpans };
|
|
57
|
+
}
|
|
58
|
+
export class IngestClient {
|
|
59
|
+
apiUrl;
|
|
60
|
+
apiKey;
|
|
61
|
+
timeoutMs;
|
|
62
|
+
otlpExporterUrl;
|
|
63
|
+
constructor(apiUrl, apiKey, timeoutMs = 5000, otlpExporterUrl) {
|
|
64
|
+
this.apiUrl = apiUrl.replace(/\/+$/, "");
|
|
65
|
+
this.apiKey = apiKey;
|
|
66
|
+
this.timeoutMs = timeoutMs;
|
|
67
|
+
this.otlpExporterUrl = otlpExporterUrl;
|
|
68
|
+
}
|
|
69
|
+
static fromEnv() {
|
|
70
|
+
const apiUrl = process.env.AGENTTRACE_API_URL;
|
|
71
|
+
if (!apiUrl) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const apiKey = process.env.AGENTTRACE_API_KEY;
|
|
75
|
+
const otlpExporterUrl = process.env.AGENTTRACE_OTLP_EXPORTER_URL;
|
|
76
|
+
return new IngestClient(apiUrl, apiKey || undefined, 5000, otlpExporterUrl || undefined);
|
|
77
|
+
}
|
|
78
|
+
async ingest(payload) {
|
|
79
|
+
if (this.otlpExporterUrl) {
|
|
80
|
+
await this._ingestOtlp(payload);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
await this._ingestCustom(payload);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async _ingestCustom(payload) {
|
|
87
|
+
const url = `${this.apiUrl}/v1/ingest`;
|
|
88
|
+
const headers = {
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
};
|
|
91
|
+
if (this.apiKey) {
|
|
92
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
93
|
+
}
|
|
94
|
+
const response = await fetch(url, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers,
|
|
97
|
+
body: JSON.stringify(payload),
|
|
98
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error(`Ingest failed: ${response.status} ${response.statusText}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async _ingestOtlp(payload) {
|
|
105
|
+
const runs = payload["runs"] ?? [];
|
|
106
|
+
const spans = payload["spans"] ?? [];
|
|
107
|
+
const serviceName = runs[0]?.["name"] ?? "agenttrace";
|
|
108
|
+
const otlpPayload = _buildOtlpEnvelope(runs, spans, serviceName);
|
|
109
|
+
const headers = {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
};
|
|
112
|
+
if (this.apiKey) {
|
|
113
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
114
|
+
}
|
|
115
|
+
const response = await fetch(this.otlpExporterUrl, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers,
|
|
118
|
+
body: JSON.stringify(otlpPayload),
|
|
119
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new Error(`OTLP ingest failed: ${response.status} ${response.statusText}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type SpanType = "llm" | "tool" | "retrieval" | "chain" | "agent";
|
|
2
|
+
export interface Run {
|
|
3
|
+
runId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
createdAt: Date;
|
|
6
|
+
metadataJson: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface Span {
|
|
9
|
+
spanId: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
parentSpanId: string | null;
|
|
12
|
+
spanType: SpanType;
|
|
13
|
+
name: string;
|
|
14
|
+
startTime: Date;
|
|
15
|
+
endTime: Date;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
inputJson: unknown;
|
|
18
|
+
outputJson: unknown;
|
|
19
|
+
error: string | null;
|
|
20
|
+
metadataJson: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export declare function validateRun(run: Run): void;
|
|
23
|
+
export declare function validateSpan(span: Span): void;
|
|
24
|
+
/**
|
|
25
|
+
* Builds a payload for streaming use: skips cross-reference validation so
|
|
26
|
+
* individual spans can be sent without their run or parent spans in the same
|
|
27
|
+
* payload (they were already sent in earlier calls).
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildStreamingPayload(run: Run | null, spans: Span[]): {
|
|
30
|
+
runs: Record<string, unknown>[];
|
|
31
|
+
spans: Record<string, unknown>[];
|
|
32
|
+
};
|
|
33
|
+
export declare function buildIngestPayload(runs: Run[], spans: Span[]): {
|
|
34
|
+
runs: Record<string, unknown>[];
|
|
35
|
+
spans: Record<string, unknown>[];
|
|
36
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
function ensureJsonSerializable(value, fieldName) {
|
|
2
|
+
try {
|
|
3
|
+
JSON.stringify(value);
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
throw new Error(`${fieldName} must be JSON serializable`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function validateRun(run) {
|
|
10
|
+
if (!run.name) {
|
|
11
|
+
throw new Error("run.name must be non-empty");
|
|
12
|
+
}
|
|
13
|
+
ensureJsonSerializable(run.metadataJson, "run.metadataJson");
|
|
14
|
+
}
|
|
15
|
+
export function validateSpan(span) {
|
|
16
|
+
if (!span.name) {
|
|
17
|
+
throw new Error("span.name must be non-empty");
|
|
18
|
+
}
|
|
19
|
+
if (span.durationMs < 0) {
|
|
20
|
+
throw new Error("span.durationMs must be >= 0");
|
|
21
|
+
}
|
|
22
|
+
ensureJsonSerializable(span.inputJson, "span.inputJson");
|
|
23
|
+
ensureJsonSerializable(span.outputJson, "span.outputJson");
|
|
24
|
+
ensureJsonSerializable(span.metadataJson, "span.metadataJson");
|
|
25
|
+
if (span.startTime > span.endTime) {
|
|
26
|
+
throw new Error("span.startTime must be <= span.endTime");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function runToDict(run) {
|
|
30
|
+
validateRun(run);
|
|
31
|
+
return {
|
|
32
|
+
run_id: run.runId,
|
|
33
|
+
name: run.name,
|
|
34
|
+
created_at: run.createdAt.toISOString(),
|
|
35
|
+
metadata_json: run.metadataJson,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function spanToDict(span) {
|
|
39
|
+
validateSpan(span);
|
|
40
|
+
return {
|
|
41
|
+
span_id: span.spanId,
|
|
42
|
+
run_id: span.runId,
|
|
43
|
+
parent_span_id: span.parentSpanId,
|
|
44
|
+
span_type: span.spanType,
|
|
45
|
+
name: span.name,
|
|
46
|
+
start_time: span.startTime.toISOString(),
|
|
47
|
+
end_time: span.endTime.toISOString(),
|
|
48
|
+
duration_ms: span.durationMs,
|
|
49
|
+
input_json: span.inputJson,
|
|
50
|
+
output_json: span.outputJson,
|
|
51
|
+
error: span.error,
|
|
52
|
+
metadata_json: span.metadataJson,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Builds a payload for streaming use: skips cross-reference validation so
|
|
57
|
+
* individual spans can be sent without their run or parent spans in the same
|
|
58
|
+
* payload (they were already sent in earlier calls).
|
|
59
|
+
*/
|
|
60
|
+
export function buildStreamingPayload(run, spans) {
|
|
61
|
+
return {
|
|
62
|
+
runs: run ? [runToDict(run)] : [],
|
|
63
|
+
spans: spans.map(spanToDict),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function buildIngestPayload(runs, spans) {
|
|
67
|
+
const runIds = new Set(runs.map((r) => r.runId));
|
|
68
|
+
const spanIdsByRun = new Map();
|
|
69
|
+
for (const span of spans) {
|
|
70
|
+
validateSpan(span);
|
|
71
|
+
if (!spanIdsByRun.has(span.runId)) {
|
|
72
|
+
spanIdsByRun.set(span.runId, new Set());
|
|
73
|
+
}
|
|
74
|
+
spanIdsByRun.get(span.runId).add(span.spanId);
|
|
75
|
+
}
|
|
76
|
+
for (const span of spans) {
|
|
77
|
+
if (!runIds.has(span.runId)) {
|
|
78
|
+
throw new Error(`span ${span.spanId} references unknown run_id ${span.runId}`);
|
|
79
|
+
}
|
|
80
|
+
if (span.parentSpanId &&
|
|
81
|
+
!spanIdsByRun.get(span.runId)?.has(span.parentSpanId)) {
|
|
82
|
+
throw new Error(`span ${span.spanId} has parent_span_id ${span.parentSpanId} not found in run ${span.runId}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
runs: runs.map(runToDict),
|
|
87
|
+
spans: spans.map(spanToDict),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Span, type SpanType } from "./contracts.js";
|
|
2
|
+
export declare class RunTraceContext {
|
|
3
|
+
private readonly _run;
|
|
4
|
+
private readonly _spans;
|
|
5
|
+
private _flushed;
|
|
6
|
+
private _flushError;
|
|
7
|
+
private _streamClient;
|
|
8
|
+
private _runStreamed;
|
|
9
|
+
constructor(name: string, metadata?: Record<string, unknown>);
|
|
10
|
+
get runId(): string;
|
|
11
|
+
get spans(): ReadonlyArray<Span>;
|
|
12
|
+
get flushError(): Error | null;
|
|
13
|
+
addSpan(opts: {
|
|
14
|
+
spanType: SpanType;
|
|
15
|
+
name: string;
|
|
16
|
+
startTime: Date;
|
|
17
|
+
endTime: Date;
|
|
18
|
+
inputJson: unknown;
|
|
19
|
+
outputJson: unknown;
|
|
20
|
+
error?: string | null;
|
|
21
|
+
metadataJson?: Record<string, unknown>;
|
|
22
|
+
parentSpanId?: string | null;
|
|
23
|
+
spanId?: string;
|
|
24
|
+
}): Span;
|
|
25
|
+
private _streamSpan;
|
|
26
|
+
buildPayload(): {
|
|
27
|
+
runs: Record<string, unknown>[];
|
|
28
|
+
spans: Record<string, unknown>[];
|
|
29
|
+
};
|
|
30
|
+
flush(): Promise<void>;
|
|
31
|
+
run<T>(callback: (ctx: RunTraceContext) => T | Promise<T>): Promise<T>;
|
|
32
|
+
}
|
|
33
|
+
export declare class TraceAPI {
|
|
34
|
+
run<T>(name: string, callback: (ctx: RunTraceContext) => T | Promise<T>, metadata?: Record<string, unknown>): Promise<T>;
|
|
35
|
+
current(): RunTraceContext | null;
|
|
36
|
+
}
|
|
37
|
+
export declare const trace: TraceAPI;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { IngestClient } from "./client.js";
|
|
4
|
+
import { buildIngestPayload, buildStreamingPayload, validateSpan, } from "./contracts.js";
|
|
5
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
6
|
+
export class RunTraceContext {
|
|
7
|
+
_run;
|
|
8
|
+
_spans = [];
|
|
9
|
+
_flushed = false;
|
|
10
|
+
_flushError = null;
|
|
11
|
+
_streamClient = undefined; // undefined = not yet resolved
|
|
12
|
+
_runStreamed = false;
|
|
13
|
+
constructor(name, metadata) {
|
|
14
|
+
this._run = {
|
|
15
|
+
runId: randomUUID(),
|
|
16
|
+
name,
|
|
17
|
+
createdAt: new Date(),
|
|
18
|
+
metadataJson: metadata ?? {},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
get runId() {
|
|
22
|
+
return this._run.runId;
|
|
23
|
+
}
|
|
24
|
+
get spans() {
|
|
25
|
+
return [...this._spans];
|
|
26
|
+
}
|
|
27
|
+
get flushError() {
|
|
28
|
+
return this._flushError;
|
|
29
|
+
}
|
|
30
|
+
addSpan(opts) {
|
|
31
|
+
const spanId = opts.spanId ?? randomUUID();
|
|
32
|
+
const parentSpanId = opts.parentSpanId ?? null;
|
|
33
|
+
const durationMs = opts.endTime.getTime() - opts.startTime.getTime();
|
|
34
|
+
const span = {
|
|
35
|
+
spanId,
|
|
36
|
+
runId: this._run.runId,
|
|
37
|
+
parentSpanId,
|
|
38
|
+
spanType: opts.spanType,
|
|
39
|
+
name: opts.name,
|
|
40
|
+
startTime: opts.startTime,
|
|
41
|
+
endTime: opts.endTime,
|
|
42
|
+
durationMs,
|
|
43
|
+
inputJson: opts.inputJson,
|
|
44
|
+
outputJson: opts.outputJson,
|
|
45
|
+
error: opts.error ?? null,
|
|
46
|
+
metadataJson: opts.metadataJson ?? {},
|
|
47
|
+
};
|
|
48
|
+
validateSpan(span);
|
|
49
|
+
this._spans.push(span);
|
|
50
|
+
this._streamSpan(span);
|
|
51
|
+
return span;
|
|
52
|
+
}
|
|
53
|
+
_streamSpan(span) {
|
|
54
|
+
if (this._streamClient === undefined) {
|
|
55
|
+
this._streamClient = IngestClient.fromEnv();
|
|
56
|
+
}
|
|
57
|
+
if (!this._streamClient)
|
|
58
|
+
return;
|
|
59
|
+
// Include the run only on the first streaming call so the backend can
|
|
60
|
+
// commit the run row before any spans arrive. Subsequent calls omit it.
|
|
61
|
+
const runToSend = this._runStreamed ? null : this._run;
|
|
62
|
+
this._runStreamed = true;
|
|
63
|
+
const payload = buildStreamingPayload(runToSend, [span]);
|
|
64
|
+
this._streamClient.ingest(payload).catch(() => {
|
|
65
|
+
// Streaming is best-effort; errors are silently dropped.
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
buildPayload() {
|
|
69
|
+
return buildIngestPayload([this._run], this._spans);
|
|
70
|
+
}
|
|
71
|
+
async flush() {
|
|
72
|
+
if (this._flushed)
|
|
73
|
+
return;
|
|
74
|
+
const client = IngestClient.fromEnv();
|
|
75
|
+
if (!client) {
|
|
76
|
+
this._flushed = true;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const payload = this.buildPayload();
|
|
80
|
+
await client.ingest(payload);
|
|
81
|
+
this._flushed = true;
|
|
82
|
+
}
|
|
83
|
+
async run(callback) {
|
|
84
|
+
return asyncLocalStorage.run(this, async () => {
|
|
85
|
+
let result;
|
|
86
|
+
try {
|
|
87
|
+
result = await callback(this);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const now = new Date();
|
|
91
|
+
this.addSpan({
|
|
92
|
+
spanType: "agent",
|
|
93
|
+
name: "run_error",
|
|
94
|
+
startTime: now,
|
|
95
|
+
endTime: now,
|
|
96
|
+
inputJson: {},
|
|
97
|
+
outputJson: {},
|
|
98
|
+
error: err instanceof Error ? err.message : String(err),
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
await this.flush();
|
|
102
|
+
}
|
|
103
|
+
catch (flushErr) {
|
|
104
|
+
this._flushError =
|
|
105
|
+
flushErr instanceof Error ? flushErr : new Error(String(flushErr));
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await this.flush();
|
|
111
|
+
}
|
|
112
|
+
catch (flushErr) {
|
|
113
|
+
this._flushError =
|
|
114
|
+
flushErr instanceof Error ? flushErr : new Error(String(flushErr));
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export class TraceAPI {
|
|
121
|
+
async run(name, callback, metadata) {
|
|
122
|
+
const ctx = new RunTraceContext(name, metadata);
|
|
123
|
+
return ctx.run(callback);
|
|
124
|
+
}
|
|
125
|
+
current() {
|
|
126
|
+
return asyncLocalStorage.getStore() ?? null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export const trace = new TraceAPI();
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@m_keshav/agenttrace",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "TypeScript SDK for AgentTrace – capture and ingest agent execution traces",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/esm/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/esm/index.js",
|
|
12
|
+
"require": "./dist/cjs/index.js",
|
|
13
|
+
"types": "./dist/esm/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json",
|
|
21
|
+
"test": "vitest run"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"testcontainers": "^12.0.4",
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"vitest": "^3.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|