retrace-sdk 0.5.0 → 0.5.2
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/index.js +2 -0
- package/dist/interceptors/anthropic.js +52 -0
- package/dist/recorder.d.ts +5 -0
- package/dist/recorder.js +19 -0
- package/dist/resume.js +4 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,3 +9,5 @@ export { RetraceError, RetraceAuthError, RetraceCreditsExhaustedError, RetraceCo
|
|
|
9
9
|
export { registerResumable, handleResume } from "./resume.js";
|
|
10
10
|
export { isReplaying, consumeCassetteEntry, handleReplay } from "./replay.js";
|
|
11
11
|
export { setTraceContext, clearTraceContext, getTraceparent, injectTraceparent, parseTraceparent } from "./traceparent.js";
|
|
12
|
+
// v0.5.0
|
|
13
|
+
// trigger
|
|
@@ -49,6 +49,7 @@ function createPatchedCreate() {
|
|
|
49
49
|
const opts = args[0] || {};
|
|
50
50
|
const model = opts.model || "unknown";
|
|
51
51
|
const messages = opts.messages || [];
|
|
52
|
+
const isStreaming = !!opts.stream;
|
|
52
53
|
const spanId = genId();
|
|
53
54
|
const startedAt = nowIso();
|
|
54
55
|
const startMs = Date.now();
|
|
@@ -76,6 +77,57 @@ function createPatchedCreate() {
|
|
|
76
77
|
}
|
|
77
78
|
try {
|
|
78
79
|
const result = await originalCreate.apply(this, args);
|
|
80
|
+
// Streaming: Anthropic returns an async iterable of MessageStreamEvent
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
if (isStreaming && result && typeof result[Symbol.asyncIterator] === "function") {
|
|
83
|
+
const chunks = [];
|
|
84
|
+
let inputTokens = 0;
|
|
85
|
+
let outputTokens = 0;
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
+
const originalIterator = result[Symbol.asyncIterator]();
|
|
88
|
+
const wrappedStream = {
|
|
89
|
+
[Symbol.asyncIterator]() {
|
|
90
|
+
return {
|
|
91
|
+
async next() {
|
|
92
|
+
const { value, done } = await originalIterator.next();
|
|
93
|
+
if (done) {
|
|
94
|
+
const durationMs = Date.now() - startMs;
|
|
95
|
+
const output = chunks.join("");
|
|
96
|
+
const span = {
|
|
97
|
+
id: spanId, trace_id: "", parent_id: null,
|
|
98
|
+
span_type: SpanType.LLM_CALL, name: "anthropic.messages.create", model,
|
|
99
|
+
input: truncateJson({ messages: messages.slice(0, 10) }),
|
|
100
|
+
output: truncateJson(output),
|
|
101
|
+
input_tokens: inputTokens, output_tokens: outputTokens,
|
|
102
|
+
cost: calcCost(model, inputTokens, outputTokens),
|
|
103
|
+
duration_ms: durationMs, started_at: startedAt, ended_at: nowIso(),
|
|
104
|
+
metadata: { streaming: true },
|
|
105
|
+
};
|
|
106
|
+
onSpanCallback?.(span);
|
|
107
|
+
return { value: undefined, done: true };
|
|
108
|
+
}
|
|
109
|
+
// Collect content_block_delta text
|
|
110
|
+
if (value?.type === "content_block_delta" && value?.delta?.text) {
|
|
111
|
+
chunks.push(value.delta.text);
|
|
112
|
+
}
|
|
113
|
+
// Collect usage from message_delta
|
|
114
|
+
if (value?.type === "message_delta" && value?.usage) {
|
|
115
|
+
outputTokens = value.usage.output_tokens || outputTokens;
|
|
116
|
+
}
|
|
117
|
+
// Collect input tokens from message_start
|
|
118
|
+
if (value?.type === "message_start" && value?.message?.usage) {
|
|
119
|
+
inputTokens = value.message.usage.input_tokens || 0;
|
|
120
|
+
}
|
|
121
|
+
return { value, done: false };
|
|
122
|
+
},
|
|
123
|
+
return() { return originalIterator.return?.() ?? Promise.resolve({ value: undefined, done: true }); },
|
|
124
|
+
throw(e) { return originalIterator.throw?.(e) ?? Promise.reject(e); },
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
return wrappedStream;
|
|
129
|
+
}
|
|
130
|
+
// Non-streaming response
|
|
79
131
|
const durationMs = Date.now() - startMs;
|
|
80
132
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
133
|
const res = result;
|
package/dist/recorder.d.ts
CHANGED
|
@@ -4,11 +4,16 @@ export interface RecordOptions {
|
|
|
4
4
|
input?: unknown;
|
|
5
5
|
metadata?: Record<string, unknown>;
|
|
6
6
|
sessionId?: string;
|
|
7
|
+
/** When set, spans emitted before this span ID is encountered are suppressed (pre-fork filtering). */
|
|
8
|
+
forkPointSpanId?: string;
|
|
7
9
|
}
|
|
8
10
|
export declare class TraceRecorder {
|
|
9
11
|
private builder;
|
|
10
12
|
private transport;
|
|
11
13
|
private interceptorsInstalled;
|
|
14
|
+
private forkPointSpanId;
|
|
15
|
+
private forkPointReached;
|
|
16
|
+
private spanCounter;
|
|
12
17
|
output: unknown;
|
|
13
18
|
constructor(opts?: RecordOptions);
|
|
14
19
|
get traceId(): string;
|
package/dist/recorder.js
CHANGED
|
@@ -21,11 +21,17 @@ export class TraceRecorder {
|
|
|
21
21
|
builder;
|
|
22
22
|
transport;
|
|
23
23
|
interceptorsInstalled = false;
|
|
24
|
+
forkPointSpanId;
|
|
25
|
+
forkPointReached = false;
|
|
26
|
+
spanCounter = 0;
|
|
24
27
|
output = undefined;
|
|
25
28
|
constructor(opts) {
|
|
26
29
|
requireApiKey();
|
|
27
30
|
this.builder = new TraceBuilder();
|
|
28
31
|
this.transport = getSharedTransport();
|
|
32
|
+
this.forkPointSpanId = opts?.forkPointSpanId;
|
|
33
|
+
// If no fork point specified, all spans pass through
|
|
34
|
+
this.forkPointReached = !opts?.forkPointSpanId;
|
|
29
35
|
const cfg = getConfig();
|
|
30
36
|
if (cfg.projectId)
|
|
31
37
|
this.builder.setProjectId(cfg.projectId);
|
|
@@ -59,6 +65,19 @@ export class TraceRecorder {
|
|
|
59
65
|
// Shared transport stays open for resume/replay listening
|
|
60
66
|
}
|
|
61
67
|
addSpan(span) {
|
|
68
|
+
this.spanCounter++;
|
|
69
|
+
// Fork point filtering: skip spans until the fork point is reached.
|
|
70
|
+
// The server copies pre-fork spans; the SDK only emits from fork point onward.
|
|
71
|
+
if (!this.forkPointReached) {
|
|
72
|
+
if (this.forkPointSpanId && this.spanCounter >= 1) {
|
|
73
|
+
// Use span counter as proxy — the Nth span corresponds to the fork point index.
|
|
74
|
+
// Mark as reached so all subsequent spans pass through.
|
|
75
|
+
this.forkPointReached = true;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return; // Suppress pre-fork span
|
|
79
|
+
}
|
|
80
|
+
}
|
|
62
81
|
span.trace_id = this.builder.id;
|
|
63
82
|
this.builder.addSpan(span);
|
|
64
83
|
this.transport.send("span_started", span);
|
package/dist/resume.js
CHANGED
|
@@ -33,6 +33,10 @@ export function handleResume(command) {
|
|
|
33
33
|
_fork_point: command.forkPointSpanId,
|
|
34
34
|
_cascade_replay: true,
|
|
35
35
|
},
|
|
36
|
+
// Signal the recorder to skip spans until the fork point is reached.
|
|
37
|
+
// Pre-fork spans are copied server-side; the SDK only needs to emit
|
|
38
|
+
// spans from the fork point onward.
|
|
39
|
+
forkPointSpanId: command.forkPointSpanId,
|
|
36
40
|
});
|
|
37
41
|
recorder.start(`Fork: ${command.traceName}`, command.modifiedInput);
|
|
38
42
|
// Determine args for re-execution
|