@bunny-agent/daemon 0.9.28 → 0.9.29-beta.0

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/bundle.mjs CHANGED
@@ -263376,18 +263376,139 @@ var generateImageSchema = {
263376
263376
  required: ["prompt"],
263377
263377
  additionalProperties: false
263378
263378
  };
263379
- async function resolveB64(item) {
263379
+ async function resolveB64(item, apiKey) {
263380
263380
  if (item.b64_json)
263381
263381
  return item.b64_json;
263382
- if (item.url) {
263383
- const res = await fetch(item.url);
263382
+ if (item.b64Json)
263383
+ return item.b64Json;
263384
+ if (item.image_base64)
263385
+ return item.image_base64;
263386
+ if (item.imageBase64)
263387
+ return item.imageBase64;
263388
+ if (item.base64)
263389
+ return item.base64;
263390
+ if (typeof item.image === "string")
263391
+ return item.image;
263392
+ if (item.image?.b64_json)
263393
+ return item.image.b64_json;
263394
+ if (item.image?.base64)
263395
+ return item.image.base64;
263396
+ const url = item.url ?? item.image_url ?? item.imageUrl ?? item.image?.url;
263397
+ if (url) {
263398
+ const headers = {};
263399
+ if (apiKey) {
263400
+ headers.Authorization = `Bearer ${apiKey}`;
263401
+ }
263402
+ const res = await fetch(url, { headers });
263384
263403
  if (res.ok)
263385
263404
  return Buffer.from(await res.arrayBuffer()).toString("base64");
263386
263405
  }
263387
263406
  return void 0;
263388
263407
  }
263389
- async function saveImageItem(item, filePath) {
263390
- const b64 = await resolveB64(item);
263408
+ function pickImageItem(response) {
263409
+ const tryFromObject = (value2) => {
263410
+ if (!value2 || typeof value2 !== "object")
263411
+ return void 0;
263412
+ const obj = value2;
263413
+ return {
263414
+ b64_json: obj.b64_json ?? obj.b64Json,
263415
+ b64Json: obj.b64Json,
263416
+ url: obj.url ?? obj.imageUrl,
263417
+ image_base64: obj.image_base64 ?? obj.imageBase64,
263418
+ imageBase64: obj.imageBase64,
263419
+ image_url: obj.image_url ?? obj.imageUrl,
263420
+ imageUrl: obj.imageUrl,
263421
+ base64: obj.base64,
263422
+ image: obj.image
263423
+ };
263424
+ };
263425
+ const asItem = (value2) => {
263426
+ if (value2 == null)
263427
+ return void 0;
263428
+ if (typeof value2 === "string") {
263429
+ return { base64: value2 };
263430
+ }
263431
+ if (typeof value2 === "object") {
263432
+ const normalized = tryFromObject(value2);
263433
+ if (normalized)
263434
+ return normalized;
263435
+ }
263436
+ return void 0;
263437
+ };
263438
+ const fromDataArray = Array.isArray(response.data) ? asItem(response.data[0]) : void 0;
263439
+ if (fromDataArray)
263440
+ return fromDataArray;
263441
+ const fromDataValue = asItem(response.data);
263442
+ if (fromDataValue)
263443
+ return fromDataValue;
263444
+ const responseRecord = response;
263445
+ const imagesValue = responseRecord.images;
263446
+ const outputValue = responseRecord.output;
263447
+ const fromImagesArray = Array.isArray(imagesValue) ? asItem(imagesValue[0]) : void 0;
263448
+ if (fromImagesArray)
263449
+ return fromImagesArray;
263450
+ const fromImagesValue = asItem(imagesValue);
263451
+ if (fromImagesValue)
263452
+ return fromImagesValue;
263453
+ const fromOutputArray = Array.isArray(outputValue) ? asItem(outputValue[0]) : void 0;
263454
+ if (fromOutputArray)
263455
+ return fromOutputArray;
263456
+ const fromOutputValue = asItem(outputValue);
263457
+ if (fromOutputValue)
263458
+ return fromOutputValue;
263459
+ const fromTopLevel = asItem(response);
263460
+ if (fromTopLevel)
263461
+ return fromTopLevel;
263462
+ const queue = [response];
263463
+ while (queue.length > 0) {
263464
+ const current = queue.shift();
263465
+ if (current == null)
263466
+ continue;
263467
+ if (typeof current === "string") {
263468
+ if (/^[A-Za-z0-9+/=]{32,}$/.test(current))
263469
+ return { base64: current };
263470
+ continue;
263471
+ }
263472
+ if (typeof current !== "object")
263473
+ continue;
263474
+ const normalized = tryFromObject(current);
263475
+ if (normalized) {
263476
+ const hasUsefulField = Boolean(normalized.b64_json ?? normalized.b64Json ?? normalized.image_base64 ?? normalized.imageBase64 ?? normalized.base64 ?? normalized.url ?? normalized.image_url ?? normalized.imageUrl ?? (typeof normalized.image === "string" ? normalized.image : normalized.image?.b64_json ?? normalized.image?.base64 ?? normalized.image?.url));
263477
+ if (hasUsefulField)
263478
+ return normalized;
263479
+ }
263480
+ if (Array.isArray(current)) {
263481
+ queue.push(...current);
263482
+ continue;
263483
+ }
263484
+ for (const value2 of Object.values(current)) {
263485
+ queue.push(value2);
263486
+ }
263487
+ }
263488
+ return {};
263489
+ }
263490
+ function detectImageMime(filePath) {
263491
+ const ext2 = extname2(filePath).toLowerCase();
263492
+ if (ext2 === ".jpg" || ext2 === ".jpeg")
263493
+ return "image/jpeg";
263494
+ if (ext2 === ".webp")
263495
+ return "image/webp";
263496
+ if (ext2 === ".gif")
263497
+ return "image/gif";
263498
+ return "image/png";
263499
+ }
263500
+ function buildPolicySafeEditPrompt(prompt) {
263501
+ const riskyPattern = /\b(watermark|watermarks|logo|logos|copyright|brand mark|remove branding)\b/i;
263502
+ if (!riskyPattern.test(prompt)) {
263503
+ return { prompt, rewritten: false };
263504
+ }
263505
+ return {
263506
+ prompt: "Clean up distracting overlay text or marks naturally while preserving the original scene, style, and layout. Keep the result seamless and high quality.",
263507
+ rewritten: true
263508
+ };
263509
+ }
263510
+ async function saveImageItem(item, filePath, apiKey) {
263511
+ const b64 = await resolveB64(item, apiKey);
263391
263512
  if (!b64)
263392
263513
  return void 0;
263393
263514
  mkdirSync10(dirname19(filePath), { recursive: true });
@@ -263428,20 +263549,22 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263428
263549
  prompt,
263429
263550
  n: 1,
263430
263551
  size,
263431
- quality
263552
+ quality,
263553
+ response_format: "b64_json",
263554
+ output_format: "png"
263432
263555
  })
263433
263556
  });
