@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 +323 -12
- package/dist/index.js +323 -12
- package/dist/nextjs.js +322 -12
- package/dist/routes/coding.d.ts.map +1 -1
- package/package.json +1 -1
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.
|
|
263383
|
-
|
|
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
|
-
|
|
263390
|
-
const
|
|
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
|
|
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 ??
|
|
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.
|
|
263379
|
-
|
|
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
|
-
|
|
263386
|
-
const
|
|
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
|
|
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 ??
|
|
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.
|
|
263375
|
-
|
|
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
|
-
|
|
263382
|
-
const
|
|
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
|
|
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 ??
|
|
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,
|
|
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"}
|