@lmnr-ai/claude-code-proxy 0.1.8 → 0.1.9

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.d.ts CHANGED
@@ -10,15 +10,80 @@ export interface SpanContext {
10
10
  laminarUrl?: string;
11
11
  }
12
12
  /**
13
- * Set the current trace context by sending an HTTP request to the running proxy server.
13
+ * A Claude Code Proxy server instance that can be run on a specific port.
14
14
  *
15
- * @param context - The span context containing trace information
16
- * @throws Error if the HTTP request fails
15
+ * Each instance manages its own server state, health monitoring,
16
+ * and can be started/stopped independently.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Create multiple proxy instances on different ports
21
+ * const proxy1 = new ProxyServer(45667);
22
+ * const proxy2 = new ProxyServer(45668);
23
+ *
24
+ * proxy1.runServer('https://api.anthropic.com');
25
+ * proxy2.runServer('https://api.anthropic.com');
26
+ *
27
+ * await proxy1.setCurrentTrace({ traceId: '...', spanId: '...', projectApiKey: '...' });
28
+ * await proxy2.setCurrentTrace({ traceId: '...', spanId: '...', projectApiKey: '...' });
29
+ *
30
+ * proxy1.stopServer();
31
+ * proxy2.stopServer();
32
+ * ```
17
33
  */
18
- export declare function setCurrentTrace(context: SpanContext): Promise<void>;
34
+ export declare class ProxyServer {
35
+ private port;
36
+ private targetUrl;
37
+ private monitorInterval;
38
+ private isHealthCheckRunning;
39
+ private _isRunning;
40
+ /**
41
+ * Initialize a new proxy server instance.
42
+ *
43
+ * @param port - The port to listen on (default: 45667)
44
+ */
45
+ constructor(port?: number);
46
+ /**
47
+ * Set the current trace context by sending an HTTP request to this proxy server.
48
+ *
49
+ * @param context - The span context containing trace information
50
+ * @throws Error if the HTTP request fails
51
+ */
52
+ setCurrentTrace(context: SpanContext): Promise<void>;
53
+ /**
54
+ * Background function that monitors server health and restarts if needed.
55
+ * Polls every HEALTH_CHECK_INTERVAL ms.
56
+ * Uses a flag to prevent concurrent execution, avoiding race conditions.
57
+ */
58
+ private healthCheckMonitor;
59
+ /**
60
+ * Run the proxy server in a background thread with health monitoring.
61
+ *
62
+ * A background monitor will check the server health every HEALTH_CHECK_INTERVAL ms
63
+ * and automatically restart it if it dies.
64
+ *
65
+ * @param url - The target URL to proxy requests to
66
+ */
67
+ runServer(url: string): void;
68
+ /**
69
+ * Stop the proxy server and its health monitoring.
70
+ */
71
+ stopServer(): void;
72
+ /**
73
+ * Check if the server is currently running.
74
+ */
75
+ get isRunning(): boolean;
76
+ /**
77
+ * Get the port this server is running on.
78
+ */
79
+ getPort(): number;
80
+ }
19
81
  /**
20
82
  * Run the proxy server in a background thread with health monitoring.
21
83
  *
84
+ * This is a backwards-compatible function that uses a global singleton instance.
85
+ * For more control, consider creating your own ProxyServer instance.
86
+ *
22
87
  * A background monitor will check the server health every HEALTH_CHECK_INTERVAL ms
23
88
  * and automatically restart it if it dies.
24
89
  *
@@ -28,6 +93,19 @@ export declare function setCurrentTrace(context: SpanContext): Promise<void>;
28
93
  export declare function runServer(url: string, port?: number): void;
29
94
  /**
30
95
  * Stop the proxy server and its health monitoring.
96
+ *
97
+ * This is a backwards-compatible function that uses a global singleton instance.
98
+ * For more control, consider creating your own ProxyServer instance.
31
99
  */
32
100
  export declare function stopServer(): void;
