@blockrun/clawrouter 0.12.251 → 0.12.253

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/README.md CHANGED
@@ -105,7 +105,7 @@ openclaw gateway restart
105
105
 
106
106
  ### Option B — Standalone (continue.dev, Cursor, VS Code, any OpenAI-compatible client)
107
107
 
108
- > **Using Claude Code?** Check out [BRCC](https://blockrun.ai/brcc.md) — it's purpose-built for Claude Code with the same smart routing and x402 payments.
108
+ > **Using Claude Code?** Check out [BRCC](https://github.com/BlockRunAI/brcc) — it's purpose-built for Claude Code with the same smart routing and x402 payments.
109
109
  >
110
110
  > **Using NousResearch Hermes?** See [ClawRouter-Hermes](https://github.com/BlockRunAI/ClawRouter-Hermes) — a Python plugin that wires Hermes into the ClawRouter proxy. Same wallet, same <!-- br:models.chatVisible -->71<!-- /br:models.chatVisible --> models, same x402 USDC settlement on Base & Solana.
111
111
 
@@ -674,7 +674,7 @@ You're here. <!-- br:models.chatVisible -->71<!-- /br:models.chatVisible --> mod
674
674
  </td>
675
675
  <td width="50%">
676
676
 
677
- ### 🤖 [BRCC](https://blockrun.ai/brcc.md)
677
+ ### 🤖 [BRCC](https://github.com/BlockRunAI/brcc)
678
678
 
679
679
  **BlockRun for Claude Code**
680
680
 
package/dist/cli.js CHANGED
@@ -89597,6 +89597,15 @@ function debrandSystemMessages(messages, resolvedModel) {
89597
89597
  });
89598
89598
  return hasChanges ? result : messages;
89599
89599
  }
89600
+ function isToolResultMessage(msg) {
89601
+ if (msg.role === "tool") return true;
89602
+ if (Array.isArray(msg.content)) {
89603
+ return msg.content.some(
89604
+ (b) => b && typeof b === "object" && b.type === "tool_result"
89605
+ );
89606
+ }
89607
+ return false;
89608
+ }
89600
89609
  function truncateMessages(messages) {
89601
89610
  if (!messages || messages.length <= MAX_MESSAGES) {
89602
89611
  return {
@@ -89609,7 +89618,11 @@ function truncateMessages(messages) {
89609
89618
  const systemMsgs = messages.filter((m) => m.role === "system");
89610
89619
  const conversationMsgs = messages.filter((m) => m.role !== "system");
89611
89620
  const maxConversation = MAX_MESSAGES - systemMsgs.length;
89612
- const truncatedConversation = conversationMsgs.slice(-maxConversation);
89621
+ let start = Math.max(0, conversationMsgs.length - maxConversation);
89622
+ while (start < conversationMsgs.length && isToolResultMessage(conversationMsgs[start])) {
89623
+ start++;
89624
+ }
89625
+ const truncatedConversation = conversationMsgs.slice(start);
89613
89626
  const result = [...systemMsgs, ...truncatedConversation];
89614
89627
  console.log(
89615
89628
  `[ClawRouter] Truncated messages: ${messages.length} \u2192 ${result.length} (kept ${systemMsgs.length} system + ${truncatedConversation.length} recent)`
@@ -90472,6 +90485,10 @@ async function startProxy(options) {
90472
90485
  }
90473
90486
  if (req.url === "/v1/images/image2image" && req.method === "POST") {
90474
90487
  const img2imgStartTime = Date.now();
90488
+ const clientAbort = new AbortController();
90489
+ res.on("close", () => {
90490
+ if (!res.writableEnded) clientAbort.abort();
90491
+ });
90475
90492
  const chunks = [];
90476
90493
  for await (const chunk of req) {
90477
90494
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -90487,7 +90504,7 @@ async function startProxy(options) {
90487
90504
  if (typeof val !== "string" || !val) continue;
90488
90505
  if (val.startsWith("data:")) {
90489
90506
  } else if (val.startsWith("https://") || val.startsWith("http://")) {
90490
- const imgResp = await fetch(val);
90507
+ const imgResp = await fetch(val, { signal: clientAbort.signal });
90491
90508
  if (!imgResp.ok)
90492
90509
  throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
90493
90510
  const contentType = imgResp.headers.get("content-type") ?? "image/png";
@@ -90515,7 +90532,8 @@ async function startProxy(options) {
90515
90532
  const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
90516
90533
  method: "POST",
90517
90534
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
90518
- body: reqBody
90535
+ body: reqBody,
90536
+ signal: clientAbort.signal
90519
90537
  });
90520
90538
  const text = await upstream.text();
90521
90539
  if (!upstream.ok) {
@@ -90577,6 +90595,7 @@ async function startProxy(options) {
90577
90595
  res.writeHead(200, { "Content-Type": "application/json" });
90578
90596
  res.end(JSON.stringify(result));
90579
90597
  } catch (err) {
90598
+ if (clientAbort.signal.aborted) return;
90580
90599
  const msg = err instanceof Error ? err.message : String(err);
90581
90600
  console.error(`[ClawRouter] Image editing error: ${msg}`);
90582
90601
  if (!res.headersSent) {
@@ -90588,6 +90607,10 @@ async function startProxy(options) {
90588
90607
  }
90589
90608
  if (req.url === "/v1/audio/generations" && req.method === "POST") {
90590
90609
  const audioStartTime = Date.now();
90610
+ const clientAbort = new AbortController();
90611
+ res.on("close", () => {
90612
+ if (!res.writableEnded) clientAbort.abort();
90613
+ });
90591
90614
  const chunks = [];
90592
90615
  for await (const chunk of req) {
90593
90616
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -90603,7 +90626,8 @@ async function startProxy(options) {
90603
90626
  const upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
90604
90627
  method: "POST",
90605
90628
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
90606
- body: reqBody
90629
+ body: reqBody,
90630
+ signal: clientAbort.signal
90607
90631
  });
90608
90632
  const text = await upstream.text();
90609
90633
  if (!upstream.ok) {
@@ -90625,7 +90649,7 @@ async function startProxy(options) {
90625
90649
  for (const track of result.data) {
90626
90650
  if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
90627
90651
  try {
90628
- const audioResp = await fetch(track.url);
90652
+ const audioResp = await fetch(track.url, { signal: clientAbort.signal });
90629
90653
  if (audioResp.ok) {
90630
90654
  const contentType = audioResp.headers.get("content-type") ?? "audio/mpeg";
90631
90655
  const ext = contentType.includes("wav") ? "wav" : "mp3";
@@ -90657,6 +90681,7 @@ async function startProxy(options) {
90657
90681
  res.writeHead(200, { "Content-Type": "application/json" });
90658
90682
  res.end(JSON.stringify(result));
90659
90683
  } catch (err) {
90684
+ if (clientAbort.signal.aborted) return;
90660
90685
  const msg = err instanceof Error ? err.message : String(err);
90661
90686
  console.error(`[ClawRouter] Audio generation error: ${msg}`);
90662
90687
  if (!res.headersSent) {
@@ -91676,6 +91701,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
91676
91701
  console.log(
91677
91702
  `[ClawRouter] /img2img \u2192 ${img2imgModel} (${img2imgSize}): ${img2imgPrompt.slice(0, 80)}`
91678
91703
  );
91704
+ const img2imgAbort = new AbortController();
91705
+ res.on("close", () => {
91706
+ if (!res.writableEnded) img2imgAbort.abort();
91707
+ });
91679
91708
  try {
91680
91709
  const img2imgBody = JSON.stringify({
91681
91710
  model: img2imgModel,
@@ -91688,7 +91717,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
91688
91717
  const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
91689
91718
  method: "POST",
91690
91719
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
91691
- body: img2imgBody
91720
+ body: img2imgBody,
91721
+ signal: img2imgAbort.signal
91692
91722
  });
91693
91723
  const img2imgResult = await img2imgResponse.json();
91694
91724
  let responseText;
@@ -91738,6 +91768,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
91738
91768
  }
91739
91769
  sendImg2ImgText(responseText);
91740
91770
  } catch (err) {
91771
+ if (img2imgAbort.signal.aborted) return;
91741
91772
  const errMsg = err instanceof Error ? err.message : String(err);
91742
91773
  console.error(`[ClawRouter] /img2img error: ${errMsg}`);
91743
91774
  if (!res.headersSent) {
package/dist/index.js CHANGED
@@ -89417,6 +89417,15 @@ function debrandSystemMessages(messages, resolvedModel) {
89417
89417
  });
89418
89418
  return hasChanges ? result : messages;
89419
89419
  }
89420
+ function isToolResultMessage(msg) {
89421
+ if (msg.role === "tool") return true;
89422
+ if (Array.isArray(msg.content)) {
89423
+ return msg.content.some(
89424
+ (b) => b && typeof b === "object" && b.type === "tool_result"
89425
+ );
89426
+ }
89427
+ return false;
89428
+ }
89420
89429
  function truncateMessages(messages) {
89421
89430
  if (!messages || messages.length <= MAX_MESSAGES) {
89422
89431
  return {
@@ -89429,7 +89438,11 @@ function truncateMessages(messages) {
89429
89438
  const systemMsgs = messages.filter((m) => m.role === "system");
89430
89439
  const conversationMsgs = messages.filter((m) => m.role !== "system");
89431
89440
  const maxConversation = MAX_MESSAGES - systemMsgs.length;
89432
- const truncatedConversation = conversationMsgs.slice(-maxConversation);
89441
+ let start = Math.max(0, conversationMsgs.length - maxConversation);
89442
+ while (start < conversationMsgs.length && isToolResultMessage(conversationMsgs[start])) {
89443
+ start++;
89444
+ }
89445
+ const truncatedConversation = conversationMsgs.slice(start);
89433
89446
  const result = [...systemMsgs, ...truncatedConversation];
89434
89447
  console.log(
89435
89448
  `[ClawRouter] Truncated messages: ${messages.length} \u2192 ${result.length} (kept ${systemMsgs.length} system + ${truncatedConversation.length} recent)`
@@ -90292,6 +90305,10 @@ async function startProxy(options) {
90292
90305
  }
90293
90306
  if (req.url === "/v1/images/image2image" && req.method === "POST") {
90294
90307
  const img2imgStartTime = Date.now();
90308
+ const clientAbort = new AbortController();
90309
+ res.on("close", () => {
90310
+ if (!res.writableEnded) clientAbort.abort();
90311
+ });
90295
90312
  const chunks = [];
90296
90313
  for await (const chunk of req) {
90297
90314
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -90307,7 +90324,7 @@ async function startProxy(options) {
90307
90324
  if (typeof val !== "string" || !val) continue;
90308
90325
  if (val.startsWith("data:")) {
90309
90326
  } else if (val.startsWith("https://") || val.startsWith("http://")) {
90310
- const imgResp = await fetch(val);
90327
+ const imgResp = await fetch(val, { signal: clientAbort.signal });
90311
90328
  if (!imgResp.ok)
90312
90329
  throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
90313
90330
  const contentType = imgResp.headers.get("content-type") ?? "image/png";
@@ -90335,7 +90352,8 @@ async function startProxy(options) {
90335
90352
  const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
90336
90353
  method: "POST",
90337
90354
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
90338
- body: reqBody
90355
+ body: reqBody,
90356
+ signal: clientAbort.signal
90339
90357
  });
90340
90358
  const text = await upstream.text();
90341
90359
  if (!upstream.ok) {
@@ -90397,6 +90415,7 @@ async function startProxy(options) {
90397
90415
  res.writeHead(200, { "Content-Type": "application/json" });
90398
90416
  res.end(JSON.stringify(result));
90399
90417
  } catch (err) {
90418
+ if (clientAbort.signal.aborted) return;
90400
90419
  const msg = err instanceof Error ? err.message : String(err);
90401
90420
  console.error(`[ClawRouter] Image editing error: ${msg}`);
90402
90421
  if (!res.headersSent) {
@@ -90408,6 +90427,10 @@ async function startProxy(options) {
90408
90427
  }
90409
90428
  if (req.url === "/v1/audio/generations" && req.method === "POST") {
90410
90429
  const audioStartTime = Date.now();
90430
+ const clientAbort = new AbortController();
90431
+ res.on("close", () => {
90432
+ if (!res.writableEnded) clientAbort.abort();
90433
+ });
90411
90434
  const chunks = [];
90412
90435
  for await (const chunk of req) {
90413
90436
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -90423,7 +90446,8 @@ async function startProxy(options) {
90423
90446
  const upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
90424
90447
  method: "POST",
90425
90448
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
90426
- body: reqBody
90449
+ body: reqBody,
90450
+ signal: clientAbort.signal
90427
90451
  });
90428
90452
  const text = await upstream.text();
90429
90453
  if (!upstream.ok) {
@@ -90445,7 +90469,7 @@ async function startProxy(options) {
90445
90469
  for (const track of result.data) {
90446
90470
  if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
90447
90471
  try {
90448
- const audioResp = await fetch(track.url);
90472
+ const audioResp = await fetch(track.url, { signal: clientAbort.signal });
90449
90473
  if (audioResp.ok) {
90450
90474
  const contentType = audioResp.headers.get("content-type") ?? "audio/mpeg";
90451
90475
  const ext = contentType.includes("wav") ? "wav" : "mp3";
@@ -90477,6 +90501,7 @@ async function startProxy(options) {
90477
90501
  res.writeHead(200, { "Content-Type": "application/json" });
90478
90502
  res.end(JSON.stringify(result));
90479
90503
  } catch (err) {
90504
+ if (clientAbort.signal.aborted) return;
90480
90505
  const msg = err instanceof Error ? err.message : String(err);
90481
90506
  console.error(`[ClawRouter] Audio generation error: ${msg}`);
90482
90507
  if (!res.headersSent) {
@@ -91496,6 +91521,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
91496
91521
  console.log(
91497
91522
  `[ClawRouter] /img2img \u2192 ${img2imgModel} (${img2imgSize}): ${img2imgPrompt.slice(0, 80)}`
91498
91523
  );
91524
+ const img2imgAbort = new AbortController();
91525
+ res.on("close", () => {
91526
+ if (!res.writableEnded) img2imgAbort.abort();
91527
+ });
91499
91528
  try {
91500
91529
  const img2imgBody = JSON.stringify({
91501
91530
  model: img2imgModel,
@@ -91508,7 +91537,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
91508
91537
  const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
91509
91538
  method: "POST",
91510
91539
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
91511
- body: img2imgBody
91540
+ body: img2imgBody,
91541
+ signal: img2imgAbort.signal
91512
91542
  });
91513
91543
  const img2imgResult = await img2imgResponse.json();
91514
91544
  let responseText;
@@ -91558,6 +91588,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
91558
91588
  }
91559
91589
  sendImg2ImgText(responseText);
91560
91590
  } catch (err) {
91591
+ if (img2imgAbort.signal.aborted) return;
91561
91592
  const errMsg = err instanceof Error ? err.message : String(err);
91562
91593
  console.error(`[ClawRouter] /img2img error: ${errMsg}`);
91563
91594
  if (!res.headersSent) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/clawrouter",
3
- "version": "0.12.251",
3
+ "version": "0.12.253",
4
4
  "description": "Smart LLM router — save 88% on inference costs. 71 models (5 free), one wallet, x402 micropayments.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",