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 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;
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retrace-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "The execution replay engine for AI agents. Record, replay, fork, and share agent executions.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",