@blockrun/clawrouter 0.12.32 → 0.12.34

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,103 @@ 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 rawBody = Buffer.concat(chunks);
6403
+ let reqBody;
6404
+ try {
6405
+ const parsed = JSON.parse(rawBody.toString());
6406
+ for (const field of ["image", "mask"]) {
6407
+ const val = parsed[field];
6408
+ if (typeof val !== "string" || !val) continue;
6409
+ if (val.startsWith("data:")) {
6410
+ } else if (val.startsWith("https://") || val.startsWith("http://")) {
6411
+ const imgResp = await fetch(val);
6412
+ if (!imgResp.ok) throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
6413
+ const contentType = imgResp.headers.get("content-type") ?? "image/png";
6414
+ const buf = Buffer.from(await imgResp.arrayBuffer());
6415
+ parsed[field] = `data:${contentType};base64,${buf.toString("base64")}`;
6416
+ console.log(`[ClawRouter] img2img: downloaded ${field} URL \u2192 data URI (${buf.length} bytes)`);
6417
+ } else {
6418
+ parsed[field] = readImageFileAsDataUri(val);
6419
+ console.log(`[ClawRouter] img2img: read ${field} file \u2192 data URI`);
6420
+ }
6421
+ }
6422
+ if (!parsed.model) parsed.model = "openai/gpt-image-1";
6423
+ reqBody = JSON.stringify(parsed);
6424
+ } catch (parseErr) {
6425
+ const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
6426
+ res.writeHead(400, { "Content-Type": "application/json" });
6427
+ res.end(JSON.stringify({ error: "Invalid request", details: msg }));
6428
+ return;
6429
+ }
6430
+ try {
6431
+ const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
6432
+ method: "POST",
6433
+ headers: { "content-type": "application/json", "user-agent": USER_AGENT },
6434
+ body: reqBody
6435
+ });
6436
+ const text = await upstream.text();
6437
+ if (!upstream.ok) {
6438
+ res.writeHead(upstream.status, { "Content-Type": "application/json" });
6439
+ res.end(text);
6440
+ return;
6441
+ }
6442
+ let result;
6443
+ try {
6444
+ result = JSON.parse(text);
6445
+ } catch {
6446
+ res.writeHead(200, { "Content-Type": "application/json" });
6447
+ res.end(text);
6448
+ return;
6449
+ }
6450
+ if (result.data?.length) {
6451
+ await mkdir3(IMAGE_DIR, { recursive: true });
6452
+ const port2 = server.address()?.port ?? 8402;
6453
+ for (const img of result.data) {
6454
+ const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
6455
+ if (dataUriMatch) {
6456
+ const [, mimeType, b64] = dataUriMatch;
6457
+ const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
6458
+ const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
6459
+ await writeFile2(join5(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
6460
+ img.url = `http://localhost:${port2}/images/${filename}`;
6461
+ console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
6462
+ } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
6463
+ try {
6464
+ const imgResp = await fetch(img.url);
6465
+ if (imgResp.ok) {
6466
+ const contentType = imgResp.headers.get("content-type") ?? "image/png";
6467
+ const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
6468
+ const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
6469
+ const buf = Buffer.from(await imgResp.arrayBuffer());
6470
+ await writeFile2(join5(IMAGE_DIR, filename), buf);
6471
+ img.url = `http://localhost:${port2}/images/${filename}`;
6472
+ console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
6473
+ }
6474
+ } catch (downloadErr) {
6475
+ console.warn(
6476
+ `[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
6477
+ );
6478
+ }
6479
+ }
6480
+ }
6481
+ }
6482
+ res.writeHead(200, { "Content-Type": "application/json" });
6483
+ res.end(JSON.stringify(result));
6484
+ } catch (err) {
6485
+ const msg = err instanceof Error ? err.message : String(err);
6486
+ console.error(`[ClawRouter] Image editing error: ${msg}`);
6487
+ if (!res.headersSent) {
6488
+ res.writeHead(502, { "Content-Type": "application/json" });
6489
+ res.end(JSON.stringify({ error: "Image editing failed", details: msg }));
6490
+ }
6491
+ }
6492
+ return;
6493
+ }
6378
6494
  if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
6379
6495
  try {
6380
6496
  await proxyPartnerRequest(req, res, apiBase, payFetch);
@@ -6989,6 +7105,177 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6989
7105
  }
6990
7106
  return;
6991
7107
  }
7108
+ if (lastContent.startsWith("/img2img")) {
7109
+ const imgArgs = lastContent.slice("/img2img".length).trim();
7110
+ let img2imgModel = "openai/gpt-image-1";
7111
+ let img2imgSize = "1024x1024";
7112
+ let imagePath = null;
7113
+ let maskPath = null;
7114
+ let img2imgPrompt = imgArgs;
7115
+ const imageMatch = imgArgs.match(/--image\s+(\S+)/);
7116
+ if (imageMatch) {
7117
+ imagePath = imageMatch[1];
7118
+ img2imgPrompt = img2imgPrompt.replace(/--image\s+\S+/, "").trim();
7119
+ }
7120
+ const maskMatch = imgArgs.match(/--mask\s+(\S+)/);
7121
+ if (maskMatch) {
7122
+ maskPath = maskMatch[1];
7123
+ img2imgPrompt = img2imgPrompt.replace(/--mask\s+\S+/, "").trim();
7124
+ }
7125
+ const img2imgSizeMatch = imgArgs.match(/--size\s+(\d+x\d+)/);
7126
+ if (img2imgSizeMatch) {
7127
+ img2imgSize = img2imgSizeMatch[1];
7128
+ img2imgPrompt = img2imgPrompt.replace(/--size\s+\d+x\d+/, "").trim();
7129
+ }
7130
+ const img2imgModelMatch = imgArgs.match(/--model\s+(\S+)/);
7131
+ if (img2imgModelMatch) {
7132
+ const raw = img2imgModelMatch[1];
7133
+ const IMG2IMG_ALIASES = {
7134
+ "gpt-image": "openai/gpt-image-1",
7135
+ "gpt-image-1": "openai/gpt-image-1"
7136
+ };
7137
+ img2imgModel = IMG2IMG_ALIASES[raw] ?? raw;
7138
+ img2imgPrompt = img2imgPrompt.replace(/--model\s+\S+/, "").trim();
7139
+ }
7140
+ const usageText = [
7141
+ "Usage: /img2img --image <path> <prompt>",
7142
+ "",
7143
+ "Options:",
7144
+ " --image <path> Source image path (required)",
7145
+ " --mask <path> Mask image path (optional, white = area to edit)",
7146
+ " --model <model> Model (default: gpt-image-1)",
7147
+ " --size <WxH> Output size (default: 1024x1024)",
7148
+ "",
7149
+ "Models:",
7150
+ " gpt-image-1 OpenAI GPT Image 1 \u2014 $0.02/image",
7151
+ "",
7152
+ "Examples:",
7153
+ " /img2img --image ~/photo.png change background to starry sky",
7154
+ " /img2img --image ./cat.jpg --mask ./mask.png remove the background",
7155
+ " /img2img --image /tmp/portrait.png --size 1536x1024 add a hat"
7156
+ ].join("\n");
7157
+ const sendImg2ImgText = (text) => {
7158
+ const completionId = `chatcmpl-img2img-${Date.now()}`;
7159
+ const timestamp = Math.floor(Date.now() / 1e3);
7160
+ if (isStreaming) {
7161
+ res.writeHead(200, {
7162
+ "Content-Type": "text/event-stream",
7163
+ "Cache-Control": "no-cache",
7164
+ Connection: "keep-alive"
7165
+ });
7166
+ res.write(
7167
+ `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 }] })}
7168
+
7169
+ `
7170
+ );
7171
+ res.write(
7172
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/img2img", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
7173
+
7174
+ `
7175
+ );
7176
+ res.write("data: [DONE]\n\n");
7177
+ res.end();
7178
+ } else {
7179
+ res.writeHead(200, { "Content-Type": "application/json" });
7180
+ res.end(
7181
+ JSON.stringify({
7182
+ id: completionId,
7183
+ object: "chat.completion",
7184
+ created: timestamp,
7185
+ model: "clawrouter/img2img",
7186
+ choices: [
7187
+ {
7188
+ index: 0,
7189
+ message: { role: "assistant", content: text },
7190
+ finish_reason: "stop"
7191
+ }
7192
+ ],
7193
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
7194
+ })
7195
+ );
7196
+ }
7197
+ };
7198
+ if (!imagePath || !img2imgPrompt) {
7199
+ sendImg2ImgText(usageText);
7200
+ return;
7201
+ }
7202
+ let imageDataUri;
7203
+ let maskDataUri;
7204
+ try {
7205
+ imageDataUri = readImageFileAsDataUri(imagePath);
7206
+ if (maskPath) maskDataUri = readImageFileAsDataUri(maskPath);
7207
+ } catch (fileErr) {
7208
+ const fileErrMsg = fileErr instanceof Error ? fileErr.message : String(fileErr);
7209
+ sendImg2ImgText(`Failed to read image file: ${fileErrMsg}`);
7210
+ return;
7211
+ }
7212
+ console.log(
7213
+ `[ClawRouter] /img2img \u2192 ${img2imgModel} (${img2imgSize}): ${img2imgPrompt.slice(0, 80)}`
7214
+ );
7215
+ try {
7216
+ const img2imgBody = JSON.stringify({
7217
+ model: img2imgModel,
7218
+ prompt: img2imgPrompt,
7219
+ image: imageDataUri,
7220
+ ...maskDataUri ? { mask: maskDataUri } : {},
7221
+ size: img2imgSize,
7222
+ n: 1
7223
+ });
7224
+ const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
7225
+ method: "POST",
7226
+ headers: { "content-type": "application/json", "user-agent": USER_AGENT },
7227
+ body: img2imgBody
7228
+ });
7229
+ const img2imgResult = await img2imgResponse.json();
7230
+ let responseText;
7231
+ if (!img2imgResponse.ok || img2imgResult.error) {
7232
+ const errMsg = typeof img2imgResult.error === "string" ? img2imgResult.error : img2imgResult.error?.message ?? `HTTP ${img2imgResponse.status}`;
7233
+ responseText = `Image editing failed: ${errMsg}`;
7234
+ console.log(`[ClawRouter] /img2img error: ${errMsg}`);
7235
+ } else {
7236
+ const images = img2imgResult.data ?? [];
7237
+ if (images.length === 0) {
7238
+ responseText = "Image editing returned no results.";
7239
+ } else {
7240
+ const lines = [];
7241
+ for (const img of images) {
7242
+ if (img.url) {
7243
+ if (img.url.startsWith("data:")) {
7244
+ try {
7245
+ const hostedUrl = await uploadDataUriToHost(img.url);
7246
+ lines.push(hostedUrl);
7247
+ } catch (uploadErr) {
7248
+ console.error(
7249
+ `[ClawRouter] /img2img: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
7250
+ );
7251
+ lines.push("Image edited but upload failed. Try again.");
7252
+ }
7253
+ } else {
7254
+ lines.push(img.url);
7255
+ }
7256
+ }
7257
+ if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
7258
+ }
7259
+ lines.push("", `Model: ${img2imgModel} | Size: ${img2imgSize}`);
7260
+ responseText = lines.join("\n");
7261
+ }
7262
+ console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
7263
+ }
7264
+ sendImg2ImgText(responseText);
7265
+ } catch (err) {
7266
+ const errMsg = err instanceof Error ? err.message : String(err);
7267
+ console.error(`[ClawRouter] /img2img error: ${errMsg}`);
7268
+ if (!res.headersSent) {
7269
+ res.writeHead(500, { "Content-Type": "application/json" });
7270
+ res.end(
7271
+ JSON.stringify({
7272
+ error: { message: `Image editing failed: ${errMsg}`, type: "img2img_error" }
7273
+ })
7274
+ );
7275
+ }
7276
+ }
7277
+ return;
7278
+ }
6992
7279
  if (parsed.stream === true) {
6993
7280
  parsed.stream = false;
6994
7281
  bodyModified = true;
@@ -7778,7 +8065,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
7778
8065
  // src/index.ts
7779
8066
  import {
7780
8067
  writeFileSync as writeFileSync2,
7781
- existsSync as existsSync2,
8068
+ existsSync as existsSync3,
7782
8069
  readdirSync,
7783
8070
  mkdirSync as mkdirSync2,
7784
8071
  copyFileSync,
@@ -8220,7 +8507,7 @@ function injectModelsConfig(logger) {
8220
8507
  const configPath = join7(configDir, "openclaw.json");
8221
8508
  let config = {};
8222
8509
  let needsWrite = false;
8223
- if (!existsSync2(configDir)) {
8510
+ if (!existsSync3(configDir)) {
8224
8511
  try {
8225
8512
  mkdirSync2(configDir, { recursive: true });
8226
8513
  logger.info("Created OpenClaw config directory");
@@ -8231,7 +8518,7 @@ function injectModelsConfig(logger) {
8231
8518
  return;
8232
8519
  }
8233
8520
  }
8234
- if (existsSync2(configPath)) {
8521
+ if (existsSync3(configPath)) {
8235
8522
  try {
8236
8523
  const content = readTextFileSync(configPath).trim();
8237
8524
  if (content) {
@@ -8379,7 +8666,7 @@ function injectModelsConfig(logger) {
8379
8666
  }
8380
8667
  function injectAuthProfile(logger) {
8381
8668
  const agentsDir = join7(homedir6(), ".openclaw", "agents");
8382
- if (!existsSync2(agentsDir)) {
8669
+ if (!existsSync3(agentsDir)) {
8383
8670
  try {
8384
8671
  mkdirSync2(agentsDir, { recursive: true });
8385
8672
  } catch (err) {
@@ -8397,7 +8684,7 @@ function injectAuthProfile(logger) {
8397
8684
  for (const agentId of agents) {
8398
8685
  const authDir = join7(agentsDir, agentId, "agent");
8399
8686
  const authPath = join7(authDir, "auth-profiles.json");
8400
- if (!existsSync2(authDir)) {
8687
+ if (!existsSync3(authDir)) {
8401
8688
  try {
8402
8689
  mkdirSync2(authDir, { recursive: true });
8403
8690
  } catch {
@@ -8408,7 +8695,7 @@ function injectAuthProfile(logger) {
8408
8695
  version: 1,
8409
8696
  profiles: {}
8410
8697
  };
8411
- if (existsSync2(authPath)) {
8698
+ if (existsSync3(authPath)) {
8412
8699
  try {
8413
8700
  const existing = JSON.parse(readTextFileSync(authPath));
8414
8701
  if (existing.version && existing.profiles) {
@@ -8552,7 +8839,7 @@ async function createWalletCommand() {
8552
8839
  let walletKey;
8553
8840
  let address;
8554
8841
  try {
8555
- if (existsSync2(WALLET_FILE)) {
8842
+ if (existsSync3(WALLET_FILE)) {
8556
8843
  walletKey = readTextFileSync(WALLET_FILE).trim();
8557
8844
  if (walletKey.startsWith("0x") && walletKey.length === 66) {
8558
8845
  const account = privateKeyToAccount4(walletKey);
@@ -8582,7 +8869,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8582
8869
  ];
8583
8870
  let hasMnemonic = false;
8584
8871
  try {
8585
- if (existsSync2(MNEMONIC_FILE)) {
8872
+ if (existsSync3(MNEMONIC_FILE)) {
8586
8873
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8587
8874
  if (mnemonic) {
8588
8875
  hasMnemonic = true;
@@ -8624,7 +8911,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8624
8911
  if (subcommand === "solana") {
8625
8912
  try {
8626
8913
  let solanaAddr;
8627
- if (existsSync2(MNEMONIC_FILE)) {
8914
+ if (existsSync3(MNEMONIC_FILE)) {
8628
8915
  const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8629
8916
  if (existingMnemonic) {
8630
8917
  await savePaymentChain("solana");
@@ -8669,7 +8956,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8669
8956
  }
8670
8957
  if (subcommand === "migrate-solana") {
8671
8958
  try {
8672
- if (!existsSync2(MNEMONIC_FILE)) {
8959
+ if (!existsSync3(MNEMONIC_FILE)) {
8673
8960
  return {
8674
8961
  text: "No mnemonic file found. Solana wallet not set up \u2014 nothing to migrate.",
8675
8962
  isError: true
@@ -8772,7 +9059,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8772
9059
  }
8773
9060
  let solanaSection = "";
8774
9061
  try {
8775
- if (existsSync2(MNEMONIC_FILE)) {
9062
+ if (existsSync3(MNEMONIC_FILE)) {
8776
9063
  const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
8777
9064
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8778
9065
  if (mnemonic) {