@blockrun/clawrouter 0.12.32 → 0.12.33

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
@@ -1640,6 +1640,7 @@ import { finished } from "stream";
1640
1640
  import { homedir as homedir4 } from "os";
1641
1641
  import { join as join5 } from "path";
1642
1642
  import { mkdir as mkdir3, writeFile as writeFile2, readFile, stat as fsStat } from "fs/promises";
1643
+ import { readFileSync, existsSync } from "fs";
1643
1644
  import { createPublicClient as createPublicClient2, http as http2 } from "viem";
1644
1645
  import { base as base2 } from "viem/chains";
1645
1646
  import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
@@ -6064,6 +6065,22 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
6064
6065
  }).catch(() => {
6065
6066
  });
6066
6067
  }
6068
+ function readImageFileAsDataUri(filePath) {
6069
+ const resolved = filePath.startsWith("~/") ? join5(homedir4(), filePath.slice(2)) : filePath;
6070
+ if (!existsSync(resolved)) {
6071
+ throw new Error(`Image file not found: ${resolved}`);
6072
+ }
6073
+ const ext = resolved.split(".").pop()?.toLowerCase() ?? "png";
6074
+ const mimeMap = {
6075
+ png: "image/png",
6076
+ jpg: "image/jpeg",
6077
+ jpeg: "image/jpeg",
6078
+ webp: "image/webp"
6079
+ };
6080
+ const mime = mimeMap[ext] ?? "image/png";
6081
+ const data = readFileSync(resolved);
6082
+ return `data:${mime};base64,${data.toString("base64")}`;
6083
+ }
6067
6084
  async function uploadDataUriToHost(dataUri) {
6068
6085
  const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
6069
6086
  if (!match) throw new Error("Invalid data URI format");
@@ -6358,7 +6375,9 @@ async function startProxy(options) {
6358
6375
  console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
6359
6376
  }
6360
6377
  } catch (downloadErr) {
6361
- console.warn(`[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`);
6378
+ console.warn(
6379
+ `[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
6380
+ );
6362
6381
  }
6363
6382
  }
6364
6383
  }
@@ -6375,6 +6394,76 @@ async function startProxy(options) {
6375
6394
  }
6376
6395
  return;
6377
6396
  }
6397
+ if (req.url === "/v1/images/image2image" && req.method === "POST") {
6398
+ const chunks = [];
6399
+ for await (const chunk of req) {
6400
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
6401
+ }
6402
+ const reqBody = Buffer.concat(chunks);
6403
+ try {
6404
+ const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
6405
+ method: "POST",
6406
+ headers: { "content-type": "application/json", "user-agent": USER_AGENT },
6407
+ body: reqBody
6408
+ });
6409
+ const text = await upstream.text();
6410
+ if (!upstream.ok) {
6411
+ res.writeHead(upstream.status, { "Content-Type": "application/json" });
6412
+ res.end(text);
6413
+ return;
6414
+ }
6415
+ let result;
6416
+ try {
6417
+ result = JSON.parse(text);
6418
+ } catch {
6419
+ res.writeHead(200, { "Content-Type": "application/json" });
6420
+ res.end(text);
6421
+ return;
6422
+ }
6423
+ if (result.data?.length) {
6424
+ await mkdir3(IMAGE_DIR, { recursive: true });
6425
+ const port2 = server.address()?.port ?? 8402;
6426
+ for (const img of result.data) {
6427
+ const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
6428
+ if (dataUriMatch) {
6429
+ const [, mimeType, b64] = dataUriMatch;
6430
+ const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
6431
+ const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
6432
+ await writeFile2(join5(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
6433
+ img.url = `http://localhost:${port2}/images/${filename}`;
6434
+ console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
6435
+ } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
6436
+ try {
6437
+ const imgResp = await fetch(img.url);
6438
+ if (imgResp.ok) {
6439
+ const contentType = imgResp.headers.get("content-type") ?? "image/png";
6440
+ const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
6441
+ const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
6442
+ const buf = Buffer.from(await imgResp.arrayBuffer());
6443
+ await writeFile2(join5(IMAGE_DIR, filename), buf);
6444
+ img.url = `http://localhost:${port2}/images/${filename}`;
6445
+ console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
6446
+ }
6447
+ } catch (downloadErr) {
6448
+ console.warn(
6449
+ `[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
6450
+ );
6451
+ }
6452
+ }
6453
+ }
6454
+ }
6455
+ res.writeHead(200, { "Content-Type": "application/json" });
6456
+ res.end(JSON.stringify(result));
6457
+ } catch (err) {
6458
+ const msg = err instanceof Error ? err.message : String(err);
6459
+ console.error(`[ClawRouter] Image editing error: ${msg}`);
6460
+ if (!res.headersSent) {
6461
+ res.writeHead(502, { "Content-Type": "application/json" });
6462
+ res.end(JSON.stringify({ error: "Image editing failed", details: msg }));
6463
+ }
6464
+ }
6465
+ return;
6466
+ }
6378
6467
  if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
6379
6468
  try {
6380
6469
  await proxyPartnerRequest(req, res, apiBase, payFetch);
@@ -6989,6 +7078,177 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6989
7078
  }
6990
7079
  return;
6991
7080
  }
7081
+ if (lastContent.startsWith("/img2img")) {
7082
+ const imgArgs = lastContent.slice("/img2img".length).trim();
7083
+ let img2imgModel = "openai/gpt-image-1";
7084
+ let img2imgSize = "1024x1024";
7085
+ let imagePath = null;
7086
+ let maskPath = null;
7087
+ let img2imgPrompt = imgArgs;
7088
+ const imageMatch = imgArgs.match(/--image\s+(\S+)/);
7089
+ if (imageMatch) {
7090
+ imagePath = imageMatch[1];
7091
+ img2imgPrompt = img2imgPrompt.replace(/--image\s+\S+/, "").trim();
7092
+ }
7093
+ const maskMatch = imgArgs.match(/--mask\s+(\S+)/);
7094
+ if (maskMatch) {
7095
+ maskPath = maskMatch[1];
7096
+ img2imgPrompt = img2imgPrompt.replace(/--mask\s+\S+/, "").trim();
7097
+ }
7098
+ const img2imgSizeMatch = imgArgs.match(/--size\s+(\d+x\d+)/);
7099
+ if (img2imgSizeMatch) {
7100
+ img2imgSize = img2imgSizeMatch[1];
7101
+ img2imgPrompt = img2imgPrompt.replace(/--size\s+\d+x\d+/, "").trim();
7102
+ }
7103
+ const img2imgModelMatch = imgArgs.match(/--model\s+(\S+)/);
7104
+ if (img2imgModelMatch) {
7105
+ const raw = img2imgModelMatch[1];
7106
+ const IMG2IMG_ALIASES = {
7107
+ "gpt-image": "openai/gpt-image-1",
7108
+ "gpt-image-1": "openai/gpt-image-1"
7109
+ };
7110
+ img2imgModel = IMG2IMG_ALIASES[raw] ?? raw;
7111
+ img2imgPrompt = img2imgPrompt.replace(/--model\s+\S+/, "").trim();
7112
+ }
7113
+ const usageText = [
7114
+ "Usage: /img2img --image <path> <prompt>",
7115
+ "",
7116
+ "Options:",
7117
+ " --image <path> Source image path (required)",
7118
+ " --mask <path> Mask image path (optional, white = area to edit)",
7119
+ " --model <model> Model (default: gpt-image-1)",
7120
+ " --size <WxH> Output size (default: 1024x1024)",
7121
+ "",
7122
+ "Models:",
7123
+ " gpt-image-1 OpenAI GPT Image 1 \u2014 $0.02/image",
7124
+ "",
7125
+ "Examples:",
7126
+ " /img2img --image ~/photo.png change background to starry sky",
7127
+ " /img2img --image ./cat.jpg --mask ./mask.png remove the background",
7128
+ " /img2img --image /tmp/portrait.png --size 1536x1024 add a hat"
7129
+ ].join("\n");
7130
+ const sendImg2ImgText = (text) => {
7131
+ const completionId = `chatcmpl-img2img-${Date.now()}`;
7132
+ const timestamp = Math.floor(Date.now() / 1e3);
7133
+ if (isStreaming) {
7134
+ res.writeHead(200, {
7135
+ "Content-Type": "text/event-stream",
7136
+ "Cache-Control": "no-cache",
7137
+ Connection: "keep-alive"
7138
+ });
7139
+ res.write(
7140
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/img2img", choices: [{ index: 0, delta: { role: "assistant", content: text }, finish_reason: null }] })}
7141
+
7142
+ `
7143
+ );
7144
+ res.write(
7145
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/img2img", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
7146
+
7147
+ `
7148
+ );
7149
+ res.write("data: [DONE]\n\n");
7150
+ res.end();
7151
+ } else {
7152
+ res.writeHead(200, { "Content-Type": "application/json" });
7153
+ res.end(
7154
+ JSON.stringify({
7155
+ id: completionId,
7156
+ object: "chat.completion",
7157
+ created: timestamp,
7158
+ model: "clawrouter/img2img",
7159
+ choices: [
7160
+ {
7161
+ index: 0,
7162
+ message: { role: "assistant", content: text },
7163
+ finish_reason: "stop"
7164
+ }
7165
+ ],
7166
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
7167
+ })
7168
+ );
7169
+ }
7170
+ };
7171
+ if (!imagePath || !img2imgPrompt) {
7172
+ sendImg2ImgText(usageText);
7173
+ return;
7174
+ }
7175
+ let imageDataUri;
7176
+ let maskDataUri;
7177
+ try {
7178
+ imageDataUri = readImageFileAsDataUri(imagePath);
7179
+ if (maskPath) maskDataUri = readImageFileAsDataUri(maskPath);
7180
+ } catch (fileErr) {
7181
+ const fileErrMsg = fileErr instanceof Error ? fileErr.message : String(fileErr);
7182
+ sendImg2ImgText(`Failed to read image file: ${fileErrMsg}`);
7183
+ return;
7184
+ }
7185
+ console.log(
7186
+ `[ClawRouter] /img2img \u2192 ${img2imgModel} (${img2imgSize}): ${img2imgPrompt.slice(0, 80)}`
7187
+ );
7188
+ try {
7189
+ const img2imgBody = JSON.stringify({
7190
+ model: img2imgModel,
7191
+ prompt: img2imgPrompt,
7192
+ image: imageDataUri,
7193
+ ...maskDataUri ? { mask: maskDataUri } : {},
7194
+ size: img2imgSize,
7195
+ n: 1
7196
+ });
7197
+ const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
7198
+ method: "POST",
7199
+ headers: { "content-type": "application/json", "user-agent": USER_AGENT },
7200
+ body: img2imgBody
7201
+ });
7202
+ const img2imgResult = await img2imgResponse.json();
7203
+ let responseText;
7204
+ if (!img2imgResponse.ok || img2imgResult.error) {
7205
+ const errMsg = typeof img2imgResult.error === "string" ? img2imgResult.error : img2imgResult.error?.message ?? `HTTP ${img2imgResponse.status}`;
7206
+ responseText = `Image editing failed: ${errMsg}`;
7207
+ console.log(`[ClawRouter] /img2img error: ${errMsg}`);
7208
+ } else {
7209
+ const images = img2imgResult.data ?? [];
7210
+ if (images.length === 0) {
7211
+ responseText = "Image editing returned no results.";
7212
+ } else {
7213
+ const lines = [];
7214
+ for (const img of images) {
7215
+ if (img.url) {
7216
+ if (img.url.startsWith("data:")) {
7217
+ try {
7218
+ const hostedUrl = await uploadDataUriToHost(img.url);
7219
+ lines.push(hostedUrl);
7220
+ } catch (uploadErr) {
7221
+ console.error(
7222
+ `[ClawRouter] /img2img: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
7223
+ );
7224
+ lines.push("Image edited but upload failed. Try again.");
7225
+ }
7226
+ } else {
7227
+ lines.push(img.url);
7228
+ }
7229
+ }
7230
+ if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
7231
+ }
7232
+ lines.push("", `Model: ${img2imgModel} | Size: ${img2imgSize}`);
7233
+ responseText = lines.join("\n");
7234
+ }
7235
+ console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
7236
+ }
7237
+ sendImg2ImgText(responseText);
7238
+ } catch (err) {
7239
+ const errMsg = err instanceof Error ? err.message : String(err);
7240
+ console.error(`[ClawRouter] /img2img error: ${errMsg}`);
7241
+ if (!res.headersSent) {
7242
+ res.writeHead(500, { "Content-Type": "application/json" });
7243
+ res.end(
7244
+ JSON.stringify({
7245
+ error: { message: `Image editing failed: ${errMsg}`, type: "img2img_error" }
7246
+ })
7247
+ );
7248
+ }
7249
+ }
7250
+ return;
7251
+ }
6992
7252
  if (parsed.stream === true) {
6993
7253
  parsed.stream = false;
6994
7254
  bodyModified = true;
@@ -7778,7 +8038,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
7778
8038
  // src/index.ts
7779
8039
  import {
7780
8040
  writeFileSync as writeFileSync2,
7781
- existsSync as existsSync2,
8041
+ existsSync as existsSync3,
7782
8042
  readdirSync,
7783
8043
  mkdirSync as mkdirSync2,
7784
8044
  copyFileSync,
@@ -8220,7 +8480,7 @@ function injectModelsConfig(logger) {
8220
8480
  const configPath = join7(configDir, "openclaw.json");
8221
8481
  let config = {};
8222
8482
  let needsWrite = false;
8223
- if (!existsSync2(configDir)) {
8483
+ if (!existsSync3(configDir)) {
8224
8484
  try {
8225
8485
  mkdirSync2(configDir, { recursive: true });
8226
8486
  logger.info("Created OpenClaw config directory");
@@ -8231,7 +8491,7 @@ function injectModelsConfig(logger) {
8231
8491
  return;
8232
8492
  }
8233
8493
  }
8234
- if (existsSync2(configPath)) {
8494
+ if (existsSync3(configPath)) {
8235
8495
  try {
8236
8496
  const content = readTextFileSync(configPath).trim();
8237
8497
  if (content) {
@@ -8379,7 +8639,7 @@ function injectModelsConfig(logger) {
8379
8639
  }
8380
8640
  function injectAuthProfile(logger) {
8381
8641
  const agentsDir = join7(homedir6(), ".openclaw", "agents");
8382
- if (!existsSync2(agentsDir)) {
8642
+ if (!existsSync3(agentsDir)) {
8383
8643
  try {
8384
8644
  mkdirSync2(agentsDir, { recursive: true });
8385
8645
  } catch (err) {
@@ -8397,7 +8657,7 @@ function injectAuthProfile(logger) {
8397
8657
  for (const agentId of agents) {
8398
8658
  const authDir = join7(agentsDir, agentId, "agent");
8399
8659
  const authPath = join7(authDir, "auth-profiles.json");
8400
- if (!existsSync2(authDir)) {
8660
+ if (!existsSync3(authDir)) {
8401
8661
  try {
8402
8662
  mkdirSync2(authDir, { recursive: true });
8403
8663
  } catch {
@@ -8408,7 +8668,7 @@ function injectAuthProfile(logger) {
8408
8668
  version: 1,
8409
8669
  profiles: {}
8410
8670
  };
8411
- if (existsSync2(authPath)) {
8671
+ if (existsSync3(authPath)) {
8412
8672
  try {
8413
8673
  const existing = JSON.parse(readTextFileSync(authPath));
8414
8674
  if (existing.version && existing.profiles) {
@@ -8552,7 +8812,7 @@ async function createWalletCommand() {
8552
8812
  let walletKey;
8553
8813
  let address;
8554
8814
  try {
8555
- if (existsSync2(WALLET_FILE)) {
8815
+ if (existsSync3(WALLET_FILE)) {
8556
8816
  walletKey = readTextFileSync(WALLET_FILE).trim();
8557
8817
  if (walletKey.startsWith("0x") && walletKey.length === 66) {
8558
8818
  const account = privateKeyToAccount4(walletKey);
@@ -8582,7 +8842,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8582
8842
  ];
8583
8843
  let hasMnemonic = false;
8584
8844
  try {
8585
- if (existsSync2(MNEMONIC_FILE)) {
8845
+ if (existsSync3(MNEMONIC_FILE)) {
8586
8846
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8587
8847
  if (mnemonic) {
8588
8848
  hasMnemonic = true;
@@ -8624,7 +8884,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8624
8884
  if (subcommand === "solana") {
8625
8885
  try {
8626
8886
  let solanaAddr;
8627
- if (existsSync2(MNEMONIC_FILE)) {
8887
+ if (existsSync3(MNEMONIC_FILE)) {
8628
8888
  const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8629
8889
  if (existingMnemonic) {
8630
8890
  await savePaymentChain("solana");
@@ -8669,7 +8929,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8669
8929
  }
8670
8930
  if (subcommand === "migrate-solana") {
8671
8931
  try {
8672
- if (!existsSync2(MNEMONIC_FILE)) {
8932
+ if (!existsSync3(MNEMONIC_FILE)) {
8673
8933
  return {
8674
8934
  text: "No mnemonic file found. Solana wallet not set up \u2014 nothing to migrate.",
8675
8935
  isError: true
@@ -8772,7 +9032,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8772
9032
  }
8773
9033
  let solanaSection = "";
8774
9034
  try {
8775
- if (existsSync2(MNEMONIC_FILE)) {
9035
+ if (existsSync3(MNEMONIC_FILE)) {
8776
9036
  const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
8777
9037
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8778
9038
  if (mnemonic) {