263434
263557
  if (!res.ok) {
263435
263558
  throw new Error(`Image generation failed (${res.status}): ${await res.text()}`);
263436
263559
  }
263437
263560
  const json = await res.json();
263438
- const item = json.data?.[0] ?? {};
263439
- const savedPath = await saveImageItem(item, filePath);
263561
+ const item = pickImageItem(json);
263562
+ const savedPath = await saveImageItem(item, filePath, apiKey);
263440
263563
  return {
263441
263564
  content: [
263442
263565
  {
263443
263566
  type: "text",
263444
- text: savedPath ?? "Image generated but could not be saved."
263567
+ text: savedPath ?? `Image generated but could not be saved: no image payload returned; image_model: ${imageModelId}`
263445
263568
  }
263446
263569
  ],
263447
263570
  details: {
@@ -263461,6 +263584,192 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263461
263584
  }
263462
263585
  };
263463
263586
  }
263587
+ var editImageSchema = {
263588
+ type: "object",
263589
+ properties: {
263590
+ image: {
263591
+ type: "string",
263592
+ description: "Path to the source image file to edit (relative to working directory or absolute)."
263593
+ },
263594
+ prompt: {
263595
+ type: "string",
263596
+ description: "Text description of the desired final image. Describe the full result, not just the change."
263597
+ },
263598
+ mask: {
263599
+ type: "string",
263600
+ description: "Optional path to a mask image (PNG with transparent areas indicating where to edit). If omitted, the model decides what to change based on the prompt."
263601
+ },
263602
+ filename: {
263603
+ type: "string",
263604
+ description: "Output filename with extension, e.g. 'edited_cat.png'. Defaults to a timestamp-based name."
263605
+ },
263606
+ size: {
263607
+ type: "string",
263608
+ enum: ["1024x1024", "1024x1536", "1536x1024", "auto"],
263609
+ description: "Output image dimensions. Optional; omit or set auto to let model decide."
263610
+ },
263611
+ quality: {
263612
+ type: "string",
263613
+ enum: ["low", "medium", "high", "auto"],
263614
+ description: "Image quality. Optional; omit or set auto to let model decide."
263615
+ }
263616
+ },
263617
+ required: ["image", "prompt"],
263618
+ additionalProperties: false
263619
+ };
263620
+ function buildMultipartBody(fields, files) {
263621
+ const boundary = `----SandagentBoundary${Date.now()}${Math.random().toString(36).slice(2)}`;
263622
+ const parts = [];
263623
+ for (const { name, value: value2 } of fields) {
263624
+ parts.push(Buffer.from(`--${boundary}\r
263625
+ Content-Disposition: form-data; name="${name}"\r
263626
+ \r
263627
+ ${value2}\r
263628
+ `));
263629
+ }
263630
+ for (const { name, filename, buffer, mime } of files) {
263631
+ parts.push(Buffer.from(`--${boundary}\r
263632
+ Content-Disposition: form-data; name="${name}"; filename="${filename}"\r
263633
+ Content-Type: ${mime}\r
263634
+ \r
263635
+ `));
263636
+ parts.push(buffer);
263637
+ parts.push(Buffer.from("\r\n"));
263638
+ }
263639
+ parts.push(Buffer.from(`--${boundary}--\r
263640
+ `));
263641
+ return {
263642
+ body: Buffer.concat(parts),
263643
+ contentType: `multipart/form-data; boundary=${boundary}`
263644
+ };
263645
+ }
263646
+ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
263647
+ return {
263648
+ name: "edit_image",
263649
+ label: "edit image",
263650
+ description: "Edit an existing image based on a text prompt. Optionally use a mask to control which areas to modify. Saves the result to disk and returns the file path.",
263651
+ promptSnippet: "edit_image(image, prompt, mask?, filename?, size?, quality?) - edit an existing image",
263652
+ promptGuidelines: [
263653
+ "Use edit_image when the user wants to modify, retouch, or transform an existing image.",
263654
+ "The prompt should describe the full desired final image, not just the change.",
263655
+ "Provide the source image path. Use a mask image (PNG with transparent areas) to control where edits happen.",
263656
+ "Without a mask, the model decides what to change based on the prompt."
263657
+ ],
263658
+ // biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
263659
+ parameters: editImageSchema,
263660
+ async execute(_toolCallId, params, _signal, _onUpdate) {
263661
+ const { readFileSync: readFileSync24, existsSync: existsSync31 } = await import("node:fs");
263662
+ const { resolve: resolve14, basename: basename12 } = await import("node:path");
263663
+ const p = params;
263664
+ const imagePath = p.image;
263665
+ const prompt = p.prompt;
263666
+ const maskPath = p.mask;
263667
+ const size = p.size;
263668
+ const quality = p.quality;
263669
+ const rawFilename = p.filename;
263670
+ const safePrompt = buildPolicySafeEditPrompt(prompt);
263671
+ const resolvedImage = resolve14(cwd, imagePath);
263672
+ if (!existsSync31(resolvedImage)) {
263673
+ return {
263674
+ content: [
263675
+ {
263676
+ type: "text",
263677
+ text: `Image edit error: source image not found at ${resolvedImage}`
263678
+ }
263679
+ ],
263680
+ details: void 0
263681
+ };
263682
+ }
263683
+ const filename = rawFilename ? extname2(rawFilename) ? rawFilename : `${rawFilename}.png` : `edited_${Date.now()}.png`;
263684
+ const filePath = join34(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
263685
+ try {
263686
+ const imageBuffer = readFileSync24(resolvedImage);
263687
+ const fields = [
263688
+ { name: "model", value: imageModelId },
263689
+ { name: "prompt", value: safePrompt.prompt },
263690
+ { name: "n", value: "1" },
263691
+ { name: "response_format", value: "b64_json" },
263692
+ { name: "output_format", value: "png" }
263693
+ ];
263694
+ if (size && size !== "auto") {
263695
+ fields.push({ name: "size", value: size });
263696
+ }
263697
+ if (quality && quality !== "auto") {
263698
+ fields.push({ name: "quality", value: quality });
263699
+ }
263700
+ const files = [
263701
+ {
263702
+ name: "image",
263703
+ filename: basename12(resolvedImage),
263704
+ buffer: imageBuffer,
263705
+ mime: detectImageMime(resolvedImage)
263706
+ }
263707
+ ];
263708
+ if (maskPath) {
263709
+ const resolvedMask = resolve14(cwd, maskPath);
263710
+ if (existsSync31(resolvedMask)) {
263711
+ files.push({
263712
+ name: "mask",
263713
+ filename: basename12(resolvedMask),
263714
+ buffer: readFileSync24(resolvedMask),
263715
+ mime: detectImageMime(resolvedMask)
263716
+ });
263717
+ }
263718
+ }
263719
+ const { body: multipartBody, contentType } = buildMultipartBody(fields, files);
263720
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/images/edits`;
263721
+ const sendRequest = async (body, type) => {
263722
+ const res = await fetch(url, {
263723
+ method: "POST",
263724
+ headers: {
263725
+ "Content-Type": type,
263726
+ Authorization: `Bearer ${apiKey}`
263727
+ },
263728
+ body
263729
+ });
263730
+ if (!res.ok) {
263731
+ throw new Error(`Image edit failed (${res.status}): ${await res.text()}`);
263732
+ }
263733
+ return await res.json();
263734
+ };
263735
+ let json = await sendRequest(multipartBody, contentType);
263736
+ const item = pickImageItem(json);
263737
+ let savedPath = await saveImageItem(item, filePath, apiKey);
263738
+ const firstResponseHasEmptyDataArray = Array.isArray(json.data) && json.data.length === 0;
263739
+ if (!savedPath && safePrompt.rewritten && firstResponseHasEmptyDataArray) {
263740
+ const retryFields = fields.map((f3) => f3.name === "prompt" ? {
263741
+ name: "prompt",
263742
+ value: "Remove only distracting overlay text artifacts naturally and keep all original content unchanged."
263743
+ } : f3);
263744
+ const retryMultipart = buildMultipartBody(retryFields, files);
263745
+ json = await sendRequest(retryMultipart.body, retryMultipart.contentType);
263746
+ const retryItem = pickImageItem(json);
263747
+ savedPath = await saveImageItem(retryItem, filePath, apiKey);
263748
+ }
263749
+ return {
263750
+ content: [
263751
+ {
263752
+ type: "text",
263753
+ text: savedPath ?? `Image edited but could not be saved: no image payload returned; image_model: ${imageModelId}`
263754
+ }
263755
+ ],
263756
+ details: {
263757
+ filePath: savedPath,
263758
+ response: json
263759
+ }
263760
+ };
263761
+ } catch (e2) {
263762
+ const msg = e2 instanceof Error ? e2.message : String(e2);
263763
+ return {
263764
+ content: [
263765
+ { type: "text", text: `Image edit error: ${msg}` }
263766
+ ],
263767
+ details: void 0
263768
+ };
263769
+ }
263770
+ }
263771
+ };
263772
+ }
263464
263773
 
263465
263774
  // ../../packages/runner-pi/dist/web-tools.js
263466
263775
  var braveProvider = {
@@ -264032,7 +264341,7 @@ function createPiRunner(options2 = {}) {
264032
264341
  const customTools = options2.env && Object.keys(options2.env).length > 0 ? buildSecretAwareTools(cwd, options2.env) : [];
264033
264342
  if (imageModelName) {
264034
264343
  const apiKey = await modelRegistry2.authStorage.getApiKey(provider) ?? "";
264035
- customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey));
264344
+ customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey), buildImageEditTool(cwd, imageModelName, model.baseUrl, apiKey));
264036
264345
  }
264037
264346
  const { session } = await createAgentSession({
264038
264347
  cwd,
@@ -264203,7 +264512,7 @@ function createPiRunner(options2 = {}) {
264203
264512
  if (options2.env && Object.keys(options2.env).length > 0) {
264204
264513
  output = redactSecrets(output, options2.env);
264205
264514
  }
264206
- if (event.toolName === "generate_image" && event.result !== null && typeof event.result === "object") {
264515
+ if ((event.toolName === "generate_image" || event.toolName === "edit_image") && event.result !== null && typeof event.result === "object") {
264207
264516
  const details = event.result.details;
264208
264517
  const u = details?.response?.usage;
264209
264518
  if (u) {
@@ -264436,7 +264745,9 @@ async function bunnyAgentRun(req, res, env2) {
264436
264745
  cwd: req.cwd ?? process.env.BUNNY_AGENT_ROOT ?? "/workspace",
264437
264746
  yolo: req.yolo,
264438
264747
  env: env2,
264439
- abortController
264748
+ abortController,
264749
+ // API: caller owns resume/session; do not read/write cwd/.bunny-agent or auto-load CLAUDE.md.
264750
+ autoInject: false
264440
264751
  });
264441
264752
  for await (const chunk of stream2) {
264442
264753
  res.write(chunk);
package/dist/index.js CHANGED
@@ -263372,18 +263372,139 @@ var generateImageSchema = {
263372
263372
  required: ["prompt"],
263373
263373
  additionalProperties: false
263374
263374
  };
263375
- async function resolveB64(item) {
263375
+ async function resolveB64(item, apiKey) {
263376
263376
  if (item.b64_json)
263377
263377
  return item.b64_json;
263378
- if (item.url) {
263379
- const res = await fetch(item.url);
263378
+ if (item.b64Json)
263379
+ return item.b64Json;
263380
+ if (item.image_base64)
263381
+ return item.image_base64;
263382
+ if (item.imageBase64)
263383
+ return item.imageBase64;
263384
+ if (item.base64)
263385
+ return item.base64;
263386
+ if (typeof item.image === "string")
263387
+ return item.image;
263388
+ if (item.image?.b64_json)
263389
+ return item.image.b64_json;
263390
+ if (item.image?.base64)
263391
+ return item.image.base64;
263392
+ const url = item.url ?? item.image_url ?? item.imageUrl ?? item.image?.url;
263393
+ if (url) {
263394
+ const headers = {};
263395
+ if (apiKey) {
263396
+ headers.Authorization = `Bearer ${apiKey}`;
263397
+ }
263398
+ const res = await fetch(url, { headers });
263380
263399
  if (res.ok)
263381
263400
  return Buffer.from(await res.arrayBuffer()).toString("base64");
263382
263401
  }
263383
263402
  return void 0;
263384
263403
  }
263385
- async function saveImageItem(item, filePath) {
263386
- const b64 = await resolveB64(item);
263404
+ function pickImageItem(response) {
263405
+ const tryFromObject = (value2) => {
263406
+ if (!value2 || typeof value2 !== "object")
263407
+ return void 0;
263408
+ const obj = value2;
263409
+ return {
263410
+ b64_json: obj.b64_json ?? obj.b64Json,
263411
+ b64Json: obj.b64Json,
263412
+ url: obj.url ?? obj.imageUrl,
263413
+ image_base64: obj.image_base64 ?? obj.imageBase64,
263414
+ imageBase64: obj.imageBase64,
263415
+ image_url: obj.image_url ?? obj.imageUrl,
263416
+ imageUrl: obj.imageUrl,
263417
+ base64: obj.base64,
263418
+ image: obj.image
263419
+ };
263420
+ };
263421
+ const asItem = (value2) => {
263422
+ if (value2 == null)
263423
+ return void 0;
263424
+ if (typeof value2 === "string") {
263425
+ return { base64: value2 };
263426
+ }
263427
+ if (typeof value2 === "object") {
263428
+ const normalized = tryFromObject(value2);
263429
+ if (normalized)
263430
+ return normalized;
263431
+ }
263432
+ return void 0;
263433
+ };
263434
+ const fromDataArray = Array.isArray(response.data) ? asItem(response.data[0]) : void 0;
263435
+ if (fromDataArray)
263436
+ return fromDataArray;
263437
+ const fromDataValue = asItem(response.data);
263438
+ if (fromDataValue)
263439
+ return fromDataValue;
263440
+ const responseRecord = response;
263441
+ const imagesValue = responseRecord.images;
263442
+ const outputValue = responseRecord.output;
263443
+ const fromImagesArray = Array.isArray(imagesValue) ? asItem(imagesValue[0]) : void 0;
263444
+ if (fromImagesArray)
263445
+ return fromImagesArray;
263446
+ const fromImagesValue = asItem(imagesValue);
263447
+ if (fromImagesValue)
263448
+ return fromImagesValue;
263449
+ const fromOutputArray = Array.isArray(outputValue) ? asItem(outputValue[0]) : void 0;
263450
+ if (fromOutputArray)
263451
+ return fromOutputArray;
263452
+ const fromOutputValue = asItem(outputValue);
263453
+ if (fromOutputValue)
263454
+ return fromOutputValue;
263455
+ const fromTopLevel = asItem(response);
263456
+ if (fromTopLevel)
263457
+ return fromTopLevel;
263458
+ const queue = [response];
263459
+ while (queue.length > 0) {
263460
+ const current = queue.shift();
263461
+ if (current == null)
263462
+ continue;
263463
+ if (typeof current === "string") {
263464
+ if (/^[A-Za-z0-9+/=]{32,}$/.test(current))
263465
+ return { base64: current };
263466
+ continue;
263467
+ }
263468
+ if (typeof current !== "object")
263469
+ continue;
263470
+ const normalized = tryFromObject(current);
263471
+ if (normalized) {
263472
+ const hasUsefulField = Boolean(normalized.b64_json ?? normalized.b64Json ?? normalized.image_base64 ?? normalized.imageBase64 ?? normalized.base64 ?? normalized.url ?? normalized.image_url ?? normalized.imageUrl ?? (typeof normalized.image === "string" ? normalized.image : normalized.image?.b64_json ?? normalized.image?.base64 ?? normalized.image?.url));
263473
+ if (hasUsefulField)
263474
+ return normalized;
263475
+ }
263476
+ if (Array.isArray(current)) {
263477
+ queue.push(...current);
263478
+ continue;
263479
+ }
263480
+ for (const value2 of Object.values(current)) {
263481
+ queue.push(value2);
263482
+ }
263483
+ }
263484
+ return {};
263485
+ }
263486
+ function detectImageMime(filePath) {
263487
+ const ext2 = extname2(filePath).toLowerCase();
263488
+ if (ext2 === ".jpg" || ext2 === ".jpeg")
263489
+ return "image/jpeg";
263490
+ if (ext2 === ".webp")
263491
+ return "image/webp";
263492
+ if (ext2 === ".gif")
263493
+ return "image/gif";
263494
+ return "image/png";
263495
+ }
263496
+ function buildPolicySafeEditPrompt(prompt) {
263497
+ const riskyPattern = /\b(watermark|watermarks|logo|logos|copyright|brand mark|remove branding)\b/i;
263498
+ if (!riskyPattern.test(prompt)) {
263499
+ return { prompt, rewritten: false };
263500
+ }
263501
+ return {
263502
+ prompt: "Clean up distracting overlay text or marks naturally while preserving the original scene, style, and layout. Keep the result seamless and high quality.",
263503
+ rewritten: true
263504
+ };
263505
+ }
263506
+ async function saveImageItem(item, filePath, apiKey) {
263507
+ const b64 = await resolveB64(item, apiKey);
263387
263508
  if (!b64)
263388
263509
  return void 0;
263389
263510
  mkdirSync10(dirname19(filePath), { recursive: true });
@@ -263424,20 +263545,22 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263424
263545
  prompt,
263425
263546
  n: 1,
263426
263547
  size,
263427
- quality
263548
+ quality,
263549
+ response_format: "b64_json",
263550
+ output_format: "png"
263428
263551
  })
263429
263552
  });
263430
263553
  if (!res.ok) {
263431
263554
  throw new Error(`Image generation failed (${res.status}): ${await res.text()}`);
263432
263555
  }
263433
263556
  const json = await res.json();
263434
- const item = json.data?.[0] ?? {};
263435
- const savedPath = await saveImageItem(item, filePath);
263557
+ const item = pickImageItem(json);
263558
+ const savedPath = await saveImageItem(item, filePath, apiKey);
263436
263559
  return {
263437
263560
  content: [
263438
263561
  {
263439
263562
  type: "text",
263440
- text: savedPath ?? "Image generated but could not be saved."
263563
+ text: savedPath ?? `Image generated but could not be saved: no image payload returned; image_model: ${imageModelId}`
263441
263564
  }
263442
263565
  ],
263443
263566
  details: {
@@ -263457,6 +263580,192 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263457
263580
  }
263458
263581
  };
263459
263582
  }
263583
+ var editImageSchema = {
263584
+ type: "object",
263585
+ properties: {
263586
+ image: {
263587
+ type: "string",
263588
+ description: "Path to the source image file to edit (relative to working directory or absolute)."
263589
+ },
263590
+ prompt: {
263591
+ type: "string",
263592
+ description: "Text description of the desired final image. Describe the full result, not just the change."
263593
+ },
263594
+ mask: {
263595
+ type: "string",
263596
+ description: "Optional path to a mask image (PNG with transparent areas indicating where to edit). If omitted, the model decides what to change based on the prompt."
263597
+ },
263598
+ filename: {
263599
+ type: "string",
263600
+ description: "Output filename with extension, e.g. 'edited_cat.png'. Defaults to a timestamp-based name."
263601
+ },
263602
+ size: {
263603
+ type: "string",
263604
+ enum: ["1024x1024", "1024x1536", "1536x1024", "auto"],
263605
+ description: "Output image dimensions. Optional; omit or set auto to let model decide."
263606
+ },
263607
+ quality: {
263608
+ type: "string",
263609
+ enum: ["low", "medium", "high", "auto"],
263610
+ description: "Image quality. Optional; omit or set auto to let model decide."
263611
+ }
263612
+ },
263613
+ required: ["image", "prompt"],
263614
+ additionalProperties: false
263615
+ };
263616
+ function buildMultipartBody(fields, files) {
263617
+ const boundary = `----SandagentBoundary${Date.now()}${Math.random().toString(36).slice(2)}`;
263618
+ const parts = [];
263619
+ for (const { name, value: value2 } of fields) {
263620
+ parts.push(Buffer.from(`--${boundary}\r
263621
+ Content-Disposition: form-data; name="${name}"\r
263622
+ \r
263623
+ ${value2}\r
263624
+ `));
263625
+ }
263626
+ for (const { name, filename, buffer, mime } of files) {
263627
+ parts.push(Buffer.from(`--${boundary}\r
263628
+ Content-Disposition: form-data; name="${name}"; filename="${filename}"\r
263629
+ Content-Type: ${mime}\r
263630
+ \r
263631
+ `));
263632
+ parts.push(buffer);
263633
+ parts.push(Buffer.from("\r\n"));
263634
+ }
263635
+ parts.push(Buffer.from(`--${boundary}--\r
263636
+ `));
263637
+ return {
263638
+ body: Buffer.concat(parts),
263639
+ contentType: `multipart/form-data; boundary=${boundary}`
263640
+ };
263641
+ }
263642
+ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
263643
+ return {
263644
+ name: "edit_image",
263645
+ label: "edit image",
263646
+ description: "Edit an existing image based on a text prompt. Optionally use a mask to control which areas to modify. Saves the result to disk and returns the file path.",
263647
+ promptSnippet: "edit_image(image, prompt, mask?, filename?, size?, quality?) - edit an existing image",
263648
+ promptGuidelines: [
263649
+ "Use edit_image when the user wants to modify, retouch, or transform an existing image.",
263650
+ "The prompt should describe the full desired final image, not just the change.",
263651
+ "Provide the source image path. Use a mask image (PNG with transparent areas) to control where edits happen.",
263652
+ "Without a mask, the model decides what to change based on the prompt."
263653
+ ],
263654
+ // biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
263655
+ parameters: editImageSchema,
263656
+ async execute(_toolCallId, params, _signal, _onUpdate) {
263657
+ const { readFileSync: readFileSync24, existsSync: existsSync31 } = await import("node:fs");
263658
+ const { resolve: resolve14, basename: basename12 } = await import("node:path");
263659
+ const p = params;
263660
+ const imagePath = p.image;
263661
+ const prompt = p.prompt;
263662
+ const maskPath = p.mask;
263663
+ const size = p.size;
263664
+ const quality = p.quality;
263665
+ const rawFilename = p.filename;
263666
+ const safePrompt = buildPolicySafeEditPrompt(prompt);
263667
+ const resolvedImage = resolve14(cwd, imagePath);
263668
+ if (!existsSync31(resolvedImage)) {
263669
+ return {
263670
+ content: [
263671
+ {
263672
+ type: "text",
263673
+ text: `Image edit error: source image not found at ${resolvedImage}`
263674
+ }
263675
+ ],
263676
+ details: void 0
263677
+ };
263678
+ }
263679
+ const filename = rawFilename ? extname2(rawFilename) ? rawFilename : `${rawFilename}.png` : `edited_${Date.now()}.png`;
263680
+ const filePath = join34(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
263681
+ try {
263682
+ const imageBuffer = readFileSync24(resolvedImage);
263683
+ const fields = [
263684
+ { name: "model", value: imageModelId },
263685
+ { name: "prompt", value: safePrompt.prompt },
263686
+ { name: "n", value: "1" },
263687
+ { name: "response_format", value: "b64_json" },
263688
+ { name: "output_format", value: "png" }
263689
+ ];
263690
+ if (size && size !== "auto") {
263691
+ fields.push({ name: "size", value: size });
263692
+ }
263693
+ if (quality && quality !== "auto") {
263694
+ fields.push({ name: "quality", value: quality });
263695
+ }
263696
+ const files = [
263697
+ {
263698
+ name: "image",
263699
+ filename: basename12(resolvedImage),
263700
+ buffer: imageBuffer,
263701
+ mime: detectImageMime(resolvedImage)
263702
+ }
263703
+ ];
263704
+ if (maskPath) {
263705
+ const resolvedMask = resolve14(cwd, maskPath);
263706
+ if (existsSync31(resolvedMask)) {
263707
+ files.push({
263708
+ name: "mask",
263709
+ filename: basename12(resolvedMask),
263710
+ buffer: readFileSync24(resolvedMask),
263711
+ mime: detectImageMime(resolvedMask)
263712
+ });
263713
+ }
263714
+ }
263715
+ const { body: multipartBody, contentType } = buildMultipartBody(fields, files);
263716
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/images/edits`;
263717
+ const sendRequest = async (body, type) => {
263718
+ const res = await fetch(url, {
263719
+ method: "POST",
263720
+ headers: {
263721
+ "Content-Type": type,
263722
+ Authorization: `Bearer ${apiKey}`
263723
+ },
263724
+ body
263725
+ });
263726
+ if (!res.ok) {
263727
+ throw new Error(`Image edit failed (${res.status}): ${await res.text()}`);
263728
+ }
263729
+ return await res.json();
263730
+ };
263731
+ let json = await sendRequest(multipartBody, contentType);
263732
+ const item = pickImageItem(json);
263733
+ let savedPath = await saveImageItem(item, filePath, apiKey);
263734
+ const firstResponseHasEmptyDataArray = Array.isArray(json.data) && json.data.length === 0;
263735
+ if (!savedPath && safePrompt.rewritten && firstResponseHasEmptyDataArray) {
263736
+ const retryFields = fields.map((f3) => f3.name === "prompt" ? {
263737
+ name: "prompt",
263738
+ value: "Remove only distracting overlay text artifacts naturally and keep all original content unchanged."
263739
+ } : f3);
263740
+ const retryMultipart = buildMultipartBody(retryFields, files);
263741
+ json = await sendRequest(retryMultipart.body, retryMultipart.contentType);
263742
+ const retryItem = pickImageItem(json);
263743
+ savedPath = await saveImageItem(retryItem, filePath, apiKey);
263744
+ }
263745
+ return {
263746
+ content: [
263747
+ {
263748
+ type: "text",
263749
+ text: savedPath ?? `Image edited but could not be saved: no image payload returned; image_model: ${imageModelId}`
263750
+ }
263751
+ ],
263752
+ details: {
263753
+ filePath: savedPath,
263754
+ response: json
263755
+ }
263756
+ };
263757
+ } catch (e2) {
263758
+ const msg = e2 instanceof Error ? e2.message : String(e2);
263759
+ return {
263760
+ content: [
263761
+ { type: "text", text: `Image edit error: ${msg}` }
263762
+ ],
263763
+ details: void 0
263764
+ };
263765
+ }
263766
+ }
263767
+ };
263768
+ }
263460
263769
 
