@blockrun/clawrouter 0.12.31 → 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");
@@ -6337,14 +6354,31 @@ async function startProxy(options) {
6337
6354
  await mkdir3(IMAGE_DIR, { recursive: true });
6338
6355
  const port2 = server.address()?.port ?? 8402;
6339
6356
  for (const img of result.data) {
6340
- const m = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
6341
- if (m) {
6342
- const [, mimeType, b64] = m;
6357
+ const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
6358
+ if (dataUriMatch) {
6359
+ const [, mimeType, b64] = dataUriMatch;
6343
6360
  const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
6344
6361
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
6345
6362
  await writeFile2(join5(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
6346
6363
  img.url = `http://localhost:${port2}/images/${filename}`;
6347
6364
  console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
6365
+ } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
6366
+ try {
6367
+ const imgResp = await fetch(img.url);
6368
+ if (imgResp.ok) {
6369
+ const contentType = imgResp.headers.get("content-type") ?? "image/png";
6370
+ const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
6371
+ const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
6372
+ const buf = Buffer.from(await imgResp.arrayBuffer());
6373
+ await writeFile2(join5(IMAGE_DIR, filename), buf);
6374
+ img.url = `http://localhost:${port2}/images/${filename}`;
6375
+ console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
6376
+ }
6377
+ } catch (downloadErr) {
6378
+ console.warn(
6379
+ `[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
6380
+ );
6381
+ }
6348
6382
  }
6349
6383
  }
6350
6384
  }
@@ -6360,6 +6394,76 @@ async function startProxy(options) {
6360
6394
  }
6361
6395
  return;
6362
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
+ }
6363
6467
  if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
6364
6468
  try {
6365
6469
  await proxyPartnerRequest(req, res, apiBase, payFetch);
@@ -6974,6 +7078,177 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6974
7078
  }
6975
7079
  return;
6976
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
+ }
6977
7252
  if (parsed.stream === true) {
6978
7253
  parsed.stream = false;
6979
7254
  bodyModified = true;
@@ -7763,7 +8038,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
7763
8038
  // src/index.ts
7764
8039
  import {
7765
8040
  writeFileSync as writeFileSync2,
7766
- existsSync as existsSync2,
8041
+ existsSync as existsSync3,
7767
8042
  readdirSync,
7768
8043
  mkdirSync as mkdirSync2,
7769
8044
  copyFileSync,
@@ -8205,7 +8480,7 @@ function injectModelsConfig(logger) {
8205
8480
  const configPath = join7(configDir, "openclaw.json");
8206
8481
  let config = {};
8207
8482
  let needsWrite = false;
8208
- if (!existsSync2(configDir)) {
8483
+ if (!existsSync3(configDir)) {
8209
8484
  try {
8210
8485
  mkdirSync2(configDir, { recursive: true });
8211
8486
  logger.info("Created OpenClaw config directory");
@@ -8216,7 +8491,7 @@ function injectModelsConfig(logger) {
8216
8491
  return;
8217
8492
  }
8218
8493
  }
8219
- if (existsSync2(configPath)) {
8494
+ if (existsSync3(configPath)) {
8220
8495
  try {
8221
8496
  const content = readTextFileSync(configPath).trim();
8222
8497
  if (content) {
@@ -8364,7 +8639,7 @@ function injectModelsConfig(logger) {
8364
8639
  }
8365
8640
  function injectAuthProfile(logger) {
8366
8641
  const agentsDir = join7(homedir6(), ".openclaw", "agents");
8367
- if (!existsSync2(agentsDir)) {
8642
+ if (!existsSync3(agentsDir)) {
8368
8643
  try {
8369
8644
  mkdirSync2(agentsDir, { recursive: true });
8370
8645
  } catch (err) {
@@ -8382,7 +8657,7 @@ function injectAuthProfile(logger) {
8382
8657
  for (const agentId of agents) {
8383
8658
  const authDir = join7(agentsDir, agentId, "agent");
8384
8659
  const authPath = join7(authDir, "auth-profiles.json");
8385
- if (!existsSync2(authDir)) {
8660
+ if (!existsSync3(authDir)) {
8386
8661
  try {
8387
8662
  mkdirSync2(authDir, { recursive: true });
8388
8663
  } catch {
@@ -8393,7 +8668,7 @@ function injectAuthProfile(logger) {
8393
8668
  version: 1,
8394
8669
  profiles: {}
8395
8670
  };
8396
- if (existsSync2(authPath)) {
8671
+ if (existsSync3(authPath)) {
8397
8672
  try {
8398
8673
  const existing = JSON.parse(readTextFileSync(authPath));
8399
8674
  if (existing.version && existing.profiles) {
@@ -8537,7 +8812,7 @@ async function createWalletCommand() {
8537
8812
  let walletKey;
8538
8813
  let address;
8539
8814
  try {
8540
- if (existsSync2(WALLET_FILE)) {
8815
+ if (existsSync3(WALLET_FILE)) {
8541
8816
  walletKey = readTextFileSync(WALLET_FILE).trim();
8542
8817
  if (walletKey.startsWith("0x") && walletKey.length === 66) {
8543
8818
  const account = privateKeyToAccount4(walletKey);
@@ -8567,7 +8842,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8567
8842
  ];
8568
8843
  let hasMnemonic = false;
8569
8844
  try {
8570
- if (existsSync2(MNEMONIC_FILE)) {
8845
+ if (existsSync3(MNEMONIC_FILE)) {
8571
8846
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8572
8847
  if (mnemonic) {
8573
8848
  hasMnemonic = true;
@@ -8609,7 +8884,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8609
8884
  if (subcommand === "solana") {
8610
8885
  try {
8611
8886
  let solanaAddr;
8612
- if (existsSync2(MNEMONIC_FILE)) {
8887
+ if (existsSync3(MNEMONIC_FILE)) {
8613
8888
  const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8614
8889
  if (existingMnemonic) {
8615
8890
  await savePaymentChain("solana");
@@ -8654,7 +8929,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8654
8929
  }
8655
8930
  if (subcommand === "migrate-solana") {
8656
8931
  try {
8657
- if (!existsSync2(MNEMONIC_FILE)) {
8932
+ if (!existsSync3(MNEMONIC_FILE)) {
8658
8933
  return {
8659
8934
  text: "No mnemonic file found. Solana wallet not set up \u2014 nothing to migrate.",
8660
8935
  isError: true
@@ -8757,7 +9032,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
8757
9032
  }
8758
9033
  let solanaSection = "";
8759
9034
  try {
8760
- if (existsSync2(MNEMONIC_FILE)) {
9035
+ if (existsSync3(MNEMONIC_FILE)) {
8761
9036
  const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
8762
9037
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8763
9038
  if (mnemonic) {