retrace-sdk 0.1.5 → 0.1.7

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yash Bogam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # retrace
1
+ # retrace-sdk
2
2
 
3
- Record, replay, fork & share AI agent executions — TypeScript SDK.
3
+ The execution replay engine for AI agents. Record, replay, fork & share AI agent executions — TypeScript SDK.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,26 +8,36 @@ Record, replay, fork & share AI agent executions — TypeScript SDK.
8
8
  npm install retrace-sdk
9
9
  ```
10
10
 
11
+ Requires Node.js 20+. ESM-only package.
12
+
11
13
  ## Quick Start
12
14
 
13
15
  ```typescript
14
- import { configure, record, trace, TraceStatus } from "retrace-sdk";
16
+ import { configure, trace } from "retrace-sdk";
15
17
 
16
- configure({ apiKey: "rt_live_...", projectId: "your-project-id" });
18
+ configure({ apiKey: "rt_live_..." }); // Get your key at retrace.yashbogam.me/settings
17
19
 
18
- // Wrap a function with trace()
19
- const myAgent = trace((prompt: string) => {
20
- // OpenAI calls are auto-captured
20
+ const myAgent = trace(async (prompt: string) => {
21
21
  const response = await openai.chat.completions.create({
22
- model: "gpt-4o",
22
+ model: "gpt-5.5",
23
23
  messages: [{ role: "user", content: prompt }],
24
24
  });
25
25
  return response.choices[0].message.content;
26
26
  }, { name: "my-agent" });
27
27
 
28
- const result = myAgent("What is the meaning of life?");
28
+ await myAgent("What is quantum computing?");
29
29
  ```
30
30
 
31
+ ## Auto-Instrumentation
32
+
33
+ LLM calls from all major providers are captured automatically:
34
+
35
+ - **OpenAI** — `openai.chat.completions.create()` captured
36
+ - **Anthropic** — `anthropic.messages.create()` captured
37
+ - **Google Gemini** — `ai.models.generateContent()` captured
38
+
39
+ No extra setup needed. Install the provider SDK alongside `retrace-sdk`.
40
+
31
41
  ## Configuration
32
42
 
33
43
  ```typescript
@@ -35,32 +45,13 @@ import { configure } from "retrace-sdk";
35
45
 
36
46
  configure({
37
47
  apiKey: "rt_live_...", // or RETRACE_API_KEY env var
38
- baseUrl: "http://localhost:3001", // or RETRACE_BASE_URL env var
48
+ baseUrl: "https://api.retrace.yashbogam.me",
39
49
  projectId: "...", // or RETRACE_PROJECT_ID env var
40
50
  });
41
51
  ```
42
52
 
43
53
  Set `RETRACE_ENABLED=false` to disable recording without changing code.
44
54
 
45
- ## OpenAI Auto-Capture
46
-
47
- ```typescript
48
- import { record, TraceStatus } from "retrace-sdk";
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
55
  ## Manual Span Creation
65
56
 
66
57
  ```typescript
@@ -76,19 +67,12 @@ recorder.endSpan(span, { results: ["..."] });
76
67
  recorder.end("Done");
77
68
  ```
78
69
 
79
- ## Transport Options
70
+ ## API Key
80
71
 
81
- The SDK uses WebSocket by default for real-time streaming. Falls back to HTTP if WS connection fails.
72
+ A Retrace API key (`rt_live_...`) is required. Get yours free at [retrace.yashbogam.me/settings](https://retrace.yashbogam.me/settings).
82
73
 
83
- ```typescript
84
- import { createTransport } from "retrace/transport";
85
-
86
- // Force HTTP transport
87
- const transport = createTransport("http");
74
+ ## Links
88
75
 
89
- // Force WebSocket
90
- const transport = createTransport("ws");
91
-
92
- // Auto-detect (default)
93
- const transport = createTransport("auto");
94
- ```
76
+ - [Documentation](https://retrace.yashbogam.me/docs)
77
+ - [GitHub](https://github.com/yash1511-bogam/retrace)
78
+ - [npm](https://www.npmjs.com/package/retrace-sdk)
package/dist/config.d.ts CHANGED
@@ -6,4 +6,5 @@ export interface Config {
6
6
  enabled: boolean;
7
7
  }
8
8
  export declare function configure(opts: Partial<Config>): Config;
9
+ export declare function requireApiKey(): string;
9
10
  export declare function getConfig(): Config;
package/dist/config.js CHANGED
@@ -8,7 +8,7 @@ const config = {
8
8
  config.wsUrl = config.baseUrl.replace("https://", "wss://").replace("http://", "ws://");
9
9
  export function configure(opts) {
10
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.");
11
+ throw new Error("Invalid Retrace API key. Keys must start with 'rt_live_'. Get yours at https://retrace.yashbogam.me/settings");
12
12
  }
13
13
  Object.assign(config, opts);
14
14
  if (opts.baseUrl && !opts.wsUrl) {
@@ -16,6 +16,12 @@ export function configure(opts) {
16
16
  }
17
17
  return config;
18
18
  }
19
+ export function requireApiKey() {
20
+ if (!config.apiKey) {
21
+ throw new Error("Retrace API key required. Call configure({ apiKey: 'rt_live_...' }) or set RETRACE_API_KEY. Get yours at https://retrace.yashbogam.me/settings");
22
+ }
23
+ return config.apiKey;
24
+ }
19
25
  export function getConfig() {
20
26
  return config;
21
27
  }
package/dist/index.d.ts CHANGED
@@ -4,3 +4,5 @@ export { SpanBuilder, TraceBuilder } from "./trace.js";
4
4
  export type { SpanData, TraceData } from "./trace.js";
5
5
  export { SpanType, TraceStatus } from "./trace.js";
6
6
  export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./interceptors/gemini.js";
7
+ export { installOpenAIInterceptor, uninstallOpenAIInterceptor } from "./interceptors/openai.js";
8
+ export { installAnthropicInterceptor, uninstallAnthropicInterceptor } from "./interceptors/anthropic.js";
package/dist/index.js CHANGED
@@ -3,3 +3,5 @@ export { record, trace, TraceRecorder } from "./recorder.js";
3
3
  export { SpanBuilder, TraceBuilder } from "./trace.js";
4
4
  export { SpanType, TraceStatus } from "./trace.js";
5
5
  export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./interceptors/gemini.js";
6
+ export { installOpenAIInterceptor, uninstallOpenAIInterceptor } from "./interceptors/openai.js";
7
+ export { installAnthropicInterceptor, uninstallAnthropicInterceptor } from "./interceptors/anthropic.js";
@@ -0,0 +1,3 @@
1
+ import { SpanData } from "../trace.js";
2
+ export declare function installAnthropicInterceptor(onSpan: (span: SpanData) => void): void;
3
+ export declare function uninstallAnthropicInterceptor(): void;
@@ -0,0 +1,102 @@
1
+ import { SpanType } from "../trace.js";
2
+ import { genId, nowIso, truncateJson } from "../utils.js";
3
+ const PRICING = {
4
+ "claude-opus-4.7": [5.0, 25.0],
5
+ "claude-opus-4.6": [5.0, 25.0],
6
+ "claude-sonnet-4.6": [3.0, 15.0],
7
+ "claude-sonnet-4": [3.0, 15.0],
8
+ "claude-haiku-4.5": [1.0, 5.0],
9
+ "claude-3-7-sonnet": [3.0, 15.0],
10
+ "claude-3-5-sonnet": [3.0, 15.0],
11
+ "claude-3-5-haiku": [0.80, 4.0],
12
+ "claude-3-opus": [15.0, 75.0],
13
+ };
14
+ function calcCost(model, inputTokens, outputTokens) {
15
+ for (const [key, p] of Object.entries(PRICING)) {
16
+ if (model.includes(key))
17
+ return (inputTokens * p[0] + outputTokens * p[1]) / 1_000_000;
18
+ }
19
+ return 0;
20
+ }
21
+ let originalCreate = null;
22
+ let installed = false;
23
+ let onSpanCallback = null;
24
+ export function installAnthropicInterceptor(onSpan) {
25
+ if (installed) {
26
+ onSpanCallback = onSpan;
27
+ return;
28
+ }
29
+ onSpanCallback = onSpan;
30
+ import("@anthropic-ai/sdk").then((anthropicMod) => {
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ const mod = anthropicMod;
33
+ const Anthropic = mod?.Anthropic || mod?.default;
34
+ if (!Anthropic)
35
+ return;
36
+ const proto = Anthropic.Messages?.prototype || Object.getPrototypeOf(new Anthropic({ apiKey: "dummy" }).messages);
37
+ if (!proto?.create)
38
+ return;
39
+ originalCreate = proto.create;
40
+ proto.create = createPatchedCreate();
41
+ installed = true;
42
+ }).catch(() => { });
43
+ }
44
+ function createPatchedCreate() {
45
+ return async function (...args) {
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ const opts = args[0] || {};
48
+ const model = opts.model || "unknown";
49
+ const messages = opts.messages || [];
50
+ const spanId = genId();
51
+ const startedAt = nowIso();
52
+ const startMs = Date.now();
53
+ try {
54
+ const result = await originalCreate.apply(this, args);
55
+ const durationMs = Date.now() - startMs;
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const res = result;
58
+ const inputTokens = res?.usage?.input_tokens || 0;
59
+ const outputTokens = res?.usage?.output_tokens || 0;
60
+ const output = res?.content?.[0]?.text || "";
61
+ const span = {
62
+ id: spanId, trace_id: "", parent_id: null,
63
+ span_type: SpanType.LLM_CALL, name: "anthropic.messages.create", model,
64
+ input: truncateJson({ messages: messages.slice(0, 10) }),
65
+ output: truncateJson(output),
66
+ input_tokens: inputTokens, output_tokens: outputTokens,
67
+ cost: calcCost(model, inputTokens, outputTokens),
68
+ duration_ms: durationMs, started_at: startedAt, ended_at: nowIso(),
69
+ };
70
+ onSpanCallback?.(span);
71
+ return result;
72
+ }
73
+ catch (err) {
74
+ const span = {
75
+ id: spanId, trace_id: "", parent_id: null,
76
+ span_type: SpanType.LLM_CALL, name: "anthropic.messages.create", model,
77
+ input: truncateJson({ messages: messages.slice(0, 10) }),
78
+ started_at: startedAt, ended_at: nowIso(),
79
+ duration_ms: Date.now() - startMs,
80
+ error: err instanceof Error ? err.message : String(err),
81
+ };
82
+ onSpanCallback?.(span);
83
+ throw err;
84
+ }
85
+ };
86
+ }
87
+ export function uninstallAnthropicInterceptor() {
88
+ if (!installed || !originalCreate)
89
+ return;
90
+ import("@anthropic-ai/sdk").then((anthropicMod) => {
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ const mod = anthropicMod;
93
+ const Anthropic = mod?.Anthropic || mod?.default;
94
+ if (!Anthropic)
95
+ return;
96
+ const proto = Anthropic.Messages?.prototype;
97
+ if (proto)
98
+ proto.create = originalCreate;
99
+ }).catch(() => { });
100
+ installed = false;
101
+ onSpanCallback = null;
102
+ }
@@ -1,10 +1,16 @@
1
1
  import { SpanType } from "../trace.js";
2
2
  import { genId, nowIso, truncateJson } from "../utils.js";
3
3
  const PRICING = {
4
+ "gemini-3.1-flash-lite": [0.10, 0.40],
5
+ "gemini-3.1-flash": [0.50, 3.0],
6
+ "gemini-3-flash": [0.50, 3.0],
7
+ "gemini-3-pro": [2.0, 12.0],
4
8
  "gemini-3.1-pro-preview": [2.0, 12.0],
5
9
  "gemini-2.5-pro": [1.25, 10.0],
6
- "gemini-2.5-flash": [0.15, 0.60],
10
+ "gemini-2.5-flash": [0.30, 2.50],
11
+ "gemini-2.5-flash-lite": [0.10, 0.40],
7
12
  "gemini-2.0-flash": [0.10, 0.40],
13
+ "gemini-2.0-flash-lite": [0.05, 0.20],
8
14
  };
9
15
  function calcCost(model, inputTokens, outputTokens) {
10
16
  const p = PRICING[model] || [0, 0];
@@ -1 +1,3 @@
1
1
  export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./gemini.js";
2
+ export { installOpenAIInterceptor, uninstallOpenAIInterceptor } from "./openai.js";
3
+ export { installAnthropicInterceptor, uninstallAnthropicInterceptor } from "./anthropic.js";
@@ -1 +1,3 @@
1
1
  export { installGeminiInterceptor, uninstallGeminiInterceptor } from "./gemini.js";
2
+ export { installOpenAIInterceptor, uninstallOpenAIInterceptor } from "./openai.js";
3
+ export { installAnthropicInterceptor, uninstallAnthropicInterceptor } from "./anthropic.js";
@@ -0,0 +1,3 @@
1
+ import { SpanData } from "../trace.js";
2
+ export declare function installOpenAIInterceptor(onSpan: (span: SpanData) => void): void;
3
+ export declare function uninstallOpenAIInterceptor(): void;
@@ -0,0 +1,115 @@
1
+ import { SpanType } from "../trace.js";
2
+ import { genId, nowIso, truncateJson } from "../utils.js";
3
+ const PRICING = {
4
+ "gpt-5.5-pro": [30.0, 180.0],
5
+ "gpt-5.5": [5.0, 30.0],
6
+ "gpt-5.4-pro": [15.0, 90.0],
7
+ "gpt-5.4-mini": [0.75, 4.50],
8
+ "gpt-5.4-nano": [0.20, 1.20],
9
+ "gpt-5.4": [2.50, 15.0],
10
+ "gpt-5-mini": [0.50, 3.0],
11
+ "gpt-5-nano": [0.10, 0.60],
12
+ "gpt-5": [1.25, 10.0],
13
+ "gpt-4.1-mini": [0.40, 1.60],
14
+ "gpt-4.1-nano": [0.10, 0.40],
15
+ "gpt-4.1": [2.0, 8.0],
16
+ "gpt-4o-mini": [0.15, 0.60],
17
+ "gpt-4o": [2.50, 10.0],
18
+ "o3": [10.0, 40.0],
19
+ "o4-mini": [1.10, 4.40],
20
+ "o3-mini": [1.10, 4.40],
21
+ };
22
+ function calcCost(model, inputTokens, outputTokens) {
23
+ for (const [key, p] of Object.entries(PRICING)) {
24
+ if (model.includes(key))
25
+ return (inputTokens * p[0] + outputTokens * p[1]) / 1_000_000;
26
+ }
27
+ return 0;
28
+ }
29
+ let originalCreate = null;
30
+ let installed = false;
31
+ let onSpanCallback = null;
32
+ export function installOpenAIInterceptor(onSpan) {
33
+ if (installed) {
34
+ onSpanCallback = onSpan;
35
+ return;
36
+ }
37
+ onSpanCallback = onSpan;
38
+ import("openai").then((openaiMod) => {
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ const mod = openaiMod;
41
+ const proto = mod?.OpenAI?.Chat?.Completions?.prototype || mod?.default?.Chat?.Completions?.prototype;
42
+ if (!proto?.create) {
43
+ // Try alternative path
44
+ const OpenAI = mod?.OpenAI || mod?.default;
45
+ if (OpenAI) {
46
+ const instance = Object.getPrototypeOf(new OpenAI({ apiKey: "dummy" }).chat.completions);
47
+ if (instance?.create) {
48
+ originalCreate = instance.create;
49
+ instance.create = createPatchedCreate();
50
+ installed = true;
51
+ }
52
+ }
53
+ return;
54
+ }
55
+ originalCreate = proto.create;
56
+ proto.create = createPatchedCreate();
57
+ installed = true;
58
+ }).catch(() => { });
59
+ }
60
+ function createPatchedCreate() {
61
+ return async function (...args) {
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ const opts = args[0] || {};
64
+ const model = opts.model || "unknown";
65
+ const messages = opts.messages || [];
66
+ const spanId = genId();
67
+ const startedAt = nowIso();
68
+ const startMs = Date.now();
69
+ try {
70
+ const result = await originalCreate.apply(this, args);
71
+ const durationMs = Date.now() - startMs;
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ const res = result;
74
+ const inputTokens = res?.usage?.prompt_tokens || 0;
75
+ const outputTokens = res?.usage?.completion_tokens || 0;
76
+ const output = res?.choices?.[0]?.message?.content || "";
77
+ const span = {
78
+ id: spanId, trace_id: "", parent_id: null,
79
+ span_type: SpanType.LLM_CALL, name: "openai.chat.completions.create", model,
80
+ input: truncateJson({ messages: messages.slice(0, 10) }),
81
+ output: truncateJson(output),
82
+ input_tokens: inputTokens, output_tokens: outputTokens,
83
+ cost: calcCost(model, inputTokens, outputTokens),
84
+ duration_ms: durationMs, started_at: startedAt, ended_at: nowIso(),
85
+ };
86
+ onSpanCallback?.(span);
87
+ return result;
88
+ }
89
+ catch (err) {
90
+ const span = {
91
+ id: spanId, trace_id: "", parent_id: null,
92
+ span_type: SpanType.LLM_CALL, name: "openai.chat.completions.create", model,
93
+ input: truncateJson({ messages: messages.slice(0, 10) }),
94
+ started_at: startedAt, ended_at: nowIso(),
95
+ duration_ms: Date.now() - startMs,
96
+ error: err instanceof Error ? err.message : String(err),
97
+ };
98
+ onSpanCallback?.(span);
99
+ throw err;
100
+ }
101
+ };
102
+ }
103
+ export function uninstallOpenAIInterceptor() {
104
+ if (!installed || !originalCreate)
105
+ return;
106
+ import("openai").then((openaiMod) => {
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ const mod = openaiMod;
109
+ const proto = mod?.OpenAI?.Chat?.Completions?.prototype || mod?.default?.Chat?.Completions?.prototype;
110
+ if (proto)
111
+ proto.create = originalCreate;
112
+ }).catch(() => { });
113
+ installed = false;
114
+ onSpanCallback = null;
115
+ }
package/dist/recorder.js CHANGED
@@ -1,13 +1,16 @@
1
- import { getConfig } from "./config.js";
1
+ import { getConfig, requireApiKey } from "./config.js";
2
2
  import { SpanBuilder, SpanType, TraceBuilder, TraceStatus } from "./trace.js";
3
3
  import { createTransport } from "./transport.js";
4
4
  import { installGeminiInterceptor } from "./interceptors/gemini.js";
5
+ import { installOpenAIInterceptor } from "./interceptors/openai.js";
6
+ import { installAnthropicInterceptor } from "./interceptors/anthropic.js";
5
7
  export class TraceRecorder {
6
8
  builder;
7
9
  transport;
8
10
  interceptorsInstalled = false;
9
11
  output = undefined;
10
12
  constructor(opts) {
13
+ requireApiKey();
11
14
  this.builder = new TraceBuilder();
12
15
  this.transport = createTransport();
13
16
  const cfg = getConfig();
@@ -84,6 +87,8 @@ export class TraceRecorder {
84
87
  if (this.interceptorsInstalled)
85
88
  return;
86
89
  installGeminiInterceptor((span) => this.addSpan(span));
90
+ installOpenAIInterceptor((span) => this.addSpan(span));
91
+ installAnthropicInterceptor((span) => this.addSpan(span));
87
92
  this.interceptorsInstalled = true;
88
93
  }
89
94
  }
@@ -5,8 +5,10 @@ export interface Transport {
5
5
  export declare class WSTransport implements Transport {
6
6
  private ws;
7
7
  private connected;
8
+ private closed;
8
9
  private backoff;
9
10
  private queue;
11
+ get isConnected(): boolean;
10
12
  connect(): void;
11
13
  private reconnect;
12
14
  private flushQueue;
package/dist/transport.js CHANGED
@@ -3,9 +3,13 @@ import { getConfig } from "./config.js";
3
3
  export class WSTransport {
4
4
  ws = null;
5
5
  connected = false;
6
+ closed = false;
6
7
  backoff = 1000;
7
8
  queue = [];
9
+ get isConnected() { return this.connected; }
8
10
  connect() {
11
+ if (this.closed)
12
+ return;
9
13
  const cfg = getConfig();
10
14
  const url = `${cfg.wsUrl}/ws/v1/stream`;
11
15
  this.ws = new WebSocket(url);
@@ -26,15 +30,17 @@ export class WSTransport {
26
30
  this.ws.on("close", () => {
27
31
  this.connected = false;
28
32
  this.ws = null;
29
- setTimeout(() => this.reconnect(), this.backoff);
30
- this.backoff = Math.min(this.backoff * 2, 30000);
33
+ if (!this.closed) {
34
+ setTimeout(() => this.reconnect(), this.backoff);
35
+ this.backoff = Math.min(this.backoff * 2, 30000);
36
+ }
31
37
  });
32
38
  this.ws.on("error", () => {
33
39
  this.ws?.close();
34
40
  });
35
41
  }
36
42
  reconnect() {
37
- if (!this.connected && !this.ws)
43
+ if (!this.closed && !this.connected && !this.ws)
38
44
  this.connect();
39
45
  }
40
46
  flushQueue() {
@@ -49,11 +55,12 @@ export class WSTransport {
49
55
  }
50
56
  else {
51
57
  this.queue.push(msg);
52
- if (!this.ws)
58
+ if (!this.ws && !this.closed)
53
59
  this.connect();
54
60
  }
55
61
  }
56
62
  close() {
63
+ this.closed = true;
57
64
  if (this.ws) {
58
65
  this.ws.close();
59
66
  this.ws = null;
@@ -114,11 +121,61 @@ export function createTransport(mode = "auto") {
114
121
  return new HTTPTransport();
115
122
  if (mode === "ws")
116
123
  return new WSTransport();
117
- // Auto: try WS
118
- try {
119
- return new WSTransport();
120
- }
121
- catch {
122
- return new HTTPTransport();
123
- }
124
+ // Auto: start with WS, fall back to HTTP if connection fails within timeout
125
+ const ws = new WSTransport();
126
+ const http = new HTTPTransport();
127
+ let useWs = true;
128
+ let decided = false;
129
+ const buffer = [];
130
+ const fallbackTimer = setTimeout(() => {
131
+ if (!decided && !ws.isConnected) {
132
+ decided = true;
133
+ useWs = false;
134
+ ws.close();
135
+ for (const item of buffer.splice(0))
136
+ http.send(item.eventType, item.data);
137
+ }
138
+ }, 5000);
139
+ ws.connect();
140
+ const originalSend = ws.send.bind(ws);
141
+ const checkConnected = () => {
142
+ if (!decided && ws.isConnected) {
143
+ decided = true;
144
+ clearTimeout(fallbackTimer);
145
+ for (const item of buffer.splice(0))
146
+ originalSend(item.eventType, item.data);
147
+ }
148
+ };
149
+ return {
150
+ send(eventType, data) {
151
+ checkConnected();
152
+ if (decided) {
153
+ if (useWs)
154
+ originalSend(eventType, data);
155
+ else
156
+ http.send(eventType, data);
157
+ }
158
+ else {
159
+ buffer.push({ eventType, data });
160
+ }
161
+ },
162
+ close() {
163
+ clearTimeout(fallbackTimer);
164
+ if (!decided) {
165
+ // WS not yet connected — force HTTP fallback to avoid data loss
166
+ decided = true;
167
+ useWs = false;
168
+ ws.close();
169
+ for (const item of buffer.splice(0))
170
+ http.send(item.eventType, item.data);
171
+ http.close();
172
+ }
173
+ else if (useWs) {
174
+ ws.close();
175
+ }
176
+ else {
177
+ http.close();
178
+ }
179
+ },
180
+ };
124
181
  }
package/package.json CHANGED
@@ -1,37 +1,61 @@
1
1
  {
2
2
  "name": "retrace-sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
12
  }
13
13
  },
14
+ "files": ["dist", "README.md", "LICENSE"],
15
+ "license": "MIT",
16
+ "author": "Yash Bogam",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/yash1511-bogam/retrace",
20
+ "directory": "packages/sdk-typescript"
21
+ },
22
+ "homepage": "https://retrace.yashbogam.me/docs/sdk-typescript",
23
+ "keywords": ["ai", "agent", "tracing", "observability", "replay", "llm", "openai", "anthropic", "gemini"],
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
14
27
  "scripts": {
15
28
  "dev": "tsc --watch",
16
29
  "build": "tsc",
17
30
  "lint": "eslint src/",
18
- "test": "vitest run"
31
+ "test": "vitest run",
32
+ "prepublishOnly": "npm run build"
19
33
  },
20
34
  "dependencies": {
21
35
  "ws": "8.20.0"
22
36
  },
23
37
  "peerDependencies": {
24
- "@google/genai": ">=1.52.0"
38
+ "@google/genai": ">=1.52.0",
39
+ "openai": ">=4.0.0",
40
+ "@anthropic-ai/sdk": ">=0.30.0"
25
41
  },
26
42
  "peerDependenciesMeta": {
27
43
  "@google/genai": {
28
44
  "optional": true
45
+ },
46
+ "openai": {
47
+ "optional": true
48
+ },
49
+ "@anthropic-ai/sdk": {
50
+ "optional": true
29
51
  }
30
52
  },
31
53
  "devDependencies": {
32
54
  "@google/genai": "^1.52.0",
33
55
  "@types/node": "22.15.3",
34
56
  "@types/ws": "8.18.1",
35
- "typescript": "6.0.3"
57
+ "typescript": "6.0.3",
58
+ "openai": "^4.90.0",
59
+ "@anthropic-ai/sdk": "^0.95.0"
36
60
  }
37
61
  }
@@ -1 +0,0 @@
1
- export {};