263461
263770
  // ../../packages/runner-pi/dist/web-tools.js
263462
263771
  var braveProvider = {
@@ -264028,7 +264337,7 @@ function createPiRunner(options2 = {}) {
264028
264337
  const customTools = options2.env && Object.keys(options2.env).length > 0 ? buildSecretAwareTools(cwd, options2.env) : [];
264029
264338
  if (imageModelName) {
264030
264339
  const apiKey = await modelRegistry2.authStorage.getApiKey(provider) ?? "";
264031
- customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey));
264340
+ customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey), buildImageEditTool(cwd, imageModelName, model.baseUrl, apiKey));
264032
264341
  }
264033
264342
  const { session } = await createAgentSession({
264034
264343
  cwd,
@@ -264199,7 +264508,7 @@ function createPiRunner(options2 = {}) {
264199
264508
  if (options2.env && Object.keys(options2.env).length > 0) {
264200
264509
  output = redactSecrets(output, options2.env);
264201
264510
  }
264202
- if (event.toolName === "generate_image" && event.result !== null && typeof event.result === "object") {
264511
+ if ((event.toolName === "generate_image" || event.toolName === "edit_image") && event.result !== null && typeof event.result === "object") {
264203
264512
  const details = event.result.details;
264204
264513
  const u = details?.response?.usage;
264205
264514
  if (u) {
@@ -264432,7 +264741,9 @@ async function bunnyAgentRun(req, res, env2) {
264432
264741
  cwd: req.cwd ?? process.env.BUNNY_AGENT_ROOT ?? "/workspace",
264433
264742
  yolo: req.yolo,
264434
264743
  env: env2,
264435
- abortController
264744
+ abortController,
264745
+ // API: caller owns resume/session; do not read/write cwd/.bunny-agent or auto-load CLAUDE.md.
264746
+ autoInject: false
264436
264747
  });
264437
264748
  for await (const chunk of stream2) {
264438
264749
  res.write(chunk);
package/dist/nextjs.js CHANGED
@@ -263368,18 +263368,139 @@ var generateImageSchema = {
263368
263368
  required: ["prompt"],
263369
263369
  additionalProperties: false
263370
263370
  };
263371
- async function resolveB64(item) {
263371
+ async function resolveB64(item, apiKey) {
263372
263372
  if (item.b64_json)
263373
263373
  return item.b64_json;
263374
- if (item.url) {
263375
- const res = await fetch(item.url);
263374
+ if (item.b64Json)
263375
+ return item.b64Json;
263376
+ if (item.image_base64)
263377
+ return item.image_base64;
263378
+ if (item.imageBase64)
263379
+ return item.imageBase64;
263380
+ if (item.base64)
263381
+ return item.base64;
263382
+ if (typeof item.image === "string")
263383
+ return item.image;
263384
+ if (item.image?.b64_json)
263385
+ return item.image.b64_json;
263386
+ if (item.image?.base64)
263387
+ return item.image.base64;
263388
+ const url = item.url ?? item.image_url ?? item.imageUrl ?? item.image?.url;
263389
+ if (url) {
263390
+ const headers = {};
263391
+ if (apiKey) {
263392
+ headers.Authorization = `Bearer ${apiKey}`;
263393
+ }
263394
+ const res = await fetch(url, { headers });
263376
263395
  if (res.ok)
263377
263396
  return Buffer.from(await res.arrayBuffer()).toString("base64");
263378
263397
  }
263379
263398
  return void 0;
263380
263399
  }
263381
- async function saveImageItem(item, filePath) {
263382
- const b64 = await resolveB64(item);
263400
+ function pickImageItem(response) {
263401
+ const tryFromObject = (value2) => {
263402
+ if (!value2 || typeof value2 !== "object")
263403
+ return void 0;
263404
+ const obj = value2;
263405
+ return {
263406
+ b64_json: obj.b64_json ?? obj.b64Json,
263407
+ b64Json: obj.b64Json,
263408
+ url: obj.url ?? obj.imageUrl,
263409
+ image_base64: obj.image_base64 ?? obj.imageBase64,
263410
+ imageBase64: obj.imageBase64,
263411
+ image_url: obj.image_url ?? obj.imageUrl,
263412
+ imageUrl: obj.imageUrl,
263413
+ base64: obj.base64,
263414
+ image: obj.image
263415
+ };
263416
+ };
263417
+ const asItem = (value2) => {
263418
+ if (value2 == null)
263419
+ return void 0;
263420
+ if (typeof value2 === "string") {
263421
+ return { base64: value2 };
263422
+ }
263423
+ if (typeof value2 === "object") {
263424
+ const normalized = tryFromObject(value2);
263425
+ if (normalized)
263426
+ return normalized;
263427
+ }
263428
+ return void 0;
263429
+ };
263430
+ const fromDataArray = Array.isArray(response.data) ? asItem(response.data[0]) : void 0;
263431
+ if (fromDataArray)
263432
+ return fromDataArray;
263433
+ const fromDataValue = asItem(response.data);
263434
+ if (fromDataValue)
263435
+ return fromDataValue;
263436
+ const responseRecord = response;
263437
+ const imagesValue = responseRecord.images;
263438
+ const outputValue = responseRecord.output;
263439
+ const fromImagesArray = Array.isArray(imagesValue) ? asItem(imagesValue[0]) : void 0;
263440
+ if (fromImagesArray)
263441
+ return fromImagesArray;
263442
+ const fromImagesValue = asItem(imagesValue);
263443
+ if (fromImagesValue)
263444
+ return fromImagesValue;
263445
+ const fromOutputArray = Array.isArray(outputValue) ? asItem(outputValue[0]) : void 0;
263446
+ if (fromOutputArray)
263447
+ return fromOutputArray;
263448
+ const fromOutputValue = asItem(outputValue);
263449
+ if (fromOutputValue)
263450
+ return fromOutputValue;
263451
+ const fromTopLevel = asItem(response);
263452
+ if (fromTopLevel)
263453
+ return fromTopLevel;
263454
+ const queue = [response];
263455
+ while (queue.length > 0) {
263456
+ const current = queue.shift();
263457
+ if (current == null)
263458
+ continue;
263459
+ if (typeof current === "string") {
263460
+ if (/^[A-Za-z0-9+/=]{32,}$/.test(current))
263461
+ return { base64: current };
263462
+ continue;
263463
+ }
263464
+ if (typeof current !== "object")
263465
+ continue;
263466
+ const normalized = tryFromObject(current);
263467
+ if (normalized) {
263468
+ const hasUsefulField = Boolean(normalized.b64_json ?? normalized.b64Json ?? normalized.image_base64 ?? normalized.imageBase64 ?? normalized.base64 ?? normalized.url ?? normalized.image_url ?? normalized.imageUrl ?? (typeof normalized.image === "string" ? normalized.image : normalized.image?.b64_json ?? normalized.image?.base64 ?? normalized.image?.url));
263469
+ if (hasUsefulField)
263470
+ return normalized;
263471
+ }
263472
+ if (Array.isArray(current)) {
263473
+ queue.push(...current);
263474
+ continue;
263475
+ }
263476
+ for (const value2 of Object.values(current)) {
263477
+ queue.push(value2);
263478
+ }
263479
+ }
263480
+ return {};
263481
+ }
263482
+ function detectImageMime(filePath) {
263483
+ const ext2 = extname2(filePath).toLowerCase();
263484
+ if (ext2 === ".jpg" || ext2 === ".jpeg")
263485
+ return "image/jpeg";
263486
+ if (ext2 === ".webp")
263487
+ return "image/webp";
263488
+ if (ext2 === ".gif")
263489
+ return "image/gif";
263490
+ return "image/png";
263491
+ }
263492
+ function buildPolicySafeEditPrompt(prompt) {
263493
+ const riskyPattern = /\b(watermark|watermarks|logo|logos|copyright|brand mark|remove branding)\b/i;
263494
+ if (!riskyPattern.test(prompt)) {
263495
+ return { prompt, rewritten: false };
263496
+ }
263497
+ return {
263498
+ prompt: "Clean up distracting overlay text or marks naturally while preserving the original scene, style, and layout. Keep the result seamless and high quality.",
263499
+ rewritten: true
263500
+ };
263501
+ }
263502
+ async function saveImageItem(item, filePath, apiKey) {
263503
+ const b64 = await resolveB64(item, apiKey);
263383
263504
  if (!b64)
263384
263505
  return void 0;
263385
263506
  mkdirSync10(dirname19(filePath), { recursive: true });
@@ -263420,20 +263541,22 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263420
263541
  prompt,
263421
263542
  n: 1,
263422
263543
  size,
263423
- quality
263544
+ quality,
263545
+ response_format: "b64_json",
263546
+ output_format: "png"
263424
263547
  })
263425
263548
  });
263426
263549
  if (!res.ok) {
263427
263550
  throw new Error(`Image generation failed (${res.status}): ${await res.text()}`);
263428
263551
  }
263429
263552
  const json = await res.json();
263430
- const item = json.data?.[0] ?? {};
263431
- const savedPath = await saveImageItem(item, filePath);
263553
+ const item = pickImageItem(json);
263554
+ const savedPath = await saveImageItem(item, filePath, apiKey);
263432
263555
  return {
263433
263556
  content: [
263434
263557
  {
263435
263558
  type: "text",
263436
- text: savedPath ?? "Image generated but could not be saved."
263559
+ text: savedPath ?? `Image generated but could not be saved: no image payload returned; image_model: ${imageModelId}`
263437
263560
  }
263438
263561
  ],
263439
263562
  details: {
@@ -263453,6 +263576,192 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263453
263576
  }
263454
263577
  };
263455
263578
  }
263579
+ var editImageSchema = {
263580
+ type: "object",
263581
+ properties: {
263582
+ image: {
263583
+ type: "string",
263584
+ description: "Path to the source image file to edit (relative to working directory or absolute)."
263585
+ },
263586
+ prompt: {
263587
+ type: "string",
263588
+ description: "Text description of the desired final image. Describe the full result, not just the change."
263589
+ },
263590
+ mask: {
263591
+ type: "string",
263592
+ description: "Optional path to a mask image (PNG with transparent areas indicating where to edit). If omitted, the model decides what to change based on the prompt."
263593
+ },
263594
+ filename: {
263595
+ type: "string",
263596
+ description: "Output filename with extension, e.g. 'edited_cat.png'. Defaults to a timestamp-based name."
263597
+ },
263598
+ size: {
263599
+ type: "string",
263600
+ enum: ["1024x1024", "1024x1536", "1536x1024", "auto"],
263601
+ description: "Output image dimensions. Optional; omit or set auto to let model decide."
263602
+ },
263603
+ quality: {
263604
+ type: "string",
263605
+ enum: ["low", "medium", "high", "auto"],
263606
+ description: "Image quality. Optional; omit or set auto to let model decide."
263607
+ }
263608
+ },
263609
+ required: ["image", "prompt"],
263610
+ additionalProperties: false
263611
+ };
263612
+ function buildMultipartBody(fields, files) {
263613
+ const boundary = `----SandagentBoundary${Date.now()}${Math.random().toString(36).slice(2)}`;
263614
+ const parts = [];
263615
+ for (const { name, value: value2 } of fields) {
263616
+ parts.push(Buffer.from(`--${boundary}\r
263617
+ Content-Disposition: form-data; name="${name}"\r
263618
+ \r
263619
+ ${value2}\r
263620
+ `));
263621
+ }
263622
+ for (const { name, filename, buffer, mime } of files) {
263623
+ parts.push(Buffer.from(`--${boundary}\r
263624
+ Content-Disposition: form-data; name="${name}"; filename="${filename}"\r
263625
+ Content-Type: ${mime}\r
263626
+ \r
263627
+ `));
263628
+ parts.push(buffer);
263629
+ parts.push(Buffer.from("\r\n"));
263630
+ }
263631
+ parts.push(Buffer.from(`--${boundary}--\r
263632
+ `));
263633
+ return {
263634
+ body: Buffer.concat(parts),
263635
+ contentType: `multipart/form-data; boundary=${boundary}`
263636
+ };
263637
+ }
263638
+ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
263639
+ return {
263640
+ name: "edit_image",
263641
+ label: "edit image",
263642
+ description: "Edit an existing image based on a text prompt. Optionally use a mask to control which areas to modify. Saves the result to disk and returns the file path.",
263643
+ promptSnippet: "edit_image(image, prompt, mask?, filename?, size?, quality?) - edit an existing image",
263644
+ promptGuidelines: [
263645
+ "Use edit_image when the user wants to modify, retouch, or transform an existing image.",
263646
+ "The prompt should describe the full desired final image, not just the change.",
263647
+ "Provide the source image path. Use a mask image (PNG with transparent areas) to control where edits happen.",
263648
+ "Without a mask, the model decides what to change based on the prompt."
263649
+ ],
263650
+ // biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
263651
+ parameters: editImageSchema,
263652
+ async execute(_toolCallId, params, _signal, _onUpdate) {
263653
+ const { readFileSync: readFileSync24, existsSync: existsSync31 } = await import("node:fs");
263654
+ const { resolve: resolve14, basename: basename12 } = await import("node:path");
263655
+ const p = params;
263656
+ const imagePath = p.image;
263657
+ const prompt = p.prompt;
263658
+ const maskPath = p.mask;
263659
+ const size = p.size;
263660
+ const quality = p.quality;
263661
+ const rawFilename = p.filename;
263662
+ const safePrompt = buildPolicySafeEditPrompt(prompt);
263663
+ const resolvedImage = resolve14(cwd, imagePath);
263664
+ if (!existsSync31(resolvedImage)) {
263665
+ return {
263666
+ content: [
263667
+ {
263668
+ type: "text",
263669
+ text: `Image edit error: source image not found at ${resolvedImage}`
263670
+ }
263671
+ ],
263672
+ details: void 0
263673
+ };
263674
+ }
263675
+ const filename = rawFilename ? extname2(rawFilename) ? rawFilename : `${rawFilename}.png` : `edited_${Date.now()}.png`;
263676
+ const filePath = join34(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
263677
+ try {
263678
+ const imageBuffer = readFileSync24(resolvedImage);
263679
+ const fields = [
263680
+ { name: "model", value: imageModelId },
263681
+ { name: "prompt", value: safePrompt.prompt },
263682
+ { name: "n", value: "1" },
263683
+ { name: "response_format", value: "b64_json" },
263684
+ { name: "output_format", value: "png" }
263685
+ ];
263686
+ if (size && size !== "auto") {
263687
+ fields.push({ name: "size", value: size });
263688
+ }
263689
+ if (quality && quality !== "auto") {
263690
+ fields.push({ name: "quality", value: quality });
263691
+ }
263692
+ const files = [
263693
+ {
263694
+ name: "image",
263695
+ filename: basename12(resolvedImage),
263696
+ buffer: imageBuffer,
263697
+ mime: detectImageMime(resolvedImage)
263698
+ }
263699
+ ];
263700
+ if (maskPath) {
263701
+ const resolvedMask = resolve14(cwd, maskPath);
263702
+ if (existsSync31(resolvedMask)) {
263703
+ files.push({
263704
+ name: "mask",
263705
+ filename: basename12(resolvedMask),
263706
+ buffer: readFileSync24(resolvedMask),
263707
+ mime: detectImageMime(resolvedMask)
263708
+ });
263709
+ }
263710
+ }
263711
+ const { body: multipartBody, contentType } = buildMultipartBody(fields, files);
263712
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/images/edits`;
263713
+ const sendRequest = async (body, type) => {
263714
+ const res = await fetch(url, {
263715
+ method: "POST",
263716
+ headers: {
263717
+ "Content-Type": type,
263718
+ Authorization: `Bearer ${apiKey}`
263719
+ },
263720
+ body
263721
+ });
263722
+ if (!res.ok) {
263723
+ throw new Error(`Image edit failed (${res.status}): ${await res.text()}`);
263724
+ }
263725
+ return await res.json();
263726
+ };
263727
+ let json = await sendRequest(multipartBody, contentType);
263728
+ const item = pickImageItem(json);
263729
+ let savedPath = await saveImageItem(item, filePath, apiKey);
263730
+ const firstResponseHasEmptyDataArray = Array.isArray(json.data) && json.data.length === 0;
263731
+ if (!savedPath && safePrompt.rewritten && firstResponseHasEmptyDataArray) {
263732
+ const retryFields = fields.map((f3) => f3.name === "prompt" ? {
263733
+ name: "prompt",
263734
+ value: "Remove only distracting overlay text artifacts naturally and keep all original content unchanged."
263735
+ } : f3);
263736
+ const retryMultipart = buildMultipartBody(retryFields, files);
263737
+ json = await sendRequest(retryMultipart.body, retryMultipart.contentType);
263738
+ const retryItem = pickImageItem(json);
263739
+ savedPath = await saveImageItem(retryItem, filePath, apiKey);
263740
+ }
263741
+ return {
263742
+ content: [
263743
+ {
263744
+ type: "text",
263745
+ text: savedPath ?? `Image edited but could not be saved: no image payload returned; image_model: ${imageModelId}`
263746
+ }
263747
+ ],
263748
+ details: {
263749
+ filePath: savedPath,
263750
+ response: json
263751
+ }
263752
+ };
263753
+ } catch (e2) {
263754
+ const msg = e2 instanceof Error ? e2.message : String(e2);
263755
+ return {
263756
+ content: [
263757
+ { type: "text", text: `Image edit error: ${msg}` }
263758
+ ],
263759
+ details: void 0
263760
+ };
263761
+ }
263762
+ }
263763
+ };
263764
+ }
263456
263765
 
263457
263766
  // ../../packages/runner-pi/dist/web-tools.js
263458
263767
  var braveProvider = {
@@ -264024,7 +264333,7 @@ function createPiRunner(options2 = {}) {
264024
264333
  const customTools = options2.env && Object.keys(options2.env).length > 0 ? buildSecretAwareTools(cwd, options2.env) : [];
264025
264334
  if (imageModelName) {
264026
264335
  const apiKey = await modelRegistry2.authStorage.getApiKey(provider) ?? "";
264027
- customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey));
264336
+ customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey), buildImageEditTool(cwd, imageModelName, model.baseUrl, apiKey));
264028
264337
  }
264029
264338
  const { session } = await createAgentSession({
264030
264339
  cwd,
@@ -264195,7 +264504,7 @@ function createPiRunner(options2 = {}) {
264195
264504
  if (options2.env && Object.keys(options2.env).length > 0) {
264196
264505
  output = redactSecrets(output, options2.env);
264197
264506
  }
264198
- if (event.toolName === "generate_image" && event.result !== null && typeof event.result === "object") {
264507
+ if ((event.toolName === "generate_image" || event.toolName === "edit_image") && event.result !== null && typeof event.result === "object") {
264199
264508
  const details = event.result.details;
264200
264509
  const u = details?.response?.usage;
264201
264510
  if (u) {
@@ -264426,7 +264735,8 @@ function codingRunStream(req, env2) {
264426
264735
  cwd: req.cwd ?? process.env.BUNNY_AGENT_ROOT ?? "/workspace",
264427
264736
  yolo: req.yolo,
264428
264737
  env: env2,
264429
- abortController
264738
+ abortController,
264739
+ autoInject: false
264430
264740
  });
264431
264741
  for await (const chunk of stream2) {
264432
264742
  controller.enqueue(encoder.encode(chunk));
@@ -1 +1 @@
1
- {"version":3,"file":"coding.d.ts","sourceRoot":"","sources":["../../src/routes/coding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AAGvC,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAKD,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD,sCAAsC;AACtC,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED,8DAA8D;AAC9D,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,QAAQ,CA4DV"}
1
+ {"version":3,"file":"coding.d.ts","sourceRoot":"","sources":["../../src/routes/coding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AAGvC,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAKD,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD,sCAAsC;AACtC,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED,8DAA8D;AAC9D,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,QAAQ,CA6DV"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunny-agent/daemon",
3
- "version": "0.9.28",
3
+ "version": "0.9.29-beta.0",
4
4
  "description": "BunnyAgent Daemon - Unified API gateway for sandbox services (file, git, volumes)",
5
5
  "type": "module",
6
6
  "bin": {