101
+ /**
102
+ * Set the current trace context by sending an HTTP request to the running proxy server.
103
+ *
104
+ * This is a backwards-compatible function that uses a global singleton instance.
105
+ * For more control, consider creating your own ProxyServer instance.
106
+ *
107
+ * @param context - The span context containing trace information
108
+ * @throws Error if the HTTP request fails
109
+ */
110
+ export declare function setCurrentTrace(context: SpanContext): Promise<void>;
33
111
  export declare const run: any, stop: any;
package/dist/index.js CHANGED
@@ -3,10 +3,10 @@
3
3
  * Laminar Claude Code Proxy - TypeScript/JavaScript bindings for HTTP proxy server
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.stop = exports.run = void 0;
7
- exports.setCurrentTrace = setCurrentTrace;
6
+ exports.stop = exports.run = exports.ProxyServer = void 0;
8
7
  exports.runServer = runServer;
9
8
  exports.stopServer = stopServer;
9
+ exports.setCurrentTrace = setCurrentTrace;
10
10
  // Platform-specific package loading
11
11
  // Uses explicit .node subpath exports for bundler compatibility=
12
12
  const { join } = require('path');
@@ -47,101 +47,208 @@ function loadNative() {
47
47
  }
48
48
  const native = loadNative();
49
49
  const HEALTH_CHECK_INTERVAL = 1000;
50
- // Module-level variables to track the server state
51
- let serverPort = 45667;
52
- let targetUrl = '';
53
- let monitorInterval = null;
54
- let isHealthCheckRunning = false;
55
50
  /**
56
- * Set the current trace context by sending an HTTP request to the running proxy server.
51
+ * A Claude Code Proxy server instance that can be run on a specific port.
57
52
  *
58
- * @param context - The span context containing trace information
59
- * @throws Error if the HTTP request fails
60
- */
61
- async function setCurrentTrace(context) {
62
- const { traceId, spanId, projectApiKey, spanIdsPath = [], spanPath = [], laminarUrl = 'https://api.lmnr.ai', } = context;
63
- // Prepare the JSON payload
64
- const payload = {
65
- trace_id: traceId,
66
- span_id: spanId,
67
- project_api_key: projectApiKey,
68
- span_ids_path: spanIdsPath,
69
- span_path: spanPath,
70
- laminar_url: laminarUrl,
71
- };
72
- // Send POST request to the internal endpoint
73
- const url = `http://127.0.0.1:${serverPort}/lmnr-internal/span-context`;
74
- try {
75
- const response = await fetch(url, {
76
- method: 'POST',
77
- headers: {
78
- 'Content-Type': 'application/json',
79
- },
80
- body: JSON.stringify(payload),
81
- });
82
- if (!response.ok) {
83
- throw new Error(`HTTP ${response.status}`);
84
- }
85
- }
86
- catch (error) {
87
- throw new Error(`Failed to set trace context: ${error}`);
88
- }
89
- }
90
- /**
91
- * Background function that monitors server health and restarts if needed.
92
- * Polls every HEALTH_CHECK_INTERVAL ms.
93
- * Uses a flag to prevent concurrent execution, avoiding race conditions.
53
+ * Each instance manages its own server state, health monitoring,
54
+ * and can be started/stopped independently.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * // Create multiple proxy instances on different ports
59
+ * const proxy1 = new ProxyServer(45667);
60
+ * const proxy2 = new ProxyServer(45668);
61
+ *
62
+ * proxy1.runServer('https://api.anthropic.com');
63
+ * proxy2.runServer('https://api.anthropic.com');
64
+ *
65
+ * await proxy1.setCurrentTrace({ traceId: '...', spanId: '...', projectApiKey: '...' });
66
+ * await proxy2.setCurrentTrace({ traceId: '...', spanId: '...', projectApiKey: '...' });
67
+ *
68
+ * proxy1.stopServer();
69
+ * proxy2.stopServer();
70
+ * ```
94
71
  */
