@sandagent/runner-cli 0.9.19 → 0.9.20
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 +315 -7
- package/package.json +5 -5
package/dist/bundle.mjs
CHANGED
|
@@ -1209,7 +1209,7 @@ function createOpenCodeRunner(options = {}) {
|
|
|
1209
1209
|
import { appendFileSync as appendFileSync2, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "node:fs";
|
|
1210
1210
|
import { join as join5 } from "node:path";
|
|
1211
1211
|
import { getModel } from "@mariozechner/pi-ai";
|
|
1212
|
-
import { AuthStorage, createAgentSession,
|
|
1212
|
+
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
1213
1213
|
|
|
1214
1214
|
// ../../packages/runner-pi/dist/sandagent-resource-loader.js
|
|
1215
1215
|
import { existsSync as existsSync3 } from "node:fs";
|
|
@@ -1299,13 +1299,290 @@ var SandagentResourceLoader = class {
|
|
|
1299
1299
|
}
|
|
1300
1300
|
};
|
|
1301
1301
|
|
|
1302
|
-
// ../../packages/runner-pi/dist/
|
|
1302
|
+
// ../../packages/runner-pi/dist/tool-overrides.js
|
|
1303
|
+
import { createBashTool, createReadTool } from "@mariozechner/pi-coding-agent";
|
|
1304
|
+
|
|
1305
|
+
// ../../packages/runner-pi/dist/web-tools.js
|
|
1306
|
+
var braveProvider = {
|
|
1307
|
+
id: "brave",
|
|
1308
|
+
label: "Brave Search",
|
|
1309
|
+
envKeys: ["BRAVE_API_KEY"],
|
|
1310
|
+
async search({ apiKey, query, count, country, freshness }) {
|
|
1311
|
+
const params = new URLSearchParams({
|
|
1312
|
+
q: query,
|
|
1313
|
+
count: String(Math.min(count, 20))
|
|
1314
|
+
});
|
|
1315
|
+
if (country)
|
|
1316
|
+
params.set("country", country);
|
|
1317
|
+
if (freshness)
|
|
1318
|
+
params.set("freshness", freshness);
|
|
1319
|
+
const res = await fetch(`https://api.search.brave.com/res/v1/web/search?${params}`, {
|
|
1320
|
+
headers: {
|
|
1321
|
+
Accept: "application/json",
|
|
1322
|
+
"Accept-Encoding": "gzip",
|
|
1323
|
+
"X-Subscription-Token": apiKey
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
if (!res.ok) {
|
|
1327
|
+
const body = await res.text().catch(() => "");
|
|
1328
|
+
throw new Error(`Brave API ${res.status}: ${res.statusText}
|
|
1329
|
+
${body}`);
|
|
1330
|
+
}
|
|
1331
|
+
const data = await res.json();
|
|
1332
|
+
const results = [];
|
|
1333
|
+
if (data.web?.results) {
|
|
1334
|
+
for (const r of data.web.results) {
|
|
1335
|
+
if (results.length >= count)
|
|
1336
|
+
break;
|
|
1337
|
+
results.push({
|
|
1338
|
+
title: r.title ?? "",
|
|
1339
|
+
link: r.url ?? "",
|
|
1340
|
+
snippet: r.description ?? "",
|
|
1341
|
+
age: r.age ?? r.page_age ?? ""
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return results;
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
var tavilyProvider = {
|
|
1349
|
+
id: "tavily",
|
|
1350
|
+
label: "Tavily",
|
|
1351
|
+
envKeys: ["TAVILY_API_KEY"],
|
|
1352
|
+
async search({ apiKey, query, count }) {
|
|
1353
|
+
const res = await fetch("https://api.tavily.com/search", {
|
|
1354
|
+
method: "POST",
|
|
1355
|
+
headers: { "Content-Type": "application/json" },
|
|
1356
|
+
body: JSON.stringify({
|
|
1357
|
+
api_key: apiKey,
|
|
1358
|
+
query,
|
|
1359
|
+
max_results: Math.min(count, 10),
|
|
1360
|
+
include_answer: false
|
|
1361
|
+
})
|
|
1362
|
+
});
|
|
1363
|
+
if (!res.ok) {
|
|
1364
|
+
const body = await res.text().catch(() => "");
|
|
1365
|
+
throw new Error(`Tavily API ${res.status}: ${res.statusText}
|
|
1366
|
+
${body}`);
|
|
1367
|
+
}
|
|
1368
|
+
const data = await res.json();
|
|
1369
|
+
const results = [];
|
|
1370
|
+
if (Array.isArray(data.results)) {
|
|
1371
|
+
for (const r of data.results) {
|
|
1372
|
+
results.push({
|
|
1373
|
+
title: r.title ?? "",
|
|
1374
|
+
link: r.url ?? "",
|
|
1375
|
+
snippet: r.content ?? ""
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return results;
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
var ALL_PROVIDERS = [braveProvider, tavilyProvider];
|
|
1383
|
+
function getEnv(env, key) {
|
|
1384
|
+
const v = env[key] ?? process.env[key];
|
|
1385
|
+
return v && v.length > 0 ? v : void 0;
|
|
1386
|
+
}
|
|
1387
|
+
function resolveSearchProvider(env) {
|
|
1388
|
+
for (const p of ALL_PROVIDERS) {
|
|
1389
|
+
for (const key of p.envKeys) {
|
|
1390
|
+
const val = getEnv(env, key);
|
|
1391
|
+
if (val)
|
|
1392
|
+
return { provider: p, apiKey: val };
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
var BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
1398
|
+
function htmlToText(html) {
|
|
1399
|
+
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();
|
|
1400
|
+
}
|
|
1401
|
+
async function fetchPageContent(url) {
|
|
1402
|
+
const controller = new AbortController();
|
|
1403
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
1404
|
+
try {
|
|
1405
|
+
const res = await fetch(url, {
|
|
1406
|
+
headers: {
|
|
1407
|
+
"User-Agent": BROWSER_UA,
|
|
1408
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
1409
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
1410
|
+
},
|
|
1411
|
+
signal: controller.signal
|
|
1412
|
+
});
|
|
1413
|
+
if (!res.ok)
|
|
1414
|
+
return `(HTTP ${res.status}: ${res.statusText})`;
|
|
1415
|
+
const html = await res.text();
|
|
1416
|
+
const text = htmlToText(html);
|
|
1417
|
+
return text.length > 5e4 ? `${text.slice(0, 5e4)}
|
|
1418
|
+
|
|
1419
|
+
[Truncated]` : text;
|
|
1420
|
+
} catch (e) {
|
|
1421
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1422
|
+
return `(Error fetching ${url}: ${msg})`;
|
|
1423
|
+
} finally {
|
|
1424
|
+
clearTimeout(timeout);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
function formatSearchResults(results, providerLabel) {
|
|
1428
|
+
if (results.length === 0)
|
|
1429
|
+
return "No results found.";
|
|
1430
|
+
const header = `[${providerLabel}] ${results.length} result(s)
|
|
1431
|
+
`;
|
|
1432
|
+
return header + results.map((r, i) => {
|
|
1433
|
+
const lines = [
|
|
1434
|
+
`--- Result ${i + 1} ---`,
|
|
1435
|
+
`Title: ${r.title}`,
|
|
1436
|
+
`Link: ${r.link}`
|
|
1437
|
+
];
|
|
1438
|
+
if (r.age)
|
|
1439
|
+
lines.push(`Age: ${r.age}`);
|
|
1440
|
+
lines.push(`Snippet: ${r.snippet}`);
|
|
1441
|
+
if (r.content)
|
|
1442
|
+
lines.push(`Content:
|
|
1443
|
+
${r.content}`);
|
|
1444
|
+
return lines.join("\n");
|
|
1445
|
+
}).join("\n\n");
|
|
1446
|
+
}
|
|
1447
|
+
var webSearchSchema = {
|
|
1448
|
+
type: "object",
|
|
1449
|
+
required: ["query"],
|
|
1450
|
+
properties: {
|
|
1451
|
+
query: {
|
|
1452
|
+
type: "string",
|
|
1453
|
+
description: "Search query string"
|
|
1454
|
+
},
|
|
1455
|
+
count: {
|
|
1456
|
+
type: "number",
|
|
1457
|
+
description: "Number of results to return (default: 5, max: 20)"
|
|
1458
|
+
},
|
|
1459
|
+
freshness: {
|
|
1460
|
+
type: "string",
|
|
1461
|
+
description: 'Filter by time: "pd" (past day), "pw" (past week), "pm" (past month), "py" (past year), or "YYYY-MM-DDtoYYYY-MM-DD"'
|
|
1462
|
+
},
|
|
1463
|
+
country: {
|
|
1464
|
+
type: "string",
|
|
1465
|
+
description: "Two-letter country code for results (default: US)"
|
|
1466
|
+
},
|
|
1467
|
+
fetch_content: {
|
|
1468
|
+
type: "boolean",
|
|
1469
|
+
description: "If true, also fetch and include page content for each result (slower)"
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
var webFetchSchema = {
|
|
1474
|
+
type: "object",
|
|
1475
|
+
required: ["url"],
|
|
1476
|
+
properties: {
|
|
1477
|
+
url: {
|
|
1478
|
+
type: "string",
|
|
1479
|
+
description: "URL to fetch and extract readable content from"
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
function buildWebSearchTool(env) {
|
|
1484
|
+
const resolved = resolveSearchProvider(env);
|
|
1485
|
+
if (!resolved) {
|
|
1486
|
+
throw new Error("web_search: no search provider available. Set BRAVE_API_KEY or TAVILY_API_KEY.");
|
|
1487
|
+
}
|
|
1488
|
+
const { provider, apiKey } = resolved;
|
|
1489
|
+
return {
|
|
1490
|
+
name: "web_search",
|
|
1491
|
+
label: `web search (${provider.label})`,
|
|
1492
|
+
description: "Search the web for information. Returns titles, URLs, and snippets. Use for documentation lookups, fact-checking, current events, or any query requiring web results.",
|
|
1493
|
+
promptSnippet: "web_search(query, count?, freshness?, country?, fetch_content?) - search the web",
|
|
1494
|
+
promptGuidelines: [
|
|
1495
|
+
"Use web_search when you need current information, documentation, or facts not available locally.",
|
|
1496
|
+
"Set fetch_content=true only when you need the actual page text, not just snippets \u2014 it is slower.",
|
|
1497
|
+
"Prefer specific, focused queries over broad ones for better results."
|
|
1498
|
+
],
|
|
1499
|
+
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1500
|
+
parameters: webSearchSchema,
|
|
1501
|
+
async execute(_toolCallId, params, _signal, _onUpdate) {
|
|
1502
|
+
const p = params;
|
|
1503
|
+
const query = p.query;
|
|
1504
|
+
const count = p.count ?? 5;
|
|
1505
|
+
const country = p.country ?? "US";
|
|
1506
|
+
const freshness = p.freshness;
|
|
1507
|
+
const shouldFetchContent = p.fetch_content ?? false;
|
|
1508
|
+
try {
|
|
1509
|
+
const results = await provider.search({
|
|
1510
|
+
apiKey,
|
|
1511
|
+
query,
|
|
1512
|
+
count,
|
|
1513
|
+
country,
|
|
1514
|
+
freshness
|
|
1515
|
+
});
|
|
1516
|
+
if (shouldFetchContent) {
|
|
1517
|
+
for (const r of results) {
|
|
1518
|
+
r.content = await fetchPageContent(r.link);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return {
|
|
1522
|
+
content: [
|
|
1523
|
+
{
|
|
1524
|
+
type: "text",
|
|
1525
|
+
text: formatSearchResults(results, provider.label)
|
|
1526
|
+
}
|
|
1527
|
+
],
|
|
1528
|
+
details: void 0
|
|
1529
|
+
};
|
|
1530
|
+
} catch (e) {
|
|
1531
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1532
|
+
return {
|
|
1533
|
+
content: [
|
|
1534
|
+
{
|
|
1535
|
+
type: "text",
|
|
1536
|
+
text: `Web search error (${provider.label}): ${msg}`
|
|
1537
|
+
}
|
|
1538
|
+
],
|
|
1539
|
+
details: void 0
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
function buildWebFetchTool() {
|
|
1546
|
+
return {
|
|
1547
|
+
name: "web_fetch",
|
|
1548
|
+
label: "web fetch",
|
|
1549
|
+
description: "Fetch a web page and extract its readable text content. Use when you need the full content of a specific URL (article, docs page, etc.).",
|
|
1550
|
+
promptSnippet: "web_fetch(url) - fetch and extract content from a URL",
|
|
1551
|
+
promptGuidelines: [
|
|
1552
|
+
"Use web_fetch when you already have a URL and need its content.",
|
|
1553
|
+
"For finding URLs first, use web_search instead."
|
|
1554
|
+
],
|
|
1555
|
+
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1556
|
+
parameters: webFetchSchema,
|
|
1557
|
+
async execute(_toolCallId, params, _signal, _onUpdate) {
|
|
1558
|
+
const p = params;
|
|
1559
|
+
const url = p.url;
|
|
1560
|
+
try {
|
|
1561
|
+
const content = await fetchPageContent(url);
|
|
1562
|
+
return {
|
|
1563
|
+
content: [{ type: "text", text: content }],
|
|
1564
|
+
details: void 0
|
|
1565
|
+
};
|
|
1566
|
+
} catch (e) {
|
|
1567
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1568
|
+
return {
|
|
1569
|
+
content: [
|
|
1570
|
+
{ type: "text", text: `Error fetching URL: ${msg}` }
|
|
1571
|
+
],
|
|
1572
|
+
details: void 0
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// ../../packages/runner-pi/dist/tool-overrides.js
|
|
1303
1580
|
function redactSecrets(text, secrets) {
|
|
1304
1581
|
if (Object.keys(secrets).length === 0)
|
|
1305
1582
|
return text;
|
|
1306
1583
|
let result = text;
|
|
1307
1584
|
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1308
|
-
const values = Object.values(secrets).filter((v) => v.length >=
|
|
1585
|
+
const values = Object.values(secrets).filter((v) => v.length >= 4).sort((a, b) => b.length - a.length);
|
|
1309
1586
|
for (const v of values) {
|
|
1310
1587
|
const ev = escapeRegex(v);
|
|
1311
1588
|
result = result.replace(new RegExp(`^\\S+=.*${ev}.*$\\n?`, "gm"), "");
|
|
@@ -1315,6 +1592,11 @@ function redactSecrets(text, secrets) {
|
|
|
1315
1592
|
result = result.replace(/\n{3,}/g, "\n\n");
|
|
1316
1593
|
return result.trim();
|
|
1317
1594
|
}
|
|
1595
|
+
function redactResultContent(result, secrets) {
|
|
1596
|
+
if (result?.content && Array.isArray(result.content)) {
|
|
1597
|
+
result.content = result.content.map((c) => c.type === "text" && typeof c.text === "string" ? { ...c, text: redactSecrets(c.text, secrets) } : c);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1318
1600
|
function buildEnvInjectedBashTool(cwd, extraEnv) {
|
|
1319
1601
|
const bashAgentTool = createBashTool(cwd, {
|
|
1320
1602
|
spawnHook: (ctx) => ({
|
|
@@ -1330,13 +1612,39 @@ function buildEnvInjectedBashTool(cwd, extraEnv) {
|
|
|
1330
1612
|
parameters: bashAgentTool.parameters,
|
|
1331
1613
|
async execute(toolCallId, params, signal, onUpdate) {
|
|
1332
1614
|
const result = await bashAgentTool.execute(toolCallId, params, signal, onUpdate);
|
|
1333
|
-
|
|
1334
|
-
result.content = result.content.map((c) => c.type === "text" && typeof c.text === "string" ? { ...c, text: redactSecrets(c.text, extraEnv) } : c);
|
|
1335
|
-
}
|
|
1615
|
+
redactResultContent(result, extraEnv);
|
|
1336
1616
|
return result;
|
|
1337
1617
|
}
|
|
1338
1618
|
};
|
|
1339
1619
|
}
|
|
1620
|
+
function buildSecretRedactingReadTool(cwd, secrets) {
|
|
1621
|
+
const readAgentTool = createReadTool(cwd);
|
|
1622
|
+
return {
|
|
1623
|
+
name: readAgentTool.name,
|
|
1624
|
+
label: readAgentTool.label ?? "read",
|
|
1625
|
+
description: readAgentTool.description,
|
|
1626
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi internals
|
|
1627
|
+
parameters: readAgentTool.parameters,
|
|
1628
|
+
async execute(toolCallId, params, signal, onUpdate) {
|
|
1629
|
+
const result = await readAgentTool.execute(toolCallId, params, signal, onUpdate);
|
|
1630
|
+
redactResultContent(result, secrets);
|
|
1631
|
+
return result;
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function buildSecretAwareTools(cwd, secrets) {
|
|
1636
|
+
const tools = [
|
|
1637
|
+
buildEnvInjectedBashTool(cwd, secrets),
|
|
1638
|
+
buildSecretRedactingReadTool(cwd, secrets),
|
|
1639
|
+
buildWebFetchTool()
|
|
1640
|
+
];
|
|
1641
|
+
if (resolveSearchProvider(secrets)) {
|
|
1642
|
+
tools.push(buildWebSearchTool(secrets));
|
|
1643
|
+
}
|
|
1644
|
+
return tools;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// ../../packages/runner-pi/dist/pi-runner.js
|
|
1340
1648
|
var LOG_PREFIX2 = "[sandagent:pi]";
|
|
1341
1649
|
function parseModelSpec(model) {
|
|
1342
1650
|
const trimmed = model.trim();
|
|
@@ -1511,7 +1819,7 @@ function createPiRunner(options = {}) {
|
|
|
1511
1819
|
if (resourceLoader) {
|
|
1512
1820
|
await resourceLoader.reload();
|
|
1513
1821
|
}
|
|
1514
|
-
const customTools = options.env && Object.keys(options.env).length > 0 ?
|
|
1822
|
+
const customTools = options.env && Object.keys(options.env).length > 0 ? buildSecretAwareTools(cwd, options.env) : [];
|
|
1515
1823
|
const { session } = await createAgentSession({
|
|
1516
1824
|
cwd,
|
|
1517
1825
|
model,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sandagent/runner-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.20",
|
|
4
4
|
"description": "SandAgent 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": {
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"esbuild": "^0.27.2",
|
|
54
54
|
"typescript": "^5.3.0",
|
|
55
55
|
"vitest": "^1.6.1",
|
|
56
|
-
"@sandagent/runner-codex": "0.6.2",
|
|
57
|
-
"@sandagent/runner-opencode": "0.6.2",
|
|
58
|
-
"@sandagent/runner-gemini": "0.6.2",
|
|
59
56
|
"@sandagent/runner-core": "0.1.1-beta.0",
|
|
57
|
+
"@sandagent/runner-claude": "0.6.2",
|
|
58
|
+
"@sandagent/runner-gemini": "0.6.2",
|
|
59
|
+
"@sandagent/runner-opencode": "0.6.2",
|
|
60
60
|
"@sandagent/runner-pi": "0.6.4-beta.0",
|
|
61
|
-
"@sandagent/runner-
|
|
61
|
+
"@sandagent/runner-codex": "0.6.2"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "tsc && pnpm bundle",
|