retrace-sdk 0.1.3
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/README.md +94 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +21 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +15 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/interceptors/gemini.d.ts +3 -0
- package/dist/interceptors/gemini.js +82 -0
- package/dist/interceptors/index.d.ts +1 -0
- package/dist/interceptors/index.js +1 -0
- package/dist/recorder.d.ts +22 -0
- package/dist/recorder.js +123 -0
- package/dist/trace.d.ts +74 -0
- package/dist/trace.js +93 -0
- package/dist/transport.d.ts +24 -0
- package/dist/transport.js +124 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +21 -0
- package/package.json +36 -0
- package/src/config.test.ts +16 -0
- package/src/config.ts +31 -0
- package/src/index.ts +6 -0
- package/src/interceptors/gemini.ts +86 -0
- package/src/interceptors/index.ts +1 -0
- package/src/recorder.ts +136 -0
- package/src/trace.ts +135 -0
- package/src/transport.ts +133 -0
- package/src/utils.ts +23 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# retrace
|
|
2
|
+
|
|
3
|
+
Record, replay, fork & share AI agent executions — TypeScript SDK.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install retrace
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { configure, record, trace, TraceStatus } from "retrace";
|
|
15
|
+
|
|
16
|
+
configure({ apiKey: "rt_live_...", projectId: "your-project-id" });
|
|
17
|
+
|
|
18
|
+
// Wrap a function with trace()
|
|
19
|
+
const myAgent = trace((prompt: string) => {
|
|
20
|
+
// OpenAI calls are auto-captured
|
|
21
|
+
const response = await openai.chat.completions.create({
|
|
22
|
+
model: "gpt-4o",
|
|
23
|
+
messages: [{ role: "user", content: prompt }],
|
|
24
|
+
});
|
|
25
|
+
return response.choices[0].message.content;
|
|
26
|
+
}, { name: "my-agent" });
|
|
27
|
+
|
|
28
|
+
const result = myAgent("What is the meaning of life?");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { configure } from "retrace";
|
|
35
|
+
|
|
36
|
+
configure({
|
|
37
|
+
apiKey: "rt_live_...", // or RETRACE_API_KEY env var
|
|
38
|
+
baseUrl: "http://localhost:3001", // or RETRACE_BASE_URL env var
|
|
39
|
+
projectId: "...", // or RETRACE_PROJECT_ID env var
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Set `RETRACE_ENABLED=false` to disable recording without changing code.
|
|
44
|
+
|
|
45
|
+
## OpenAI Auto-Capture
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { record, TraceStatus } from "retrace";
|
|
49
|
+
import OpenAI from "openai";
|
|
50
|
+
|
|
51
|
+
const openai = new OpenAI();
|
|
52
|
+
const recorder = record({ name: "chat-agent" });
|
|
53
|
+
recorder.start();
|
|
54
|
+
|
|
55
|
+
const response = await openai.chat.completions.create({
|
|
56
|
+
model: "gpt-4o",
|
|
57
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
58
|
+
});
|
|
59
|
+
// LLM call is automatically captured as a span
|
|
60
|
+
|
|
61
|
+
recorder.end(response.choices[0].message.content);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Manual Span Creation
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { record, SpanType } from "retrace";
|
|
68
|
+
|
|
69
|
+
const recorder = record({ name: "custom-agent" });
|
|
70
|
+
recorder.start();
|
|
71
|
+
|
|
72
|
+
const span = recorder.startSpan("web-search", SpanType.TOOL_CALL, { query: "latest news" });
|
|
73
|
+
// ... do work ...
|
|
74
|
+
recorder.endSpan(span, { results: ["..."] });
|
|
75
|
+
|
|
76
|
+
recorder.end("Done");
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Transport Options
|
|
80
|
+
|
|
81
|
+
The SDK uses WebSocket by default for real-time streaming. Falls back to HTTP if WS connection fails.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createTransport } from "retrace/transport";
|
|
85
|
+
|
|
86
|
+
// Force HTTP transport
|
|
87
|
+
const transport = createTransport("http");
|
|
88
|
+
|
|
89
|
+
// Force WebSocket
|
|
90
|
+
const transport = createTransport("ws");
|
|
91
|
+
|
|
92
|
+
// Auto-detect (default)
|
|
93
|
+
const transport = createTransport("auto");
|
|
94
|
+
```
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
apiKey: process.env.RETRACE_API_KEY || "",
|
|
3
|
+
baseUrl: process.env.RETRACE_BASE_URL || "http://localhost:3001",
|
|
4
|
+
wsUrl: "",
|
|
5
|
+
projectId: process.env.RETRACE_PROJECT_ID || undefined,
|
|
6
|
+
enabled: !["false", "0"].includes((process.env.RETRACE_ENABLED || "true").toLowerCase()),
|
|
7
|
+
};
|
|
8
|
+
config.wsUrl = config.baseUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
9
|
+
export function configure(opts) {
|
|
10
|
+
if (opts.apiKey && !opts.apiKey.startsWith("rt_live_")) {
|
|
11
|
+
console.warn("[retrace] API key does not start with 'rt_live_'. This may be invalid.");
|
|
12
|
+
}
|
|
13
|
+
Object.assign(config, opts);
|
|
14
|
+
if (opts.baseUrl && !opts.wsUrl) {
|
|
15
|
+
config.wsUrl = config.baseUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
16
|
+
}
|
|
17
|
+
return config;
|
|
18
|
+
}
|
|
19
|
+
export function getConfig() {
|
|
20
|
+
return config;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { configure, getConfig } from "./config.js";
|
|
3
|
+
describe("SDK config", () => {
|
|
4
|
+
it("has defaults", () => {
|
|
5
|
+
const c = getConfig();
|
|
6
|
+
expect(c.baseUrl).toBe("http://localhost:3001");
|
|
7
|
+
expect(c.enabled).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
it("configures custom values", () => {
|
|
10
|
+
configure({ apiKey: "rt_live_test", baseUrl: "http://custom:3001" });
|
|
11
|
+
const c = getConfig();
|
|
12
|
+
expect(c.apiKey).toBe("rt_live_test");
|
|
13
|
+
expect(c.baseUrl).toBe("http://custom:3001");
|
|
14
|
+
});
|
|
15
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { configure, getConfig } from "./config.js";
|
|
2
|
+
export { record, trace, TraceRecorder } from "./recorder.js";
|
|
3
|
+
export { SpanBuilder, TraceBuilder } from "./trace.js";
|
|
4
|
+
export type { SpanData, TraceData } from "./trace.js";
|
|
5
|
+
export { SpanType, TraceStatus } from "./trace.js";
|
|
6
|
+
export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./interceptors/gemini.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { configure, getConfig } from "./config.js";
|
|
2
|
+
export { record, trace, TraceRecorder } from "./recorder.js";
|
|
3
|
+
export { SpanBuilder, TraceBuilder } from "./trace.js";
|
|
4
|
+
export { SpanType, TraceStatus } from "./trace.js";
|
|
5
|
+
export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./interceptors/gemini.js";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { SpanType } from "../trace.js";
|
|
2
|
+
import { genId, nowIso, truncateJson } from "../utils.js";
|
|
3
|
+
const PRICING = {
|
|
4
|
+
"gemini-3.1-pro-preview": [2.0, 12.0],
|
|
5
|
+
"gemini-2.5-pro": [1.25, 10.0],
|
|
6
|
+
"gemini-2.5-flash": [0.15, 0.60],
|
|
7
|
+
"gemini-2.0-flash": [0.10, 0.40],
|
|
8
|
+
};
|
|
9
|
+
function calcCost(model, inputTokens, outputTokens) {
|
|
10
|
+
const p = PRICING[model] || [0, 0];
|
|
11
|
+
return (inputTokens * p[0] + outputTokens * p[1]) / 1_000_000;
|
|
12
|
+
}
|
|
13
|
+
let originalGenerateContent = null;
|
|
14
|
+
let installed = false;
|
|
15
|
+
let onSpanCallback = null;
|
|
16
|
+
export function installGeminiInterceptor(onSpan) {
|
|
17
|
+
if (installed) {
|
|
18
|
+
onSpanCallback = onSpan;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
onSpanCallback = onSpan;
|
|
22
|
+
import("@google/genai").then((genaiMod) => {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
const mod = genaiMod;
|
|
25
|
+
const modelsProto = mod?.Models?.prototype || mod?.default?.Models?.prototype;
|
|
26
|
+
if (!modelsProto?.generateContent)
|
|
27
|
+
return;
|
|
28
|
+
originalGenerateContent = modelsProto.generateContent;
|
|
29
|
+
modelsProto.generateContent = async function (...args) {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
const opts = args[0] || {};
|
|
32
|
+
const model = opts.model || "unknown";
|
|
33
|
+
const contents = opts.contents;
|
|
34
|
+
const spanId = genId();
|
|
35
|
+
const startedAt = nowIso();
|
|
36
|
+
const startMs = Date.now();
|
|
37
|
+
try {
|
|
38
|
+
const result = await originalGenerateContent.apply(this, args);
|
|
39
|
+
const durationMs = Date.now() - startMs;
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
const res = result;
|
|
42
|
+
const inputTokens = res?.usageMetadata?.promptTokenCount || 0;
|
|
43
|
+
const outputTokens = res?.usageMetadata?.candidatesTokenCount || 0;
|
|
44
|
+
const span = {
|
|
45
|
+
id: spanId, trace_id: "", parent_id: null,
|
|
46
|
+
span_type: SpanType.LLM_CALL, name: "retrace.ai.generate", model,
|
|
47
|
+
input: truncateJson(contents), output: truncateJson(res?.text || ""),
|
|
48
|
+
input_tokens: inputTokens, output_tokens: outputTokens,
|
|
49
|
+
cost: calcCost(model, inputTokens, outputTokens),
|
|
50
|
+
duration_ms: durationMs, started_at: startedAt, ended_at: nowIso(),
|
|
51
|
+
};
|
|
52
|
+
onSpanCallback?.(span);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const span = {
|
|
57
|
+
id: spanId, trace_id: "", parent_id: null,
|
|
58
|
+
span_type: SpanType.LLM_CALL, name: "retrace.ai.generate", model,
|
|
59
|
+
input: truncateJson(contents), started_at: startedAt, ended_at: nowIso(),
|
|
60
|
+
duration_ms: Date.now() - startMs,
|
|
61
|
+
error: err instanceof Error ? err.message : String(err),
|
|
62
|
+
};
|
|
63
|
+
onSpanCallback?.(span);
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
installed = true;
|
|
68
|
+
}).catch(() => { });
|
|
69
|
+
}
|
|
70
|
+
export function uninstallGeminiInterceptor() {
|
|
71
|
+
if (!installed || !originalGenerateContent)
|
|
72
|
+
return;
|
|
73
|
+
import("@google/genai").then((genaiMod) => {
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
const mod = genaiMod;
|
|
76
|
+
const modelsProto = mod?.Models?.prototype || mod?.default?.Models?.prototype;
|
|
77
|
+
if (modelsProto)
|
|
78
|
+
modelsProto.generateContent = originalGenerateContent;
|
|
79
|
+
}).catch(() => { });
|
|
80
|
+
installed = false;
|
|
81
|
+
onSpanCallback = null;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./gemini.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./gemini.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SpanBuilder, SpanData, SpanType, TraceStatus } from "./trace.js";
|
|
2
|
+
export interface RecordOptions {
|
|
3
|
+
name?: string;
|
|
4
|
+
input?: unknown;
|
|
5
|
+
metadata?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export declare class TraceRecorder {
|
|
8
|
+
private builder;
|
|
9
|
+
private transport;
|
|
10
|
+
private interceptorsInstalled;
|
|
11
|
+
output: unknown;
|
|
12
|
+
constructor(opts?: RecordOptions);
|
|
13
|
+
get traceId(): string;
|
|
14
|
+
start(name?: string, input?: unknown): this;
|
|
15
|
+
end(output?: unknown, status?: TraceStatus): void;
|
|
16
|
+
addSpan(span: SpanData): void;
|
|
17
|
+
startSpan(name: string, spanType?: SpanType, input?: unknown, model?: string, parentId?: string): SpanBuilder;
|
|
18
|
+
endSpan(spanBuilder: SpanBuilder, output?: unknown, error?: string): void;
|
|
19
|
+
private installInterceptors;
|
|
20
|
+
}
|
|
21
|
+
export declare function record(opts?: RecordOptions): TraceRecorder;
|
|
22
|
+
export declare function trace<T>(fn: (...args: unknown[]) => T, opts?: RecordOptions): (...args: unknown[]) => T;
|
package/dist/recorder.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { getConfig } from "./config.js";
|
|
2
|
+
import { SpanBuilder, SpanType, TraceBuilder, TraceStatus } from "./trace.js";
|
|
3
|
+
import { createTransport } from "./transport.js";
|
|
4
|
+
import { installGeminiInterceptor } from "./interceptors/gemini.js";
|
|
5
|
+
export class TraceRecorder {
|
|
6
|
+
builder;
|
|
7
|
+
transport;
|
|
8
|
+
interceptorsInstalled = false;
|
|
9
|
+
output = undefined;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.builder = new TraceBuilder();
|
|
12
|
+
this.transport = createTransport();
|
|
13
|
+
const cfg = getConfig();
|
|
14
|
+
if (cfg.projectId)
|
|
15
|
+
this.builder.setProjectId(cfg.projectId);
|
|
16
|
+
if (opts?.metadata)
|
|
17
|
+
this.builder.setMetadata(opts.metadata);
|
|
18
|
+
if (opts?.name || opts?.input) {
|
|
19
|
+
this.builder.start(opts.name, opts.input);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
get traceId() { return this.builder.id; }
|
|
23
|
+
start(name, input) {
|
|
24
|
+
this.builder.start(name, input);
|
|
25
|
+
this.installInterceptors();
|
|
26
|
+
this.transport.send("trace_started", this.builder.toDict());
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
end(output, status = TraceStatus.COMPLETED) {
|
|
30
|
+
if (output !== undefined)
|
|
31
|
+
this.output = output;
|
|
32
|
+
const data = this.builder.end(this.output, status);
|
|
33
|
+
this.transport.send("trace_ended", {
|
|
34
|
+
id: data.id,
|
|
35
|
+
ended_at: data.ended_at,
|
|
36
|
+
output: data.output,
|
|
37
|
+
status: data.status,
|
|
38
|
+
total_tokens: data.total_tokens,
|
|
39
|
+
total_cost: data.total_cost,
|
|
40
|
+
});
|
|
41
|
+
this.transport.close();
|
|
42
|
+
}
|
|
43
|
+
addSpan(span) {
|
|
44
|
+
span.trace_id = this.builder.id;
|
|
45
|
+
this.builder.addSpan(span);
|
|
46
|
+
this.transport.send("span_started", span);
|
|
47
|
+
if (span.ended_at) {
|
|
48
|
+
this.transport.send("span_ended", {
|
|
49
|
+
id: span.id,
|
|
50
|
+
ended_at: span.ended_at,
|
|
51
|
+
output: span.output,
|
|
52
|
+
output_tokens: span.output_tokens,
|
|
53
|
+
cost: span.cost,
|
|
54
|
+
error: span.error,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
startSpan(name, spanType = SpanType.LLM_CALL, input, model, parentId) {
|
|
59
|
+
const sb = new SpanBuilder(name, spanType).start();
|
|
60
|
+
sb.setTraceId(this.builder.id);
|
|
61
|
+
if (input !== undefined)
|
|
62
|
+
sb.setInput(input);
|
|
63
|
+
if (model)
|
|
64
|
+
sb.setModel(model);
|
|
65
|
+
if (parentId)
|
|
66
|
+
sb.setParentId(parentId);
|
|
67
|
+
this.transport.send("span_started", sb.toData());
|
|
68
|
+
return sb;
|
|
69
|
+
}
|
|
70
|
+
endSpan(spanBuilder, output, error) {
|
|
71
|
+
const span = spanBuilder.end(output, error);
|
|
72
|
+
span.trace_id = this.builder.id;
|
|
73
|
+
this.builder.addSpan(span);
|
|
74
|
+
this.transport.send("span_ended", {
|
|
75
|
+
id: span.id,
|
|
76
|
+
ended_at: span.ended_at,
|
|
77
|
+
output: span.output,
|
|
78
|
+
output_tokens: span.output_tokens,
|
|
79
|
+
cost: span.cost,
|
|
80
|
+
error: span.error,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
installInterceptors() {
|
|
84
|
+
if (this.interceptorsInstalled)
|
|
85
|
+
return;
|
|
86
|
+
installGeminiInterceptor((span) => this.addSpan(span));
|
|
87
|
+
this.interceptorsInstalled = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function record(opts) {
|
|
91
|
+
const cfg = getConfig();
|
|
92
|
+
if (!cfg.enabled) {
|
|
93
|
+
// Return a no-op recorder
|
|
94
|
+
return new TraceRecorder(opts);
|
|
95
|
+
}
|
|
96
|
+
return new TraceRecorder(opts);
|
|
97
|
+
}
|
|
98
|
+
export function trace(fn, opts) {
|
|
99
|
+
const cfg = getConfig();
|
|
100
|
+
return (...args) => {
|
|
101
|
+
if (!cfg.enabled)
|
|
102
|
+
return fn(...args);
|
|
103
|
+
const recorder = new TraceRecorder({
|
|
104
|
+
name: opts?.name || fn.name || "anonymous",
|
|
105
|
+
input: opts?.input ?? args,
|
|
106
|
+
metadata: opts?.metadata,
|
|
107
|
+
});
|
|
108
|
+
recorder.start(opts?.name || fn.name || "anonymous", opts?.input ?? args);
|
|
109
|
+
try {
|
|
110
|
+
const result = fn(...args);
|
|
111
|
+
// Handle async functions
|
|
112
|
+
if (result && typeof result.then === "function") {
|
|
113
|
+
return result.then((resolved) => { recorder.end(resolved, TraceStatus.COMPLETED); return resolved; }, (err) => { recorder.end(undefined, TraceStatus.FAILED); throw err; });
|
|
114
|
+
}
|
|
115
|
+
recorder.end(result, TraceStatus.COMPLETED);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
recorder.end(undefined, TraceStatus.FAILED);
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
package/dist/trace.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export declare enum SpanType {
|
|
2
|
+
LLM_CALL = "llm_call",
|
|
3
|
+
TOOL_CALL = "tool_call",
|
|
4
|
+
TOOL_RESULT = "tool_result",
|
|
5
|
+
REASONING = "reasoning",
|
|
6
|
+
ACTION = "action",
|
|
7
|
+
ERROR = "error",
|
|
8
|
+
FORK_POINT = "fork_point"
|
|
9
|
+
}
|
|
10
|
+
export declare enum TraceStatus {
|
|
11
|
+
RUNNING = "running",
|
|
12
|
+
COMPLETED = "completed",
|
|
13
|
+
FAILED = "failed"
|
|
14
|
+
}
|
|
15
|
+
export interface SpanData {
|
|
16
|
+
id: string;
|
|
17
|
+
trace_id: string;
|
|
18
|
+
parent_id: string | null;
|
|
19
|
+
span_type: string;
|
|
20
|
+
name: string;
|
|
21
|
+
model?: string;
|
|
22
|
+
input?: unknown;
|
|
23
|
+
output?: unknown;
|
|
24
|
+
input_tokens?: number;
|
|
25
|
+
output_tokens?: number;
|
|
26
|
+
cost?: number;
|
|
27
|
+
duration_ms?: number;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
started_at: string;
|
|
30
|
+
ended_at?: string;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface TraceData {
|
|
34
|
+
id: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
input?: unknown;
|
|
37
|
+
output?: unknown;
|
|
38
|
+
status: string;
|
|
39
|
+
total_tokens: number;
|
|
40
|
+
total_cost: number;
|
|
41
|
+
total_duration_ms: number;
|
|
42
|
+
metadata?: Record<string, unknown>;
|
|
43
|
+
started_at: string;
|
|
44
|
+
ended_at?: string;
|
|
45
|
+
spans?: SpanData[];
|
|
46
|
+
project_id?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare class SpanBuilder {
|
|
49
|
+
private data;
|
|
50
|
+
private _startTime?;
|
|
51
|
+
constructor(name: string, spanType: SpanType);
|
|
52
|
+
setModel(model: string): this;
|
|
53
|
+
setInput(input: unknown): this;
|
|
54
|
+
setOutput(output: unknown): this;
|
|
55
|
+
setParentId(id: string): this;
|
|
56
|
+
setTraceId(id: string): this;
|
|
57
|
+
setMetadata(m: Record<string, unknown>): this;
|
|
58
|
+
start(): this;
|
|
59
|
+
end(output?: unknown, error?: string): SpanData;
|
|
60
|
+
get id(): string;
|
|
61
|
+
toData(): SpanData;
|
|
62
|
+
}
|
|
63
|
+
export declare class TraceBuilder {
|
|
64
|
+
private data;
|
|
65
|
+
private _startTime?;
|
|
66
|
+
constructor();
|
|
67
|
+
start(name?: string, input?: unknown): this;
|
|
68
|
+
end(output?: unknown, status?: TraceStatus): TraceData;
|
|
69
|
+
addSpan(span: SpanData): void;
|
|
70
|
+
get id(): string;
|
|
71
|
+
setProjectId(id: string): void;
|
|
72
|
+
setMetadata(m: Record<string, unknown>): void;
|
|
73
|
+
toDict(): TraceData;
|
|
74
|
+
}
|
package/dist/trace.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { genId, nowIso, utcNow } from "./utils.js";
|
|
2
|
+
export var SpanType;
|
|
3
|
+
(function (SpanType) {
|
|
4
|
+
SpanType["LLM_CALL"] = "llm_call";
|
|
5
|
+
SpanType["TOOL_CALL"] = "tool_call";
|
|
6
|
+
SpanType["TOOL_RESULT"] = "tool_result";
|
|
7
|
+
SpanType["REASONING"] = "reasoning";
|
|
8
|
+
SpanType["ACTION"] = "action";
|
|
9
|
+
SpanType["ERROR"] = "error";
|
|
10
|
+
SpanType["FORK_POINT"] = "fork_point";
|
|
11
|
+
})(SpanType || (SpanType = {}));
|
|
12
|
+
export var TraceStatus;
|
|
13
|
+
(function (TraceStatus) {
|
|
14
|
+
TraceStatus["RUNNING"] = "running";
|
|
15
|
+
TraceStatus["COMPLETED"] = "completed";
|
|
16
|
+
TraceStatus["FAILED"] = "failed";
|
|
17
|
+
})(TraceStatus || (TraceStatus = {}));
|
|
18
|
+
export class SpanBuilder {
|
|
19
|
+
data;
|
|
20
|
+
_startTime;
|
|
21
|
+
constructor(name, spanType) {
|
|
22
|
+
this.data = { id: genId(), span_type: spanType, name, trace_id: "", parent_id: null, started_at: nowIso() };
|
|
23
|
+
}
|
|
24
|
+
setModel(model) { this.data.model = model; return this; }
|
|
25
|
+
setInput(input) { this.data.input = input; return this; }
|
|
26
|
+
setOutput(output) { this.data.output = output; return this; }
|
|
27
|
+
setParentId(id) { this.data.parent_id = id; return this; }
|
|
28
|
+
setTraceId(id) { this.data.trace_id = id; return this; }
|
|
29
|
+
setMetadata(m) { this.data.metadata = m; return this; }
|
|
30
|
+
start() {
|
|
31
|
+
this._startTime = utcNow();
|
|
32
|
+
this.data.started_at = this._startTime.toISOString();
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
end(output, error) {
|
|
36
|
+
const now = utcNow();
|
|
37
|
+
this.data.ended_at = now.toISOString();
|
|
38
|
+
if (output !== undefined)
|
|
39
|
+
this.data.output = output;
|
|
40
|
+
if (error)
|
|
41
|
+
this.data.error = error;
|
|
42
|
+
if (this._startTime) {
|
|
43
|
+
this.data.duration_ms = now.getTime() - this._startTime.getTime();
|
|
44
|
+
}
|
|
45
|
+
return this.data;
|
|
46
|
+
}
|
|
47
|
+
get id() { return this.data.id; }
|
|
48
|
+
toData() { return this.data; }
|
|
49
|
+
}
|
|
50
|
+
export class TraceBuilder {
|
|
51
|
+
data;
|
|
52
|
+
_startTime;
|
|
53
|
+
constructor() {
|
|
54
|
+
this.data = {
|
|
55
|
+
id: genId(),
|
|
56
|
+
status: TraceStatus.RUNNING,
|
|
57
|
+
total_tokens: 0,
|
|
58
|
+
total_cost: 0,
|
|
59
|
+
total_duration_ms: 0,
|
|
60
|
+
started_at: nowIso(),
|
|
61
|
+
spans: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
start(name, input) {
|
|
65
|
+
this._startTime = utcNow();
|
|
66
|
+
this.data.started_at = this._startTime.toISOString();
|
|
67
|
+
if (name)
|
|
68
|
+
this.data.name = name;
|
|
69
|
+
if (input !== undefined)
|
|
70
|
+
this.data.input = input;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
end(output, status = TraceStatus.COMPLETED) {
|
|
74
|
+
const now = utcNow();
|
|
75
|
+
this.data.ended_at = now.toISOString();
|
|
76
|
+
this.data.status = status;
|
|
77
|
+
if (output !== undefined)
|
|
78
|
+
this.data.output = output;
|
|
79
|
+
if (this._startTime) {
|
|
80
|
+
this.data.total_duration_ms = now.getTime() - this._startTime.getTime();
|
|
81
|
+
}
|
|
82
|
+
return this.data;
|
|
83
|
+
}
|
|
84
|
+
addSpan(span) {
|
|
85
|
+
this.data.spans.push(span);
|
|
86
|
+
this.data.total_tokens += (span.input_tokens || 0) + (span.output_tokens || 0);
|
|
87
|
+
this.data.total_cost += span.cost || 0;
|
|
88
|
+
}
|
|
89
|
+
get id() { return this.data.id; }
|
|
90
|
+
setProjectId(id) { this.data.project_id = id; }
|
|
91
|
+
setMetadata(m) { this.data.metadata = m; }
|
|
92
|
+
toDict() { return this.data; }
|
|
93
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Transport {
|
|
2
|
+
send(eventType: string, data: Record<string, unknown>): void;
|
|
3
|
+
close(): void;
|
|
4
|
+
}
|
|
5
|
+
export declare class WSTransport implements Transport {
|
|
6
|
+
private ws;
|
|
7
|
+
private connected;
|
|
8
|
+
private backoff;
|
|
9
|
+
private queue;
|
|
10
|
+
connect(): void;
|
|
11
|
+
private reconnect;
|
|
12
|
+
private flushQueue;
|
|
13
|
+
send(eventType: string, data: Record<string, unknown>): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare class HTTPTransport implements Transport {
|
|
17
|
+
private traceData;
|
|
18
|
+
private spans;
|
|
19
|
+
send(eventType: string, data: Record<string, unknown>): void;
|
|
20
|
+
flush(): void;
|
|
21
|
+
private buildSpans;
|
|
22
|
+
close(): void;
|
|
23
|
+
}
|
|
24
|
+
export declare function createTransport(mode?: "ws" | "http" | "auto"): Transport;
|