95
- async function healthCheckMonitor() {
96
- // Skip if a health check is already running to prevent concurrent restarts
97
- if (isHealthCheckRunning) {
98
- return;
72
+ class ProxyServer {
73
+ /**
74
+ * Initialize a new proxy server instance.
75
+ *
76
+ * @param port - The port to listen on (default: 45667)
77
+ */
78
+ constructor(port = 45667) {
79
+ this.targetUrl = '';
80
+ this.monitorInterval = null;
81
+ this.isHealthCheckRunning = false;
82
+ this._isRunning = false;
83
+ this.port = port;
99
84
  }
100
- isHealthCheckRunning = true;
101
- try {
102
- const url = `http://127.0.0.1:${serverPort}/lmnr-internal/health`;
85
+ /**
86
+ * Set the current trace context by sending an HTTP request to this proxy server.
87
+ *
88
+ * @param context - The span context containing trace information
89
+ * @throws Error if the HTTP request fails
90
+ */
91
+ async setCurrentTrace(context) {
92
+ const { traceId, spanId, projectApiKey, spanIdsPath = [], spanPath = [], laminarUrl = 'https://api.lmnr.ai', } = context;
93
+ // Prepare the JSON payload
94
+ const payload = {
95
+ trace_id: traceId,
96
+ span_id: spanId,
97
+ project_api_key: projectApiKey,
98
+ span_ids_path: spanIdsPath,
99
+ span_path: spanPath,
100
+ laminar_url: laminarUrl,
101
+ };
102
+ // Send POST request to the internal endpoint
103
+ const url = `http://127.0.0.1:${this.port}/lmnr-internal/span-context`;
103
104
  try {
104
105
  const response = await fetch(url, {
105
- // Force new connection for each health check to avoid interfering with streaming
106
+ method: 'POST',
106
107
  headers: {
107
- 'Connection': 'close'
108
+ 'Content-Type': 'application/json',
108
109
  },
110
+ body: JSON.stringify(payload),
109
111
  });
110
- if (response.status === 200) {
111
- // Server is healthy
112
- return;
112
+ if (!response.ok) {
113
+ throw new Error(`HTTP ${response.status}`);
113
114
  }
114
115
  }
115
116
  catch (error) {
116
- // Server is not responding, try to restart
117
- console.warn(`Server health check failed: ${error}. Attempting to restart...`);
117
+ throw new Error(`Failed to set trace context: ${error}`);
118
+ }
119
+ }
120
+ /**
121
+ * Background function that monitors server health and restarts if needed.
122
+ * Polls every HEALTH_CHECK_INTERVAL ms.
123
+ * Uses a flag to prevent concurrent execution, avoiding race conditions.
124
+ */
125
+ async healthCheckMonitor() {
126
+ // Skip if a health check is already running to prevent concurrent restarts
127
+ if (this.isHealthCheckRunning) {
128
+ return;
129
+ }
130
+ this.isHealthCheckRunning = true;
131
+ try {
132
+ const url = `http://127.0.0.1:${this.port}/lmnr-internal/health`;
118
133
  try {
119
- // Try to stop the existing server (it might be stuck)
134
+ const response = await fetch(url, {
135
+ // Force new connection for each health check to avoid interfering with streaming
136
+ headers: {
137
+ 'Connection': 'close'
138
+ },
139
+ });
140
+ if (response.status === 200) {
141
+ // Server is healthy
142
+ return;
143
+ }
144
+ }
145
+ catch (error) {
146
+ // Server is not responding, try to restart
147
+ console.warn(`Server health check failed on port ${this.port}: ${error}. Attempting to restart...`);
120
148
  try {
121
- native.stop();
149
+ // Try to stop the existing server (it might be stuck)
150
+ try {
151
+ native.stop(this.port);
152
+ }
153
+ catch {
154
+ // Server might already be dead
155
+ }
156
+ // Wait a bit before restarting
157
+ await new Promise(resolve => setTimeout(resolve, 100));
158
+ // Restart the server
159
+ native.run(this.targetUrl, this.port);
160
+ console.info(`Server restarted successfully on port ${this.port}`);
122
161
  }
123
- catch {
124
- // Server might already be dead
162
+ catch (restartError) {
163
+ console.error(`Failed to restart server on port ${this.port}: ${restartError}`);
125
164
  }
126
- // Wait a bit before restarting
127
- await new Promise(resolve => setTimeout(resolve, 100));
128
- // Restart the server
129
- native.run(targetUrl, serverPort);
130
- console.info('Server restarted successfully');
131
- }
132
- catch (restartError) {
133
- console.error(`Failed to restart server: ${restartError}`);
134
165
  }
135
166
  }
167
+ finally {
168
+ // Always reset the flag when done, even if an error occurred
169
+ this.isHealthCheckRunning = false;
170
+ }
171
+ }
172
+ /**
173
+ * Run the proxy server in a background thread with health monitoring.
174
+ *
175
+ * A background monitor will check the server health every HEALTH_CHECK_INTERVAL ms
176
+ * and automatically restart it if it dies.
177
+ *
178
+ * @param url - The target URL to proxy requests to
179
+ */
180
+ runServer(url) {
181
+ // Stop any existing monitoring
182
+ if (this.monitorInterval !== null) {
183
+ clearInterval(this.monitorInterval);
184
+ this.monitorInterval = null;
185
+ }
186
+ // Store server configuration
187
+ this.targetUrl = url;
188
+ // Start the server
189
+ try {
190
+ native.run(url, this.port);
191
+ this._isRunning = true;
192
+ }
193
+ catch (error) {
194
+ console.error(`Error running the proxy server on port ${this.port}: ${error}`);
195
+ throw error;
196
+ }
197
+ // Start the health check monitor
198
+ this.monitorInterval = setInterval(() => {
199
+ this.healthCheckMonitor().catch(() => { });
200
+ }, HEALTH_CHECK_INTERVAL);
201
+ console.info(`Started proxy server with health monitoring on port ${this.port}`);
202
+ }
203
+ /**
204
+ * Stop the proxy server and its health monitoring.
205
+ */
206
+ stopServer() {
207
+ // Stop the monitor
208
+ if (this.monitorInterval !== null) {
209
+ clearInterval(this.monitorInterval);
210
+ this.monitorInterval = null;
211
+ }
212
+ // Reset the health check flag
213
+ this.isHealthCheckRunning = false;
214
+ // Stop the server
215
+ try {
216
+ native.stop(this.port);
217
+ this._isRunning = false;
218
+ }
219
+ catch (error) {
220
+ console.debug(`Error stopping the proxy server on port ${this.port}: ${error}`);
221
+ }
222
+ console.debug(`Stopped proxy server and health monitoring on port ${this.port}`);
136
223
  }
137
- finally {
138
- // Always reset the flag when done, even if an error occurred
139
- isHealthCheckRunning = false;
224
+ /**
225
+ * Check if the server is currently running.
226
+ */
227
+ get isRunning() {
228
+ return this._isRunning;
229
+ }
230
+ /**
231
+ * Get the port this server is running on.
232
+ */
233
+ getPort() {
234
+ return this.port;
140
235
  }
141
236
  }
237
+ exports.ProxyServer = ProxyServer;
238
+ // Singleton instance for backwards compatibility
239
+ let globalProxy = null;
240
+ function getGlobalProxy(port = 45667) {
241
+ if (globalProxy === null) {
242
+ globalProxy = new ProxyServer(port);
243
+ }
244
+ return globalProxy;
245
+ }
142
246
  /**
143
247
  * Run the proxy server in a background thread with health monitoring.
144
248
  *
249
+ * This is a backwards-compatible function that uses a global singleton instance.
250
+ * For more control, consider creating your own ProxyServer instance.
251
+ *
145
252
  * A background monitor will check the server health every HEALTH_CHECK_INTERVAL ms
146
253
  * and automatically restart it if it dies.
147
254
  *
@@ -149,43 +256,38 @@ async function healthCheckMonitor() {
149
256
  * @param port - The port to listen on (default: 45667)
150
257
  */
151
258
  function runServer(url, port = 45667) {
152
- // Stop any existing monitoring
153
- if (monitorInterval !== null) {
154
- clearInterval(monitorInterval);
155
- monitorInterval = null;
156
- }
157
- // Store server configuration
158
- serverPort = port;
159
- targetUrl = url;
160
- // Start the server
161
- try {
162
- native.run(url, port);
163
- }
164
- catch (error) {
165
- console.error(`Error running the proxy server: ${error}`);
166
- throw error;
259
+ // If port is different from current global instance, create a new one
260
+ const proxy = getGlobalProxy(port);
261
+ if (proxy.getPort() !== port) {
262
+ // Stop old instance if running
263
+ if (proxy.isRunning) {
264
+ proxy.stopServer();
265
+ }
266
+ // Create new instance with the requested port
267
+ globalProxy = new ProxyServer(port);
167
268
  }
168
- // Start the health check monitor
169
- monitorInterval = setInterval(() => {
170
- healthCheckMonitor().catch(() => { });
171
- }, HEALTH_CHECK_INTERVAL);
269
+ getGlobalProxy().runServer(url);
172
270
  }
173
271
  /**
174
272
  * Stop the proxy server and its health monitoring.
273
+ *
274
+ * This is a backwards-compatible function that uses a global singleton instance.
275
+ * For more control, consider creating your own ProxyServer instance.
175
276
  */
176
277
  function stopServer() {
177
- // Stop the monitor
178
- if (monitorInterval !== null) {
179
- clearInterval(monitorInterval);
180
- monitorInterval = null;
181
- }
182
- // Reset the health check flag
183
- isHealthCheckRunning = false;
184
- // Stop the server
185
- try {
186
- native.stop();
187
- }
188
- catch { }
278
+ getGlobalProxy().stopServer();
279
+ }
280
+ /**
281
+ * Set the current trace context by sending an HTTP request to the running proxy server.
282
+ *
283
+ * This is a backwards-compatible function that uses a global singleton instance.
284
+ * For more control, consider creating your own ProxyServer instance.
285
+ *
286
+ * @param context - The span context containing trace information
287
+ * @throws Error if the HTTP request fails
288
+ */
289
+ async function setCurrentTrace(context) {
290
+ return getGlobalProxy().setCurrentTrace(context);
189
291
  }
190
292
  // Re-export native functions for direct access if needed
191
293
  exports.run = native.run, exports.stop = native.stop;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lmnr-ai/claude-code-proxy",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "HTTP proxy for Claude Desktop API requests with Laminar observability",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,13 +23,13 @@
23
23
  "dist"
24
24
  ],
25
25
  "optionalDependencies": {
26
- "@lmnr-ai/claude-code-proxy-darwin-arm64": "0.1.8",
27
- "@lmnr-ai/claude-code-proxy-darwin-x64": "0.1.8",
28
- "@lmnr-ai/claude-code-proxy-win32-x64-msvc": "0.1.8",
29
- "@lmnr-ai/claude-code-proxy-linux-x64-gnu": "0.1.8",
30
- "@lmnr-ai/claude-code-proxy-linux-arm64-gnu": "0.1.8",
31
- "@lmnr-ai/claude-code-proxy-linux-x64-musl": "0.1.8",
32
- "@lmnr-ai/claude-code-proxy-linux-arm64-musl": "0.1.8"
26
+ "@lmnr-ai/claude-code-proxy-darwin-arm64": "0.1.9",
27
+ "@lmnr-ai/claude-code-proxy-darwin-x64": "0.1.9",
28
+ "@lmnr-ai/claude-code-proxy-win32-x64-msvc": "0.1.9",
29
+ "@lmnr-ai/claude-code-proxy-linux-x64-gnu": "0.1.9",
30
+ "@lmnr-ai/claude-code-proxy-linux-arm64-gnu": "0.1.9",
31
+ "@lmnr-ai/claude-code-proxy-linux-x64-musl": "0.1.9",
32
+ "@lmnr-ai/claude-code-proxy-linux-arm64-musl": "0.1.9"
33
33
  },
34
34
  "keywords": [
35
35
  "claude",