@bunny-agent/runner-cli 0.9.28 → 0.9.29-beta.10
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/build-image.d.ts.map +1 -1
- package/dist/build-image.js +2 -0
- package/dist/build-image.js.map +1 -1
- package/dist/bundle.mjs +727 -253
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-image.d.ts","sourceRoot":"","sources":["../src/build-image.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,IAAI,EAAE,OAAO,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"build-image.d.ts","sourceRoot":"","sources":["../src/build-image.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,IAAI,EAAE,OAAO,CAAC;CACf;AA6ED,wBAAsB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2EvE"}
|
package/dist/build-image.js
CHANGED
|
@@ -13,10 +13,12 @@ function getShippedDockerfile() {
|
|
|
13
13
|
// Look for Dockerfile in several locations:
|
|
14
14
|
// 1. Package root (apps/runner-cli/Dockerfile) — shipped with npm package
|
|
15
15
|
// 2. docker/bunny-agent-claude/Dockerfile — monorepo development
|
|
16
|
+
// 3. CWD-relative docker/bunny-agent-claude/Dockerfile — global install invoked from repo root
|
|
16
17
|
const packageRoot = getPackageRoot();
|
|
17
18
|
const candidates = [
|
|
18
19
|
join(packageRoot, "Dockerfile"),
|
|
19
20
|
resolve(packageRoot, "..", "..", "docker", "bunny-agent-claude", "Dockerfile"),
|
|
21
|
+
resolve(process.cwd(), "docker", "bunny-agent-claude", "Dockerfile"),
|
|
20
22
|
];
|
|
21
23
|
for (const p of candidates) {
|
|
22
24
|
if (existsSync(p))
|
package/dist/build-image.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-image.js","sourceRoot":"","sources":["../src/build-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiBzC,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,cAAc;IACrB,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,OAAO,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,oBAAoB;IAC3B,4CAA4C;IAC5C,0EAA0E;IAC1E,iEAAiE;IACjE,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC;QAC/B,OAAO,CACL,WAAW,EACX,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,oBAAoB,EACpB,YAAY,CACb;
|
|
1
|
+
{"version":3,"file":"build-image.js","sourceRoot":"","sources":["../src/build-image.ts"],"names":[],"mappings":"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"}
|
package/dist/bundle.mjs
CHANGED
|
@@ -33,7 +33,8 @@ function getShippedDockerfile() {
|
|
|
33
33
|
"docker",
|
|
34
34
|
"bunny-agent-claude",
|
|
35
35
|
"Dockerfile"
|
|
36
|
-
)
|
|
36
|
+
),
|
|
37
|
+
resolve(process.cwd(), "docker", "bunny-agent-claude", "Dockerfile")
|
|
37
38
|
];
|
|
38
39
|
for (const p of candidates) {
|
|
39
40
|
if (existsSync(p)) return p;
|
|
@@ -1273,9 +1274,9 @@ function createOpenCodeRunner(options = {}) {
|
|
|
1273
1274
|
|
|
1274
1275
|
// ../../packages/runner-pi/dist/pi-runner.js
|
|
1275
1276
|
import { appendFileSync as appendFileSync2, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "node:fs";
|
|
1276
|
-
import { join as
|
|
1277
|
+
import { join as join8 } from "node:path";
|
|
1277
1278
|
import { getModel } from "@mariozechner/pi-ai";
|
|
1278
|
-
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
1279
|
+
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
1279
1280
|
|
|
1280
1281
|
// ../../packages/runner-pi/dist/bunny-agent-resource-loader.js
|
|
1281
1282
|
import { existsSync as existsSync4 } from "node:fs";
|
|
@@ -1399,25 +1400,146 @@ var generateImageSchema = {
|
|
|
1399
1400
|
},
|
|
1400
1401
|
quality: {
|
|
1401
1402
|
type: "string",
|
|
1402
|
-
enum: ["
|
|
1403
|
-
description: "Image quality
|
|
1403
|
+
enum: ["low", "medium", "high", "auto"],
|
|
1404
|
+
description: "Image quality. Defaults to auto."
|
|
1404
1405
|
}
|
|
1405
1406
|
},
|
|
1406
1407
|
required: ["prompt"],
|
|
1407
1408
|
additionalProperties: false
|
|
1408
1409
|
};
|
|
1409
|
-
async function resolveB64(item) {
|
|
1410
|
+
async function resolveB64(item, apiKey) {
|
|
1410
1411
|
if (item.b64_json)
|
|
1411
1412
|
return item.b64_json;
|
|
1412
|
-
if (item.
|
|
1413
|
-
|
|
1413
|
+
if (item.b64Json)
|
|
1414
|
+
return item.b64Json;
|
|
1415
|
+
if (item.image_base64)
|
|
1416
|
+
return item.image_base64;
|
|
1417
|
+
if (item.imageBase64)
|
|
1418
|
+
return item.imageBase64;
|
|
1419
|
+
if (item.base64)
|
|
1420
|
+
return item.base64;
|
|
1421
|
+
if (typeof item.image === "string")
|
|
1422
|
+
return item.image;
|
|
1423
|
+
if (item.image?.b64_json)
|
|
1424
|
+
return item.image.b64_json;
|
|
1425
|
+
if (item.image?.base64)
|
|
1426
|
+
return item.image.base64;
|
|
1427
|
+
const url = item.url ?? item.image_url ?? item.imageUrl ?? item.image?.url;
|
|
1428
|
+
if (url) {
|
|
1429
|
+
const headers = {};
|
|
1430
|
+
if (apiKey) {
|
|
1431
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
1432
|
+
}
|
|
1433
|
+
const res = await fetch(url, { headers });
|
|
1414
1434
|
if (res.ok)
|
|
1415
1435
|
return Buffer.from(await res.arrayBuffer()).toString("base64");
|
|
1416
1436
|
}
|
|
1417
1437
|
return void 0;
|
|
1418
1438
|
}
|
|
1419
|
-
|
|
1420
|
-
const
|
|
1439
|
+
function pickImageItem(response) {
|
|
1440
|
+
const tryFromObject = (value) => {
|
|
1441
|
+
if (!value || typeof value !== "object")
|
|
1442
|
+
return void 0;
|
|
1443
|
+
const obj = value;
|
|
1444
|
+
return {
|
|
1445
|
+
b64_json: obj.b64_json ?? obj.b64Json,
|
|
1446
|
+
b64Json: obj.b64Json,
|
|
1447
|
+
url: obj.url ?? obj.imageUrl,
|
|
1448
|
+
image_base64: obj.image_base64 ?? obj.imageBase64,
|
|
1449
|
+
imageBase64: obj.imageBase64,
|
|
1450
|
+
image_url: obj.image_url ?? obj.imageUrl,
|
|
1451
|
+
imageUrl: obj.imageUrl,
|
|
1452
|
+
base64: obj.base64,
|
|
1453
|
+
image: obj.image
|
|
1454
|
+
};
|
|
1455
|
+
};
|
|
1456
|
+
const asItem = (value) => {
|
|
1457
|
+
if (value == null)
|
|
1458
|
+
return void 0;
|
|
1459
|
+
if (typeof value === "string") {
|
|
1460
|
+
return { base64: value };
|
|
1461
|
+
}
|
|
1462
|
+
if (typeof value === "object") {
|
|
1463
|
+
const normalized = tryFromObject(value);
|
|
1464
|
+
if (normalized)
|
|
1465
|
+
return normalized;
|
|
1466
|
+
}
|
|
1467
|
+
return void 0;
|
|
1468
|
+
};
|
|
1469
|
+
const fromDataArray = Array.isArray(response.data) ? asItem(response.data[0]) : void 0;
|
|
1470
|
+
if (fromDataArray)
|
|
1471
|
+
return fromDataArray;
|
|
1472
|
+
const fromDataValue = asItem(response.data);
|
|
1473
|
+
if (fromDataValue)
|
|
1474
|
+
return fromDataValue;
|
|
1475
|
+
const responseRecord = response;
|
|
1476
|
+
const imagesValue = responseRecord.images;
|
|
1477
|
+
const outputValue = responseRecord.output;
|
|
1478
|
+
const fromImagesArray = Array.isArray(imagesValue) ? asItem(imagesValue[0]) : void 0;
|
|
1479
|
+
if (fromImagesArray)
|
|
1480
|
+
return fromImagesArray;
|
|
1481
|
+
const fromImagesValue = asItem(imagesValue);
|
|
1482
|
+
if (fromImagesValue)
|
|
1483
|
+
return fromImagesValue;
|
|
1484
|
+
const fromOutputArray = Array.isArray(outputValue) ? asItem(outputValue[0]) : void 0;
|
|
1485
|
+
if (fromOutputArray)
|
|
1486
|
+
return fromOutputArray;
|
|
1487
|
+
const fromOutputValue = asItem(outputValue);
|
|
1488
|
+
if (fromOutputValue)
|
|
1489
|
+
return fromOutputValue;
|
|
1490
|
+
const fromTopLevel = asItem(response);
|
|
1491
|
+
if (fromTopLevel)
|
|
1492
|
+
return fromTopLevel;
|
|
1493
|
+
const queue = [response];
|
|
1494
|
+
while (queue.length > 0) {
|
|
1495
|
+
const current = queue.shift();
|
|
1496
|
+
if (current == null)
|
|
1497
|
+
continue;
|
|
1498
|
+
if (typeof current === "string") {
|
|
1499
|
+
if (/^[A-Za-z0-9+/=]{32,}$/.test(current))
|
|
1500
|
+
return { base64: current };
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
if (typeof current !== "object")
|
|
1504
|
+
continue;
|
|
1505
|
+
const normalized = tryFromObject(current);
|
|
1506
|
+
if (normalized) {
|
|
1507
|
+
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));
|
|
1508
|
+
if (hasUsefulField)
|
|
1509
|
+
return normalized;
|
|
1510
|
+
}
|
|
1511
|
+
if (Array.isArray(current)) {
|
|
1512
|
+
queue.push(...current);
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
for (const value of Object.values(current)) {
|
|
1516
|
+
queue.push(value);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return {};
|
|
1520
|
+
}
|
|
1521
|
+
function detectImageMime(filePath) {
|
|
1522
|
+
const ext = extname(filePath).toLowerCase();
|
|
1523
|
+
if (ext === ".jpg" || ext === ".jpeg")
|
|
1524
|
+
return "image/jpeg";
|
|
1525
|
+
if (ext === ".webp")
|
|
1526
|
+
return "image/webp";
|
|
1527
|
+
if (ext === ".gif")
|
|
1528
|
+
return "image/gif";
|
|
1529
|
+
return "image/png";
|
|
1530
|
+
}
|
|
1531
|
+
function buildPolicySafeEditPrompt(prompt) {
|
|
1532
|
+
const riskyPattern = /\b(watermark|watermarks|logo|logos|copyright|brand mark|remove branding)\b/i;
|
|
1533
|
+
if (!riskyPattern.test(prompt)) {
|
|
1534
|
+
return { prompt, rewritten: false };
|
|
1535
|
+
}
|
|
1536
|
+
return {
|
|
1537
|
+
prompt: "Clean up distracting overlay text or marks naturally while preserving the original scene, style, and layout. Keep the result seamless and high quality.",
|
|
1538
|
+
rewritten: true
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
async function saveImageItem(item, filePath, apiKey) {
|
|
1542
|
+
const b64 = await resolveB64(item, apiKey);
|
|
1421
1543
|
if (!b64)
|
|
1422
1544
|
return void 0;
|
|
1423
1545
|
mkdirSync2(dirname3(filePath), { recursive: true });
|
|
@@ -1437,11 +1559,11 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1437
1559
|
],
|
|
1438
1560
|
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1439
1561
|
parameters: generateImageSchema,
|
|
1440
|
-
async execute(_toolCallId, params,
|
|
1562
|
+
async execute(_toolCallId, params, signal, _onUpdate) {
|
|
1441
1563
|
const p = params;
|
|
1442
1564
|
const prompt = p.prompt;
|
|
1443
1565
|
const size = p.size ?? "1024x1024";
|
|
1444
|
-
const quality = p.quality ?? "
|
|
1566
|
+
const quality = p.quality ?? "auto";
|
|
1445
1567
|
const rawFilename = p.filename;
|
|
1446
1568
|
const filename = rawFilename ? extname(rawFilename) ? rawFilename : `${rawFilename}.png` : `image_${Date.now()}.png`;
|
|
1447
1569
|
const filePath = join6(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
|
|
@@ -1458,24 +1580,28 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1458
1580
|
prompt,
|
|
1459
1581
|
n: 1,
|
|
1460
1582
|
size,
|
|
1461
|
-
quality
|
|
1462
|
-
|
|
1583
|
+
quality,
|
|
1584
|
+
response_format: "b64_json",
|
|
1585
|
+
output_format: "png"
|
|
1586
|
+
}),
|
|
1587
|
+
signal
|
|
1463
1588
|
});
|
|
1464
1589
|
if (!res.ok) {
|
|
1465
1590
|
throw new Error(`Image generation failed (${res.status}): ${await res.text()}`);
|
|
1466
1591
|
}
|
|
1467
1592
|
const json = await res.json();
|
|
1468
|
-
const item = json
|
|
1469
|
-
const savedPath = await saveImageItem(item, filePath);
|
|
1593
|
+
const item = pickImageItem(json);
|
|
1594
|
+
const savedPath = await saveImageItem(item, filePath, apiKey);
|
|
1470
1595
|
return {
|
|
1471
1596
|
content: [
|
|
1472
1597
|
{
|
|
1473
1598
|
type: "text",
|
|
1474
|
-
text: savedPath ??
|
|
1599
|
+
text: savedPath ?? `Image generated but could not be saved: no image payload returned; image_model: ${imageModelId}`
|
|
1475
1600
|
}
|
|
1476
1601
|
],
|
|
1477
1602
|
details: {
|
|
1478
1603
|
filePath: savedPath,
|
|
1604
|
+
...json.usage != null ? { usage: { raw: { [imageModelId]: json.usage } } } : {},
|
|
1479
1605
|
response: json
|
|
1480
1606
|
}
|
|
1481
1607
|
};
|
|
@@ -1491,6 +1617,498 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1491
1617
|
}
|
|
1492
1618
|
};
|
|
1493
1619
|
}
|
|
1620
|
+
var editImageSchema = {
|
|
1621
|
+
type: "object",
|
|
1622
|
+
properties: {
|
|
1623
|
+
image: {
|
|
1624
|
+
type: "string",
|
|
1625
|
+
description: "Path to the source image file to edit (relative to working directory or absolute)."
|
|
1626
|
+
},
|
|
1627
|
+
prompt: {
|
|
1628
|
+
type: "string",
|
|
1629
|
+
description: "Text description of the desired final image. Describe the full result, not just the change."
|
|
1630
|
+
},
|
|
1631
|
+
mask: {
|
|
1632
|
+
type: "string",
|
|
1633
|
+
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."
|
|
1634
|
+
},
|
|
1635
|
+
filename: {
|
|
1636
|
+
type: "string",
|
|
1637
|
+
description: "Output filename with extension, e.g. 'edited_cat.png'. Defaults to a timestamp-based name."
|
|
1638
|
+
},
|
|
1639
|
+
size: {
|
|
1640
|
+
type: "string",
|
|
1641
|
+
enum: ["1024x1024", "1024x1536", "1536x1024", "auto"],
|
|
1642
|
+
description: "Output image dimensions. Optional; omit or set auto to let model decide."
|
|
1643
|
+
},
|
|
1644
|
+
quality: {
|
|
1645
|
+
type: "string",
|
|
1646
|
+
enum: ["low", "medium", "high", "auto"],
|
|
1647
|
+
description: "Image quality. Optional; omit or set auto to let model decide."
|
|
1648
|
+
}
|
|
1649
|
+
},
|
|
1650
|
+
required: ["image", "prompt"],
|
|
1651
|
+
additionalProperties: false
|
|
1652
|
+
};
|
|
1653
|
+
function buildMultipartBody(fields, files) {
|
|
1654
|
+
const boundary = `----SandagentBoundary${Date.now()}${Math.random().toString(36).slice(2)}`;
|
|
1655
|
+
const parts = [];
|
|
1656
|
+
for (const { name, value } of fields) {
|
|
1657
|
+
parts.push(Buffer.from(`--${boundary}\r
|
|
1658
|
+
Content-Disposition: form-data; name="${name}"\r
|
|
1659
|
+
\r
|
|
1660
|
+
${value}\r
|
|
1661
|
+
`));
|
|
1662
|
+
}
|
|
1663
|
+
for (const { name, filename, buffer, mime } of files) {
|
|
1664
|
+
parts.push(Buffer.from(`--${boundary}\r
|
|
1665
|
+
Content-Disposition: form-data; name="${name}"; filename="${filename}"\r
|
|
1666
|
+
Content-Type: ${mime}\r
|
|
1667
|
+
\r
|
|
1668
|
+
`));
|
|
1669
|
+
parts.push(buffer);
|
|
1670
|
+
parts.push(Buffer.from("\r\n"));
|
|
1671
|
+
}
|
|
1672
|
+
parts.push(Buffer.from(`--${boundary}--\r
|
|
1673
|
+
`));
|
|
1674
|
+
return {
|
|
1675
|
+
body: Buffer.concat(parts),
|
|
1676
|
+
contentType: `multipart/form-data; boundary=${boundary}`
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
1680
|
+
return {
|
|
1681
|
+
name: "edit_image",
|
|
1682
|
+
label: "edit image",
|
|
1683
|
+
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.",
|
|
1684
|
+
promptSnippet: "edit_image(image, prompt, mask?, filename?, size?, quality?) - edit an existing image",
|
|
1685
|
+
promptGuidelines: [
|
|
1686
|
+
"Use edit_image when the user wants to modify, retouch, or transform an existing image.",
|
|
1687
|
+
"The prompt should describe the full desired final image, not just the change.",
|
|
1688
|
+
"Provide the source image path. Use a mask image (PNG with transparent areas) to control where edits happen.",
|
|
1689
|
+
"Without a mask, the model decides what to change based on the prompt."
|
|
1690
|
+
],
|
|
1691
|
+
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1692
|
+
parameters: editImageSchema,
|
|
1693
|
+
async execute(_toolCallId, params, signal, _onUpdate) {
|
|
1694
|
+
const { readFileSync: readFileSync4, existsSync: existsSync8 } = await import("node:fs");
|
|
1695
|
+
const { resolve: resolve4, basename: basename2 } = await import("node:path");
|
|
1696
|
+
const p = params;
|
|
1697
|
+
const imagePath = p.image;
|
|
1698
|
+
const prompt = p.prompt;
|
|
1699
|
+
const maskPath = p.mask;
|
|
1700
|
+
const size = p.size;
|
|
1701
|
+
const quality = p.quality;
|
|
1702
|
+
const rawFilename = p.filename;
|
|
1703
|
+
const safePrompt = buildPolicySafeEditPrompt(prompt);
|
|
1704
|
+
const resolvedImage = resolve4(cwd, imagePath);
|
|
1705
|
+
if (!existsSync8(resolvedImage)) {
|
|
1706
|
+
return {
|
|
1707
|
+
content: [
|
|
1708
|
+
{
|
|
1709
|
+
type: "text",
|
|
1710
|
+
text: `Image edit error: source image not found at ${resolvedImage}`
|
|
1711
|
+
}
|
|
1712
|
+
],
|
|
1713
|
+
details: void 0
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
const filename = rawFilename ? extname(rawFilename) ? rawFilename : `${rawFilename}.png` : `edited_${Date.now()}.png`;
|
|
1717
|
+
const filePath = join6(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
|
|
1718
|
+
try {
|
|
1719
|
+
const imageBuffer = readFileSync4(resolvedImage);
|
|
1720
|
+
const fields = [
|
|
1721
|
+
{ name: "model", value: imageModelId },
|
|
1722
|
+
{ name: "prompt", value: safePrompt.prompt },
|
|
1723
|
+
{ name: "n", value: "1" },
|
|
1724
|
+
{ name: "response_format", value: "b64_json" },
|
|
1725
|
+
{ name: "output_format", value: "png" }
|
|
1726
|
+
];
|
|
1727
|
+
if (size && size !== "auto") {
|
|
1728
|
+
fields.push({ name: "size", value: size });
|
|
1729
|
+
}
|
|
1730
|
+
if (quality && quality !== "auto") {
|
|
1731
|
+
fields.push({ name: "quality", value: quality });
|
|
1732
|
+
}
|
|
1733
|
+
const files = [
|
|
1734
|
+
{
|
|
1735
|
+
name: "image",
|
|
1736
|
+
filename: basename2(resolvedImage),
|
|
1737
|
+
buffer: imageBuffer,
|
|
1738
|
+
mime: detectImageMime(resolvedImage)
|
|
1739
|
+
}
|
|
1740
|
+
];
|
|
1741
|
+
if (maskPath) {
|
|
1742
|
+
const resolvedMask = resolve4(cwd, maskPath);
|
|
1743
|
+
if (existsSync8(resolvedMask)) {
|
|
1744
|
+
files.push({
|
|
1745
|
+
name: "mask",
|
|
1746
|
+
filename: basename2(resolvedMask),
|
|
1747
|
+
buffer: readFileSync4(resolvedMask),
|
|
1748
|
+
mime: detectImageMime(resolvedMask)
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
const { body: multipartBody, contentType } = buildMultipartBody(fields, files);
|
|
1753
|
+
const url = `${baseUrl.replace(/\/$/, "")}/v1/images/edits`;
|
|
1754
|
+
const sendRequest = async (body, type) => {
|
|
1755
|
+
const res = await fetch(url, {
|
|
1756
|
+
method: "POST",
|
|
1757
|
+
headers: {
|
|
1758
|
+
"Content-Type": type,
|
|
1759
|
+
Authorization: `Bearer ${apiKey}`
|
|
1760
|
+
},
|
|
1761
|
+
body,
|
|
1762
|
+
signal
|
|
1763
|
+
});
|
|
1764
|
+
if (!res.ok) {
|
|
1765
|
+
throw new Error(`Image edit failed (${res.status}): ${await res.text()}`);
|
|
1766
|
+
}
|
|
1767
|
+
return await res.json();
|
|
1768
|
+
};
|
|
1769
|
+
let json = await sendRequest(multipartBody, contentType);
|
|
1770
|
+
const item = pickImageItem(json);
|
|
1771
|
+
let savedPath = await saveImageItem(item, filePath, apiKey);
|
|
1772
|
+
const firstResponseHasEmptyDataArray = Array.isArray(json.data) && json.data.length === 0;
|
|
1773
|
+
if (!savedPath && safePrompt.rewritten && firstResponseHasEmptyDataArray) {
|
|
1774
|
+
const retryFields = fields.map((f) => f.name === "prompt" ? {
|
|
1775
|
+
name: "prompt",
|
|
1776
|
+
value: "Remove only distracting overlay text artifacts naturally and keep all original content unchanged."
|
|
1777
|
+
} : f);
|
|
1778
|
+
const retryMultipart = buildMultipartBody(retryFields, files);
|
|
1779
|
+
json = await sendRequest(retryMultipart.body, retryMultipart.contentType);
|
|
1780
|
+
const retryItem = pickImageItem(json);
|
|
1781
|
+
savedPath = await saveImageItem(retryItem, filePath, apiKey);
|
|
1782
|
+
}
|
|
1783
|
+
return {
|
|
1784
|
+
content: [
|
|
1785
|
+
{
|
|
1786
|
+
type: "text",
|
|
1787
|
+
text: savedPath ?? `Image edited but could not be saved: no image payload returned; image_model: ${imageModelId}`
|
|
1788
|
+
}
|
|
1789
|
+
],
|
|
1790
|
+
details: {
|
|
1791
|
+
filePath: savedPath,
|
|
1792
|
+
...json.usage != null ? { usage: { raw: { [imageModelId]: json.usage } } } : {},
|
|
1793
|
+
response: json
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
} catch (e) {
|
|
1797
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1798
|
+
return {
|
|
1799
|
+
content: [
|
|
1800
|
+
{ type: "text", text: `Image edit error: ${msg}` }
|
|
1801
|
+
],
|
|
1802
|
+
details: void 0
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// ../../packages/runner-pi/dist/session-utils.js
|
|
1810
|
+
import { closeSync, fstatSync, openSync, readdirSync as readdirSync2, readSync, statSync as statSync2 } from "node:fs";
|
|
1811
|
+
import { join as join7 } from "node:path";
|
|
1812
|
+
import { SessionManager } from "@mariozechner/pi-coding-agent";
|
|
1813
|
+
var MAX_SESSION_FILE_BYTES = Number(process.env.SANDAGENT_MAX_SESSION_BYTES) || 10 * 1024 * 1024;
|
|
1814
|
+
function resolveSessionPathById(cwd, sessionId) {
|
|
1815
|
+
const tempMgr = SessionManager.create(cwd);
|
|
1816
|
+
const sessionsDir = tempMgr.getSessionDir();
|
|
1817
|
+
try {
|
|
1818
|
+
const suffix = `_${sessionId}.jsonl`;
|
|
1819
|
+
const match = readdirSync2(sessionsDir).find((f) => f.endsWith(suffix));
|
|
1820
|
+
return match ? join7(sessionsDir, match) : void 0;
|
|
1821
|
+
} catch {
|
|
1822
|
+
return void 0;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
function isSessionFileTooLarge(sessionPath2) {
|
|
1826
|
+
try {
|
|
1827
|
+
return statSync2(sessionPath2).size > MAX_SESSION_FILE_BYTES;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function readTailEntries(sessionPath2, tailBytes = 1024 * 1024) {
|
|
1833
|
+
let fd;
|
|
1834
|
+
try {
|
|
1835
|
+
fd = openSync(sessionPath2, "r");
|
|
1836
|
+
} catch {
|
|
1837
|
+
return [];
|
|
1838
|
+
}
|
|
1839
|
+
try {
|
|
1840
|
+
const fileSize = fstatSync(fd).size;
|
|
1841
|
+
const readStart = Math.max(0, fileSize - tailBytes);
|
|
1842
|
+
const readLen = fileSize - readStart;
|
|
1843
|
+
const buf = Buffer.alloc(readLen);
|
|
1844
|
+
readSync(fd, buf, 0, readLen, readStart);
|
|
1845
|
+
const tail = buf.toString("utf8");
|
|
1846
|
+
const entries = [];
|
|
1847
|
+
for (const line of tail.split("\n")) {
|
|
1848
|
+
const trimmed = line.trim();
|
|
1849
|
+
if (!trimmed)
|
|
1850
|
+
continue;
|
|
1851
|
+
try {
|
|
1852
|
+
entries.push(JSON.parse(trimmed));
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return entries;
|
|
1857
|
+
} finally {
|
|
1858
|
+
closeSync(fd);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
function extractSessionContext(sessionPath2) {
|
|
1862
|
+
const entries = readTailEntries(sessionPath2);
|
|
1863
|
+
if (entries.length === 0)
|
|
1864
|
+
return void 0;
|
|
1865
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1866
|
+
const e = entries[i];
|
|
1867
|
+
if (e.type === "compaction" && typeof e.summary === "string") {
|
|
1868
|
+
return e.summary;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
const recentMessages = [];
|
|
1872
|
+
const MAX_MESSAGES = 6;
|
|
1873
|
+
for (let i = entries.length - 1; i >= 0 && recentMessages.length < MAX_MESSAGES; i--) {
|
|
1874
|
+
const e = entries[i];
|
|
1875
|
+
if (e.type !== "message")
|
|
1876
|
+
continue;
|
|
1877
|
+
const msg = e.message;
|
|
1878
|
+
if (!msg)
|
|
1879
|
+
continue;
|
|
1880
|
+
if (msg.role !== "user" && msg.role !== "assistant")
|
|
1881
|
+
continue;
|
|
1882
|
+
let text = "";
|
|
1883
|
+
if (typeof msg.content === "string") {
|
|
1884
|
+
text = msg.content;
|
|
1885
|
+
} else if (Array.isArray(msg.content)) {
|
|
1886
|
+
text = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
1887
|
+
}
|
|
1888
|
+
if (text) {
|
|
1889
|
+
recentMessages.unshift(`[${msg.role}]: ${text}`);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
if (recentMessages.length === 0)
|
|
1893
|
+
return void 0;
|
|
1894
|
+
return "## Previous Session Context (auto-extracted)\n\nThe following is the tail of the previous conversation:\n\n" + recentMessages.join("\n\n");
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// ../../packages/runner-pi/dist/usage-metadata.js
|
|
1898
|
+
function usageToMessageMetadata(usage) {
|
|
1899
|
+
return {
|
|
1900
|
+
input_tokens: usage.input,
|
|
1901
|
+
output_tokens: usage.output,
|
|
1902
|
+
cache_read_input_tokens: usage.cacheRead,
|
|
1903
|
+
cache_creation_input_tokens: usage.cacheWrite
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
function accumulateToolUsage(tally, raw) {
|
|
1907
|
+
for (const [key, row] of Object.entries(raw)) {
|
|
1908
|
+
const existing = tally[key];
|
|
1909
|
+
if (existing) {
|
|
1910
|
+
for (const [field, val] of Object.entries(row)) {
|
|
1911
|
+
if (typeof val === "number")
|
|
1912
|
+
existing[field] = (existing[field] ?? 0) + val;
|
|
1913
|
+
}
|
|
1914
|
+
} else {
|
|
1915
|
+
const nums = {};
|
|
1916
|
+
for (const [field, val] of Object.entries(row)) {
|
|
1917
|
+
if (typeof val === "number")
|
|
1918
|
+
nums[field] = val;
|
|
1919
|
+
}
|
|
1920
|
+
tally[key] = nums;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
function getUsageFromAgentEndMessages(messages) {
|
|
1925
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1926
|
+
const m = messages[i];
|
|
1927
|
+
if (m.role === "assistant" && m.usage != null)
|
|
1928
|
+
return m.usage;
|
|
1929
|
+
}
|
|
1930
|
+
return void 0;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// ../../packages/runner-pi/dist/stream-converter.js
|
|
1934
|
+
function emitStreamError(errorText) {
|
|
1935
|
+
const errorLine = "data: " + JSON.stringify({ type: "error", errorText }) + "\n\n";
|
|
1936
|
+
const finishLine = "data: " + JSON.stringify({ type: "finish", finishReason: "error" }) + "\n\n";
|
|
1937
|
+
return [errorLine, finishLine, "data: [DONE]\n\n"];
|
|
1938
|
+
}
|
|
1939
|
+
function extractToolResultText(result) {
|
|
1940
|
+
if (result !== null && typeof result === "object") {
|
|
1941
|
+
const r = result;
|
|
1942
|
+
if (Array.isArray(r.content) && r.content.length > 0) {
|
|
1943
|
+
const text = r.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
|
|
1944
|
+
if (text.length > 0)
|
|
1945
|
+
return text;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
if (typeof result === "string")
|
|
1949
|
+
return result;
|
|
1950
|
+
try {
|
|
1951
|
+
return JSON.stringify(result);
|
|
1952
|
+
} catch {
|
|
1953
|
+
return String(result);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function sseData(obj) {
|
|
1957
|
+
return "data: " + JSON.stringify(obj) + "\n\n";
|
|
1958
|
+
}
|
|
1959
|
+
var PiAISDKStreamConverter = class {
|
|
1960
|
+
constructor(options) {
|
|
1961
|
+
this.options = options;
|
|
1962
|
+
this.messageId = "msg_" + Date.now() + "_" + Math.random().toString(36).slice(2);
|
|
1963
|
+
this.toolUsageTally = {};
|
|
1964
|
+
this.activeTextPartId = null;
|
|
1965
|
+
this.hasStarted = false;
|
|
1966
|
+
this.hasFinished = false;
|
|
1967
|
+
}
|
|
1968
|
+
get finished() {
|
|
1969
|
+
return this.hasFinished;
|
|
1970
|
+
}
|
|
1971
|
+
forceError(errorText) {
|
|
1972
|
+
if (this.hasFinished)
|
|
1973
|
+
return [];
|
|
1974
|
+
return [...this.ensureStart(), ...this.finishError(errorText)];
|
|
1975
|
+
}
|
|
1976
|
+
handleEvent(event, aborted) {
|
|
1977
|
+
if (this.hasFinished)
|
|
1978
|
+
return [];
|
|
1979
|
+
const chunks = [...this.ensureStart()];
|
|
1980
|
+
if (event.type === "message_start") {
|
|
1981
|
+
const msg = event.message;
|
|
1982
|
+
if (msg?.role === "assistant")
|
|
1983
|
+
chunks.push(...this.endTextStreamIfOpen());
|
|
1984
|
+
return chunks;
|
|
1985
|
+
}
|
|
1986
|
+
if (event.type === "message_end")
|
|
1987
|
+
return chunks;
|
|
1988
|
+
if (event.type === "message_update") {
|
|
1989
|
+
const sub = event.assistantMessageEvent;
|
|
1990
|
+
if (sub.type === "text_start")
|
|
1991
|
+
chunks.push(...this.endTextStreamIfOpen(), ...this.openTextStream());
|
|
1992
|
+
else if (sub.type === "text_delta")
|
|
1993
|
+
chunks.push(...this.emitTextDelta(sub.delta));
|
|
1994
|
+
else if (sub.type === "toolcall_start")
|
|
1995
|
+
chunks.push(...this.endTextStreamIfOpen());
|
|
1996
|
+
return chunks;
|
|
1997
|
+
}
|
|
1998
|
+
if (event.type === "tool_execution_start") {
|
|
1999
|
+
chunks.push(...this.endTextStreamIfOpen());
|
|
2000
|
+
chunks.push(sseData({
|
|
2001
|
+
type: "tool-input-start",
|
|
2002
|
+
toolCallId: event.toolCallId,
|
|
2003
|
+
toolName: event.toolName,
|
|
2004
|
+
dynamic: true,
|
|
2005
|
+
providerExecuted: true
|
|
2006
|
+
}), sseData({
|
|
2007
|
+
type: "tool-input-available",
|
|
2008
|
+
toolCallId: event.toolCallId,
|
|
2009
|
+
toolName: event.toolName,
|
|
2010
|
+
input: event.args,
|
|
2011
|
+
dynamic: true,
|
|
2012
|
+
providerExecuted: true
|
|
2013
|
+
}));
|
|
2014
|
+
return chunks;
|
|
2015
|
+
}
|
|
2016
|
+
if (event.type === "tool_execution_end") {
|
|
2017
|
+
const output = this.options.redactText(this.options.normalizeToolOutput(event.result));
|
|
2018
|
+
const raw = event.result?.details?.usage?.raw;
|
|
2019
|
+
if (raw != null)
|
|
2020
|
+
accumulateToolUsage(this.toolUsageTally, raw);
|
|
2021
|
+
chunks.push(sseData({
|
|
2022
|
+
type: "tool-output-available",
|
|
2023
|
+
toolCallId: event.toolCallId,
|
|
2024
|
+
output,
|
|
2025
|
+
isError: event.isError,
|
|
2026
|
+
dynamic: true,
|
|
2027
|
+
providerExecuted: true
|
|
2028
|
+
}));
|
|
2029
|
+
return chunks;
|
|
2030
|
+
}
|
|
2031
|
+
if (event.type === "agent_end") {
|
|
2032
|
+
if (aborted) {
|
|
2033
|
+
chunks.push(...this.finishError("Run aborted by signal."));
|
|
2034
|
+
} else {
|
|
2035
|
+
const errorMsg = this.options.getErrorFromAgentEndMessages(event.messages);
|
|
2036
|
+
if (errorMsg)
|
|
2037
|
+
chunks.push(...this.finishError(errorMsg));
|
|
2038
|
+
else
|
|
2039
|
+
chunks.push(...this.finishSuccess(this.options.getUsageFromAgentEndMessages(event.messages)));
|
|
2040
|
+
}
|
|
2041
|
+
return chunks;
|
|
2042
|
+
}
|
|
2043
|
+
return chunks;
|
|
2044
|
+
}
|
|
2045
|
+
ensureStart() {
|
|
2046
|
+
if (this.hasStarted)
|
|
2047
|
+
return [];
|
|
2048
|
+
this.hasStarted = true;
|
|
2049
|
+
return [
|
|
2050
|
+
sseData({ type: "start", messageId: this.messageId }),
|
|
2051
|
+
sseData({
|
|
2052
|
+
type: "message-metadata",
|
|
2053
|
+
messageMetadata: { sessionId: this.options.sessionId }
|
|
2054
|
+
})
|
|
2055
|
+
];
|
|
2056
|
+
}
|
|
2057
|
+
newTextPartId() {
|
|
2058
|
+
return "text_" + Date.now() + "_" + Math.random().toString(36).slice(2) + "_" + Math.random().toString(36).slice(2);
|
|
2059
|
+
}
|
|
2060
|
+
openTextStream() {
|
|
2061
|
+
this.activeTextPartId = this.newTextPartId();
|
|
2062
|
+
return [sseData({ type: "text-start", id: this.activeTextPartId })];
|
|
2063
|
+
}
|
|
2064
|
+
emitTextDelta(rawDelta) {
|
|
2065
|
+
const delta = rawDelta ? this.options.redactText(rawDelta) : void 0;
|
|
2066
|
+
if (!delta)
|
|
2067
|
+
return [];
|
|
2068
|
+
const startChunk = this.activeTextPartId == null ? this.openTextStream() : [];
|
|
2069
|
+
return [
|
|
2070
|
+
...startChunk,
|
|
2071
|
+
sseData({ type: "text-delta", id: this.activeTextPartId, delta })
|
|
2072
|
+
];
|
|
2073
|
+
}
|
|
2074
|
+
endTextStreamIfOpen() {
|
|
2075
|
+
if (this.activeTextPartId == null)
|
|
2076
|
+
return [];
|
|
2077
|
+
const id = this.activeTextPartId;
|
|
2078
|
+
this.activeTextPartId = null;
|
|
2079
|
+
return [sseData({ type: "text-end", id })];
|
|
2080
|
+
}
|
|
2081
|
+
finishSuccess(usage) {
|
|
2082
|
+
const chunks = [...this.endTextStreamIfOpen()];
|
|
2083
|
+
const raw = {};
|
|
2084
|
+
let chatUsage;
|
|
2085
|
+
if (usage) {
|
|
2086
|
+
const { id } = this.options.model;
|
|
2087
|
+
chatUsage = {
|
|
2088
|
+
type: "chat",
|
|
2089
|
+
...usageToMessageMetadata(usage)
|
|
2090
|
+
};
|
|
2091
|
+
raw[id] = chatUsage;
|
|
2092
|
+
}
|
|
2093
|
+
for (const [key, tally] of Object.entries(this.toolUsageTally)) {
|
|
2094
|
+
raw[key] = { ...tally };
|
|
2095
|
+
}
|
|
2096
|
+
const finishPayload = {
|
|
2097
|
+
type: "finish",
|
|
2098
|
+
finishReason: "stop"
|
|
2099
|
+
};
|
|
2100
|
+
if (usage) {
|
|
2101
|
+
finishPayload.messageMetadata = { usage: { ...chatUsage, raw } };
|
|
2102
|
+
}
|
|
2103
|
+
chunks.push(sseData(finishPayload), "data: [DONE]\n\n");
|
|
2104
|
+
this.hasFinished = true;
|
|
2105
|
+
return chunks;
|
|
2106
|
+
}
|
|
2107
|
+
finishError(errorText) {
|
|
2108
|
+
this.hasFinished = true;
|
|
2109
|
+
return emitStreamError(errorText);
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
1494
2112
|
|
|
1495
2113
|
// ../../packages/runner-pi/dist/tool-overrides.js
|
|
1496
2114
|
import { createBashTool, createReadTool } from "@mariozechner/pi-coding-agent";
|
|
@@ -1500,7 +2118,7 @@ var braveProvider = {
|
|
|
1500
2118
|
id: "brave",
|
|
1501
2119
|
label: "Brave Search",
|
|
1502
2120
|
envKeys: ["BRAVE_API_KEY"],
|
|
1503
|
-
async search({ apiKey, query, count, country, freshness }) {
|
|
2121
|
+
async search({ apiKey, query, count, country, freshness, signal }) {
|
|
1504
2122
|
const params = new URLSearchParams({
|
|
1505
2123
|
q: query,
|
|
1506
2124
|
count: String(Math.min(count, 20))
|
|
@@ -1514,7 +2132,8 @@ var braveProvider = {
|
|
|
1514
2132
|
Accept: "application/json",
|
|
1515
2133
|
"Accept-Encoding": "gzip",
|
|
1516
2134
|
"X-Subscription-Token": apiKey
|
|
1517
|
-
}
|
|
2135
|
+
},
|
|
2136
|
+
signal
|
|
1518
2137
|
});
|
|
1519
2138
|
if (!res.ok) {
|
|
1520
2139
|
const body = await res.text().catch(() => "");
|
|
@@ -1535,14 +2154,14 @@ ${body}`);
|
|
|
1535
2154
|
});
|
|
1536
2155
|
}
|
|
1537
2156
|
}
|
|
1538
|
-
return results;
|
|
2157
|
+
return { results };
|
|
1539
2158
|
}
|
|
1540
2159
|
};
|
|
1541
2160
|
var tavilyProvider = {
|
|
1542
2161
|
id: "tavily",
|
|
1543
2162
|
label: "Tavily",
|
|
1544
2163
|
envKeys: ["TAVILY_API_KEY"],
|
|
1545
|
-
async search({ apiKey, query, count }) {
|
|
2164
|
+
async search({ apiKey, query, count, signal }) {
|
|
1546
2165
|
const res = await fetch("https://api.tavily.com/search", {
|
|
1547
2166
|
method: "POST",
|
|
1548
2167
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1551,7 +2170,8 @@ var tavilyProvider = {
|
|
|
1551
2170
|
query,
|
|
1552
2171
|
max_results: Math.min(count, 10),
|
|
1553
2172
|
include_answer: false
|
|
1554
|
-
})
|
|
2173
|
+
}),
|
|
2174
|
+
signal
|
|
1555
2175
|
});
|
|
1556
2176
|
if (!res.ok) {
|
|
1557
2177
|
const body = await res.text().catch(() => "");
|
|
@@ -1569,7 +2189,7 @@ ${body}`);
|
|
|
1569
2189
|
});
|
|
1570
2190
|
}
|
|
1571
2191
|
}
|
|
1572
|
-
return results;
|
|
2192
|
+
return { results };
|
|
1573
2193
|
}
|
|
1574
2194
|
};
|
|
1575
2195
|
var AUTO_DETECT_ORDER = [braveProvider, tavilyProvider];
|
|
@@ -1604,9 +2224,12 @@ var BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
|
|
|
1604
2224
|
function htmlToText(html) {
|
|
1605
2225
|
return html.replace(/<(script|style|noscript)[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|h[1-6]|li|tr)>/gi, "\n").replace(/<(p|div|h[1-6]|li|tr)[^>]*>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
1606
2226
|
}
|
|
1607
|
-
async function fetchPageContent(url) {
|
|
2227
|
+
async function fetchPageContent(url, externalSignal) {
|
|
1608
2228
|
const controller = new AbortController();
|
|
1609
2229
|
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
2230
|
+
externalSignal?.addEventListener("abort", () => controller.abort(), {
|
|
2231
|
+
once: true
|
|
2232
|
+
});
|
|
1610
2233
|
try {
|
|
1611
2234
|
const res = await fetch(url, {
|
|
1612
2235
|
headers: {
|
|
@@ -1703,7 +2326,7 @@ function buildWebSearchTool(env) {
|
|
|
1703
2326
|
],
|
|
1704
2327
|
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1705
2328
|
parameters: webSearchSchema,
|
|
1706
|
-
async execute(_toolCallId, params,
|
|
2329
|
+
async execute(_toolCallId, params, signal, _onUpdate) {
|
|
1707
2330
|
const p = params;
|
|
1708
2331
|
const query = p.query;
|
|
1709
2332
|
const count = p.count ?? 5;
|
|
@@ -1713,18 +2336,29 @@ function buildWebSearchTool(env) {
|
|
|
1713
2336
|
let lastError;
|
|
1714
2337
|
for (const { provider, apiKey } of providers) {
|
|
1715
2338
|
try {
|
|
1716
|
-
const results = await provider.search({
|
|
2339
|
+
const { results } = await provider.search({
|
|
1717
2340
|
apiKey,
|
|
1718
2341
|
query,
|
|
1719
2342
|
count,
|
|
1720
2343
|
country,
|
|
1721
|
-
freshness
|
|
2344
|
+
freshness,
|
|
2345
|
+
signal
|
|
1722
2346
|
});
|
|
2347
|
+
let fetchedPages = 0;
|
|
1723
2348
|
if (shouldFetchContent) {
|
|
1724
2349
|
for (const r of results) {
|
|
1725
|
-
r.content = await fetchPageContent(r.link);
|
|
2350
|
+
r.content = await fetchPageContent(r.link, signal);
|
|
2351
|
+
fetchedPages += 1;
|
|
1726
2352
|
}
|
|
1727
2353
|
}
|
|
2354
|
+
const usage = {
|
|
2355
|
+
raw: {
|
|
2356
|
+
[provider.id]: {
|
|
2357
|
+
requests: 1,
|
|
2358
|
+
fetchedPages
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
1728
2362
|
return {
|
|
1729
2363
|
content: [
|
|
1730
2364
|
{
|
|
@@ -1732,7 +2366,9 @@ function buildWebSearchTool(env) {
|
|
|
1732
2366
|
text: formatSearchResults(results, provider.label)
|
|
1733
2367
|
}
|
|
1734
2368
|
],
|
|
1735
|
-
details:
|
|
2369
|
+
details: {
|
|
2370
|
+
usage
|
|
2371
|
+
}
|
|
1736
2372
|
};
|
|
1737
2373
|
} catch (e) {
|
|
1738
2374
|
lastError = e;
|
|
@@ -1768,11 +2404,11 @@ function buildWebFetchTool() {
|
|
|
1768
2404
|
],
|
|
1769
2405
|
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1770
2406
|
parameters: webFetchSchema,
|
|
1771
|
-
async execute(_toolCallId, params,
|
|
2407
|
+
async execute(_toolCallId, params, signal, _onUpdate) {
|
|
1772
2408
|
const p = params;
|
|
1773
2409
|
const url = p.url;
|
|
1774
2410
|
try {
|
|
1775
|
-
const content = await fetchPageContent(url);
|
|
2411
|
+
const content = await fetchPageContent(url, signal);
|
|
1776
2412
|
return {
|
|
1777
2413
|
content: [{ type: "text", text: content }],
|
|
1778
2414
|
details: void 0
|
|
@@ -1915,52 +2551,6 @@ function applyModelOverrides(model, provider, optionsEnv) {
|
|
|
1915
2551
|
model.baseUrl = anthropicBaseUrl;
|
|
1916
2552
|
}
|
|
1917
2553
|
}
|
|
1918
|
-
function emitStreamError(errorText) {
|
|
1919
|
-
return [
|
|
1920
|
-
`data: ${JSON.stringify({ type: "error", errorText })}
|
|
1921
|
-
|
|
1922
|
-
`,
|
|
1923
|
-
`data: ${JSON.stringify({ type: "finish", finishReason: "error" })}
|
|
1924
|
-
|
|
1925
|
-
`,
|
|
1926
|
-
"data: [DONE]\n\n"
|
|
1927
|
-
];
|
|
1928
|
-
}
|
|
1929
|
-
function extractToolResultText(result) {
|
|
1930
|
-
if (result !== null && typeof result === "object") {
|
|
1931
|
-
const r = result;
|
|
1932
|
-
if (Array.isArray(r.content) && r.content.length > 0) {
|
|
1933
|
-
const text = r.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
|
|
1934
|
-
if (text.length > 0) {
|
|
1935
|
-
return text;
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
if (typeof result === "string")
|
|
1940
|
-
return result;
|
|
1941
|
-
try {
|
|
1942
|
-
return JSON.stringify(result);
|
|
1943
|
-
} catch {
|
|
1944
|
-
return String(result);
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
function usageToMessageMetadata(usage) {
|
|
1948
|
-
return {
|
|
1949
|
-
input_tokens: usage.input,
|
|
1950
|
-
output_tokens: usage.output,
|
|
1951
|
-
cache_read_input_tokens: usage.cacheRead,
|
|
1952
|
-
cache_creation_input_tokens: usage.cacheWrite
|
|
1953
|
-
};
|
|
1954
|
-
}
|
|
1955
|
-
function getUsageFromAgentEndMessages(messages) {
|
|
1956
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1957
|
-
const m = messages[i];
|
|
1958
|
-
if (m.role === "assistant" && m.usage != null) {
|
|
1959
|
-
return m.usage;
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
return void 0;
|
|
1963
|
-
}
|
|
1964
2554
|
function getErrorFromAgentEndMessages(messages) {
|
|
1965
2555
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1966
2556
|
const m = messages[i];
|
|
@@ -1976,7 +2566,7 @@ function traceRawMessage(debugCwd, data, reset = false, optionsEnv) {
|
|
|
1976
2566
|
if (!enabled)
|
|
1977
2567
|
return;
|
|
1978
2568
|
try {
|
|
1979
|
-
const file =
|
|
2569
|
+
const file = join8(debugCwd, "pi-message-stream-debug.json");
|
|
1980
2570
|
if (reset && existsSync5(file))
|
|
1981
2571
|
unlinkSync3(file);
|
|
1982
2572
|
const type = data !== null && typeof data === "object" ? data.type : void 0;
|
|
@@ -2043,13 +2633,26 @@ function createPiRunner(options = {}) {
|
|
|
2043
2633
|
const sessionManager = await (async () => {
|
|
2044
2634
|
if (resume !== void 0 && resume !== "") {
|
|
2045
2635
|
if (resume.includes("/")) {
|
|
2046
|
-
return
|
|
2636
|
+
return SessionManager2.open(resume);
|
|
2637
|
+
}
|
|
2638
|
+
const sessionPath2 = resolveSessionPathById(cwd, resume);
|
|
2639
|
+
console.error(`${LOG_PREFIX2} resume: id=${resume} path=${sessionPath2 ?? "(not found)"}`);
|
|
2640
|
+
if (sessionPath2) {
|
|
2641
|
+
if (isSessionFileTooLarge(sessionPath2)) {
|
|
2642
|
+
const context = extractSessionContext(sessionPath2);
|
|
2643
|
+
console.error(`${LOG_PREFIX2} session file too large, starting fresh${context ? " (with context)" : ""}`);
|
|
2644
|
+
const newMgr = SessionManager2.create(cwd);
|
|
2645
|
+
if (context) {
|
|
2646
|
+
const firstId = newMgr.getEntries()[0]?.id ?? "";
|
|
2647
|
+
newMgr.appendCompaction(context, firstId, 0);
|
|
2648
|
+
}
|
|
2649
|
+
return newMgr;
|
|
2650
|
+
}
|
|
2651
|
+
return SessionManager2.open(sessionPath2);
|
|
2047
2652
|
}
|
|
2048
|
-
|
|
2049
|
-
const found = sessions.find((s) => s.id === resume);
|
|
2050
|
-
return found ? SessionManager.open(found.path) : SessionManager.create(cwd);
|
|
2653
|
+
return SessionManager2.create(cwd);
|
|
2051
2654
|
}
|
|
2052
|
-
return
|
|
2655
|
+
return SessionManager2.create(cwd);
|
|
2053
2656
|
})();
|
|
2054
2657
|
const resourceLoader = options.skillPaths ? new BunnyAgentResourceLoader({
|
|
2055
2658
|
cwd,
|
|
@@ -2065,7 +2668,7 @@ function createPiRunner(options = {}) {
|
|
|
2065
2668
|
const customTools = options.env && Object.keys(options.env).length > 0 ? buildSecretAwareTools(cwd, options.env) : [];
|
|
2066
2669
|
if (imageModelName) {
|
|
2067
2670
|
const apiKey = await modelRegistry.authStorage.getApiKey(provider) ?? "";
|
|
2068
|
-
customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey));
|
|
2671
|
+
customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey), buildImageEditTool(cwd, imageModelName, model.baseUrl, apiKey));
|
|
2069
2672
|
}
|
|
2070
2673
|
const { session } = await createAgentSession({
|
|
2071
2674
|
cwd,
|
|
@@ -2105,165 +2708,34 @@ function createPiRunner(options = {}) {
|
|
|
2105
2708
|
}
|
|
2106
2709
|
try {
|
|
2107
2710
|
traceRawMessage(cwd, null, true, options.env);
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
if (imageParts.length > 0) {
|
|
2117
|
-
images = imageParts.map((p) => ({
|
|
2118
|
-
type: "image",
|
|
2119
|
-
data: p.data,
|
|
2120
|
-
mimeType: p.mimeType
|
|
2121
|
-
}));
|
|
2122
|
-
}
|
|
2711
|
+
const promptText = userInput;
|
|
2712
|
+
const promptPromise = session.prompt(promptText);
|
|
2713
|
+
const streamConverter = new PiAISDKStreamConverter({
|
|
2714
|
+
sessionId: session.sessionId,
|
|
2715
|
+
model,
|
|
2716
|
+
redactText: (value) => {
|
|
2717
|
+
if (options.env && Object.keys(options.env).length > 0) {
|
|
2718
|
+
return redactSecrets(value, options.env);
|
|
2123
2719
|
}
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
let hasFinished = false;
|
|
2131
|
-
const imageToolUsage = { input_tokens: 0, output_tokens: 0 };
|
|
2132
|
-
const newTextPartId = () => `text_${Date.now()}_${Math.random().toString(36).slice(2)}_${Math.random().toString(36).slice(2)}`;
|
|
2133
|
-
let activeTextPartId = null;
|
|
2134
|
-
let textStreamOpen = false;
|
|
2135
|
-
const endTextStreamIfOpen = function* () {
|
|
2136
|
-
if (textStreamOpen && activeTextPartId != null) {
|
|
2137
|
-
yield `data: ${JSON.stringify({ type: "text-end", id: activeTextPartId })}
|
|
2138
|
-
|
|
2139
|
-
`;
|
|
2140
|
-
textStreamOpen = false;
|
|
2141
|
-
activeTextPartId = null;
|
|
2142
|
-
}
|
|
2143
|
-
};
|
|
2144
|
-
const beginTextStream = function* () {
|
|
2145
|
-
activeTextPartId = newTextPartId();
|
|
2146
|
-
yield `data: ${JSON.stringify({ type: "text-start", id: activeTextPartId })}
|
|
2147
|
-
|
|
2148
|
-
`;
|
|
2149
|
-
textStreamOpen = true;
|
|
2150
|
-
};
|
|
2151
|
-
const ensureStartEvent = async function* () {
|
|
2152
|
-
if (!hasStarted) {
|
|
2153
|
-
yield `data: ${JSON.stringify({ type: "start", messageId })}
|
|
2154
|
-
|
|
2155
|
-
`;
|
|
2156
|
-
yield `data: ${JSON.stringify({
|
|
2157
|
-
type: "message-metadata",
|
|
2158
|
-
messageMetadata: { sessionId: session.sessionId }
|
|
2159
|
-
})}
|
|
2160
|
-
|
|
2161
|
-
`;
|
|
2162
|
-
hasStarted = true;
|
|
2163
|
-
}
|
|
2164
|
-
};
|
|
2165
|
-
const finishSuccess = async function* (usage) {
|
|
2166
|
-
yield* endTextStreamIfOpen();
|
|
2167
|
-
const finishPayload = { type: "finish", finishReason: "stop" };
|
|
2168
|
-
const hasImageUsage = imageToolUsage.input_tokens > 0 || imageToolUsage.output_tokens > 0;
|
|
2169
|
-
if (usage != null || hasImageUsage) {
|
|
2170
|
-
const base = usage != null ? usageToMessageMetadata(usage) : {};
|
|
2171
|
-
finishPayload.messageMetadata = {
|
|
2172
|
-
usage: {
|
|
2173
|
-
...base,
|
|
2174
|
-
input_tokens: (base.input_tokens ?? 0) + imageToolUsage.input_tokens,
|
|
2175
|
-
output_tokens: (base.output_tokens ?? 0) + imageToolUsage.output_tokens
|
|
2176
|
-
}
|
|
2177
|
-
};
|
|
2178
|
-
}
|
|
2179
|
-
yield `data: ${JSON.stringify(finishPayload)}
|
|
2180
|
-
|
|
2181
|
-
`;
|
|
2182
|
-
yield "data: [DONE]\n\n";
|
|
2183
|
-
hasFinished = true;
|
|
2184
|
-
};
|
|
2185
|
-
const finishError = async function* (errorText) {
|
|
2186
|
-
for (const chunk of emitStreamError(errorText)) {
|
|
2187
|
-
yield chunk;
|
|
2188
|
-
}
|
|
2189
|
-
hasFinished = true;
|
|
2190
|
-
};
|
|
2720
|
+
return value;
|
|
2721
|
+
},
|
|
2722
|
+
normalizeToolOutput: extractToolResultText,
|
|
2723
|
+
getUsageFromAgentEndMessages,
|
|
2724
|
+
getErrorFromAgentEndMessages
|
|
2725
|
+
});
|
|
2191
2726
|
while (!isComplete || eventQueue.length > 0) {
|
|
2192
2727
|
while (eventQueue.length > 0) {
|
|
2193
2728
|
const event = eventQueue.shift();
|
|
2194
2729
|
traceRawMessage(cwd, event, false, options.env);
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
if (msg?.role === "assistant") {
|
|
2199
|
-
yield* endTextStreamIfOpen();
|
|
2200
|
-
}
|
|
2201
|
-
} else if (event.type === "message_update") {
|
|
2202
|
-
const sub = event.assistantMessageEvent;
|
|
2203
|
-
if (sub.type === "text_start") {
|
|
2204
|
-
yield* endTextStreamIfOpen();
|
|
2205
|
-
yield* beginTextStream();
|
|
2206
|
-
} else if (sub.type === "text_delta") {
|
|
2207
|
-
let delta = sub.delta;
|
|
2208
|
-
if (delta) {
|
|
2209
|
-
if (options.env && Object.keys(options.env).length > 0) {
|
|
2210
|
-
delta = redactSecrets(delta, options.env);
|
|
2211
|
-
}
|
|
2212
|
-
if (!textStreamOpen) {
|
|
2213
|
-
yield* beginTextStream();
|
|
2214
|
-
}
|
|
2215
|
-
yield `data: ${JSON.stringify({
|
|
2216
|
-
type: "text-delta",
|
|
2217
|
-
id: activeTextPartId,
|
|
2218
|
-
delta
|
|
2219
|
-
})}
|
|
2220
|
-
|
|
2221
|
-
`;
|
|
2222
|
-
}
|
|
2223
|
-
} else if (sub.type === "toolcall_start") {
|
|
2224
|
-
yield* endTextStreamIfOpen();
|
|
2225
|
-
}
|
|
2226
|
-
} else if (event.type === "tool_execution_start") {
|
|
2227
|
-
yield* endTextStreamIfOpen();
|
|
2228
|
-
yield `data: ${JSON.stringify({ type: "tool-input-start", toolCallId: event.toolCallId, toolName: event.toolName, dynamic: true, providerExecuted: true })}
|
|
2229
|
-
|
|
2230
|
-
`;
|
|
2231
|
-
yield `data: ${JSON.stringify({ type: "tool-input-available", toolCallId: event.toolCallId, toolName: event.toolName, input: event.args, dynamic: true, providerExecuted: true })}
|
|
2232
|
-
|
|
2233
|
-
`;
|
|
2234
|
-
} else if (event.type === "tool_execution_end") {
|
|
2235
|
-
let output = extractToolResultText(event.result);
|
|
2236
|
-
if (options.env && Object.keys(options.env).length > 0) {
|
|
2237
|
-
output = redactSecrets(output, options.env);
|
|
2238
|
-
}
|
|
2239
|
-
if (event.toolName === "generate_image" && event.result !== null && typeof event.result === "object") {
|
|
2240
|
-
const details = event.result.details;
|
|
2241
|
-
const u = details?.response?.usage;
|
|
2242
|
-
if (u) {
|
|
2243
|
-
imageToolUsage.input_tokens += u.input_tokens ?? 0;
|
|
2244
|
-
imageToolUsage.output_tokens += u.output_tokens ?? 0;
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: event.toolCallId, output, isError: event.isError, dynamic: true, providerExecuted: true })}
|
|
2248
|
-
|
|
2249
|
-
`;
|
|
2250
|
-
} else if (event.type === "agent_end") {
|
|
2251
|
-
if (aborted) {
|
|
2252
|
-
yield* finishError("Run aborted by signal.");
|
|
2253
|
-
} else {
|
|
2254
|
-
const errorMsg = getErrorFromAgentEndMessages(event.messages);
|
|
2255
|
-
if (errorMsg) {
|
|
2256
|
-
yield* finishError(errorMsg);
|
|
2257
|
-
} else {
|
|
2258
|
-
const usage = getUsageFromAgentEndMessages(event.messages);
|
|
2259
|
-
yield* finishSuccess(usage);
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2730
|
+
const chunks = streamConverter.handleEvent(event, aborted);
|
|
2731
|
+
for (const chunk of chunks) {
|
|
2732
|
+
yield chunk;
|
|
2262
2733
|
}
|
|
2263
2734
|
}
|
|
2264
|
-
if (aborted && !
|
|
2265
|
-
|
|
2266
|
-
|
|
2735
|
+
if (aborted && !streamConverter.finished) {
|
|
2736
|
+
for (const chunk of streamConverter.forceError("Run aborted by signal.")) {
|
|
2737
|
+
yield chunk;
|
|
2738
|
+
}
|
|
2267
2739
|
break;
|
|
2268
2740
|
}
|
|
2269
2741
|
if (!isComplete && eventQueue.length === 0) {
|
|
@@ -2272,22 +2744,24 @@ function createPiRunner(options = {}) {
|
|
|
2272
2744
|
});
|
|
2273
2745
|
}
|
|
2274
2746
|
}
|
|
2275
|
-
if (
|
|
2747
|
+
if (streamConverter.finished) {
|
|
2276
2748
|
return;
|
|
2277
2749
|
}
|
|
2278
2750
|
try {
|
|
2279
2751
|
await promptPromise;
|
|
2280
2752
|
} catch (error) {
|
|
2281
|
-
if (!
|
|
2282
|
-
yield* ensureStartEvent();
|
|
2753
|
+
if (!streamConverter.finished) {
|
|
2283
2754
|
const message = error instanceof Error ? error.message : "Pi agent run failed.";
|
|
2284
|
-
|
|
2755
|
+
for (const chunk of streamConverter.forceError(message)) {
|
|
2756
|
+
yield chunk;
|
|
2757
|
+
}
|
|
2285
2758
|
}
|
|
2286
2759
|
return;
|
|
2287
2760
|
}
|
|
2288
|
-
if (!
|
|
2289
|
-
|
|
2290
|
-
|
|
2761
|
+
if (!streamConverter.finished && session.agent.state.error) {
|
|
2762
|
+
for (const chunk of streamConverter.forceError(session.agent.state.error)) {
|
|
2763
|
+
yield chunk;
|
|
2764
|
+
}
|
|
2291
2765
|
}
|
|
2292
2766
|
} finally {
|
|
2293
2767
|
if (abortSignal) {
|
|
@@ -2307,11 +2781,11 @@ function createPiRunner(options = {}) {
|
|
|
2307
2781
|
|
|
2308
2782
|
// ../../packages/runner-harness/dist/session.js
|
|
2309
2783
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2310
|
-
import { join as
|
|
2784
|
+
import { join as join9 } from "node:path";
|
|
2311
2785
|
var DIR = ".bunny-agent";
|
|
2312
2786
|
var FILE = "session-id";
|
|
2313
2787
|
function sessionPath(cwd) {
|
|
2314
|
-
return
|
|
2788
|
+
return join9(cwd, DIR, FILE);
|
|
2315
2789
|
}
|
|
2316
2790
|
function readSessionId(cwd) {
|
|
2317
2791
|
try {
|
|
@@ -2325,28 +2799,28 @@ function readSessionId(cwd) {
|
|
|
2325
2799
|
}
|
|
2326
2800
|
function writeSessionId(cwd, id) {
|
|
2327
2801
|
try {
|
|
2328
|
-
mkdirSync3(
|
|
2802
|
+
mkdirSync3(join9(cwd, DIR), { recursive: true });
|
|
2329
2803
|
writeFileSync4(sessionPath(cwd), id, "utf8");
|
|
2330
2804
|
} catch {
|
|
2331
2805
|
}
|
|
2332
2806
|
}
|
|
2333
2807
|
|
|
2334
2808
|
// ../../packages/runner-harness/dist/skills.js
|
|
2335
|
-
import { existsSync as existsSync7, readdirSync as
|
|
2809
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
2336
2810
|
import { homedir as homedir2 } from "node:os";
|
|
2337
|
-
import { join as
|
|
2811
|
+
import { join as join10 } from "node:path";
|
|
2338
2812
|
function discoverSkillPaths(cwd) {
|
|
2339
2813
|
const paths = [];
|
|
2340
2814
|
for (const base of [
|
|
2341
|
-
|
|
2342
|
-
|
|
2815
|
+
join10(cwd, "skills"),
|
|
2816
|
+
join10(homedir2(), ".bunny-agent", "skills")
|
|
2343
2817
|
]) {
|
|
2344
2818
|
if (!existsSync7(base))
|
|
2345
2819
|
continue;
|
|
2346
2820
|
try {
|
|
2347
|
-
for (const entry of
|
|
2348
|
-
const full =
|
|
2349
|
-
if (
|
|
2821
|
+
for (const entry of readdirSync3(base)) {
|
|
2822
|
+
const full = join10(base, entry);
|
|
2823
|
+
if (statSync3(full).isDirectory() && existsSync7(join10(full, "SKILL.md"))) {
|
|
2350
2824
|
paths.push(full);
|
|
2351
2825
|
}
|
|
2352
2826
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bunny-agent/runner-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.29-beta.10",
|
|
4
4
|
"description": "BunnyAgent Runner CLI - Like gemini-cli or claude-code, runs in your local terminal with AI SDK UI streaming",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"vitest": "^1.6.1",
|
|
56
56
|
"@bunny-agent/runner-harness": "0.1.1-beta.0",
|
|
57
57
|
"@bunny-agent/runner-claude": "0.6.2",
|
|
58
|
-
"@bunny-agent/runner-codex": "0.6.2",
|
|
59
58
|
"@bunny-agent/runner-gemini": "0.6.2",
|
|
59
|
+
"@bunny-agent/runner-codex": "0.6.2",
|
|
60
60
|
"@bunny-agent/runner-opencode": "0.6.2",
|
|
61
61
|
"@bunny-agent/runner-pi": "0.6.4-beta.0"
|
|
62
62
|
},
|