@blockrun/clawrouter 0.6.4 → 0.6.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/dist/index.js CHANGED
@@ -467,6 +467,7 @@ var blockrunProvider = {
467
467
 
468
468
  // src/proxy.ts
469
469
  import { createServer } from "http";
470
+ import { finished } from "stream";
470
471
  import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
471
472
 
472
473
  // src/x402.ts
@@ -2365,6 +2366,15 @@ function prioritizeNonRateLimited(models) {
2365
2366
  }
2366
2367
  return [...available, ...rateLimited];
2367
2368
  }
2369
+ function canWrite(res) {
2370
+ return !res.writableEnded && !res.destroyed && res.socket !== null && !res.socket.destroyed && res.socket.writable;
2371
+ }
2372
+ function safeWrite(res, data) {
2373
+ if (!canWrite(res)) {
2374
+ return false;
2375
+ }
2376
+ return res.write(data);
2377
+ }
2368
2378
  var BALANCE_CHECK_BUFFER = 1.5;
2369
2379
  function getProxyPort() {
2370
2380
  const envPort = process.env.BLOCKRUN_PROXY_PORT;
@@ -2573,7 +2583,24 @@ async function startProxy(options) {
2573
2583
  };
2574
2584
  const deduplicator = new RequestDeduplicator();
2575
2585
  const sessionStore = new SessionStore(options.sessionConfig);
2586
+ const connections = /* @__PURE__ */ new Set();
2576
2587
  const server = createServer(async (req, res) => {
2588
+ req.on("error", (err) => {
2589
+ console.error(`[ClawRouter] Request stream error: ${err.message}`);
2590
+ });
2591
+ res.on("error", (err) => {
2592
+ console.error(`[ClawRouter] Response stream error: ${err.message}`);
2593
+ });
2594
+ finished(res, (err) => {
2595
+ if (err && err.code !== "ERR_STREAM_DESTROYED") {
2596
+ console.error(`[ClawRouter] Response finished with error: ${err.message}`);
2597
+ }
2598
+ });
2599
+ finished(req, (err) => {
2600
+ if (err && err.code !== "ERR_STREAM_DESTROYED") {
2601
+ console.error(`[ClawRouter] Request finished with error: ${err.message}`);
2602
+ }
2603
+ });
2577
2604
  if (req.url === "/health" || req.url?.startsWith("/health?")) {
2578
2605
  const url = new URL(req.url, "http://localhost");
2579
2606
  const full = url.searchParams.get("full") === "true";
@@ -2686,14 +2713,54 @@ async function startProxy(options) {
2686
2713
  const port = addr.port;
2687
2714
  const baseUrl = `http://127.0.0.1:${port}`;
2688
2715
  options.onReady?.(port);
2716
+ server.on("error", (err) => {
2717
+ console.error(`[ClawRouter] Server runtime error: ${err.message}`);
2718
+ options.onError?.(err);
2719
+ });
2720
+ server.on("clientError", (err, socket) => {
2721
+ console.error(`[ClawRouter] Client error: ${err.message}`);
2722
+ if (socket.writable && !socket.destroyed) {
2723
+ socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
2724
+ }
2725
+ });
2726
+ server.on("connection", (socket) => {
2727
+ connections.add(socket);
2728
+ socket.setTimeout(3e5);
2729
+ socket.on("timeout", () => {
2730
+ console.error(`[ClawRouter] Socket timeout, destroying connection`);
2731
+ socket.destroy();
2732
+ });
2733
+ socket.on("end", () => {
2734
+ });
2735
+ socket.on("error", (err) => {
2736
+ console.error(`[ClawRouter] Socket error: ${err.message}`);
2737
+ });
2738
+ socket.on("close", () => {
2739
+ connections.delete(socket);
2740
+ });
2741
+ });
2689
2742
  resolve({
2690
2743
  port,
2691
2744
  baseUrl,
2692
2745
  walletAddress: account.address,
2693
2746
  balanceMonitor,
2694
2747
  close: () => new Promise((res, rej) => {
2748
+ const timeout = setTimeout(() => {
2749
+ rej(new Error("[ClawRouter] Close timeout after 4s"));
2750
+ }, 4e3);
2695
2751
  sessionStore.close();
2696
- server.close((err) => err ? rej(err) : res());
2752
+ for (const socket of connections) {
2753
+ socket.destroy();
2754
+ }
2755
+ connections.clear();
2756
+ server.close((err) => {
2757
+ clearTimeout(timeout);
2758
+ if (err) {
2759
+ rej(err);
2760
+ } else {
2761
+ res();
2762
+ }
2763
+ });
2697
2764
  })
2698
2765
  });
2699
2766
  });
@@ -2901,10 +2968,13 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
2901
2968
  connection: "keep-alive"
2902
2969
  });
2903
2970
  headersSentEarly = true;
2904
- res.write(": heartbeat\n\n");
2971
+ safeWrite(res, ": heartbeat\n\n");
2905
2972
  heartbeatInterval = setInterval(() => {
2906
- if (!res.writableEnded) {
2907
- res.write(": heartbeat\n\n");
2973
+ if (canWrite(res)) {
2974
+ safeWrite(res, ": heartbeat\n\n");
2975
+ } else {
2976
+ clearInterval(heartbeatInterval);
2977
+ heartbeatInterval = void 0;
2908
2978
  }
2909
2979
  }, HEARTBEAT_INTERVAL_MS);
2910
2980
  }
@@ -3022,8 +3092,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3022
3092
  const errEvent = `data: ${JSON.stringify({ error: { message: errBody, type: "provider_error", status: errStatus } })}
3023
3093
 
3024
3094
  `;
3025
- res.write(errEvent);
3026
- res.write("data: [DONE]\n\n");
3095
+ safeWrite(res, errEvent);
3096
+ safeWrite(res, "data: [DONE]\n\n");
3027
3097
  res.end();
3028
3098
  const errBuf = Buffer.from(errEvent + "data: [DONE]\n\n");
3029
3099
  deduplicator.complete(dedupKey, {
@@ -3088,7 +3158,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3088
3158
  const roleData = `data: ${JSON.stringify(roleChunk)}
3089
3159
 
3090
3160
  `;
3091
- res.write(roleData);
3161
+ safeWrite(res, roleData);
3092
3162
  responseChunks.push(Buffer.from(roleData));
3093
3163
  if (content) {
3094
3164
  const contentChunk = {
@@ -3098,7 +3168,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3098
3168
  const contentData = `data: ${JSON.stringify(contentChunk)}
3099
3169
 
3100
3170
  `;
3101
- res.write(contentData);
3171
+ safeWrite(res, contentData);
3102
3172
  responseChunks.push(Buffer.from(contentData));
3103
3173
  }
3104
3174
  const toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;
@@ -3117,7 +3187,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3117
3187
  const toolCallData = `data: ${JSON.stringify(toolCallChunk)}
3118
3188
 
3119
3189
  `;
3120
- res.write(toolCallData);
3190
+ safeWrite(res, toolCallData);
3121
3191
  responseChunks.push(Buffer.from(toolCallData));
3122
3192
  }
3123
3193
  const finishChunk = {
@@ -3134,7 +3204,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3134
3204
  const finishData = `data: ${JSON.stringify(finishChunk)}
3135
3205
 
3136
3206
  `;
3137
- res.write(finishData);
3207
+ safeWrite(res, finishData);
3138
3208
  responseChunks.push(Buffer.from(finishData));
3139
3209
  }
3140
3210
  }
@@ -3142,11 +3212,11 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3142
3212
  const sseData = `data: ${jsonStr}
3143
3213
 
3144
3214
  `;
3145
- res.write(sseData);
3215
+ safeWrite(res, sseData);
3146
3216
  responseChunks.push(Buffer.from(sseData));
3147
3217
  }
3148
3218
  }
3149
- res.write("data: [DONE]\n\n");
3219
+ safeWrite(res, "data: [DONE]\n\n");
3150
3220
  responseChunks.push(Buffer.from("data: [DONE]\n\n"));
3151
3221
  res.end();
3152
3222
  deduplicator.complete(dedupKey, {
@@ -3169,8 +3239,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
3169
3239
  while (true) {
3170
3240
  const { done, value } = await reader.read();
3171
3241
  if (done) break;
3172
- res.write(value);
3173
- responseChunks.push(Buffer.from(value));
3242
+ const chunk = Buffer.from(value);
3243
+ safeWrite(res, chunk);
3244
+ responseChunks.push(chunk);
3174
3245
  }
3175
3246
  } finally {
3176
3247
  reader.releaseLock();