@sandagent/runner-cli 0.9.19 → 0.9.21
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 +336 -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,311 @@ 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 AUTO_DETECT_ORDER = [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 resolveSearchProviders(env) {
|
|
1388
|
+
const available = [];
|
|
1389
|
+
for (const p of AUTO_DETECT_ORDER) {
|
|
1390
|
+
for (const key of p.envKeys) {
|
|
1391
|
+
const val = getEnv(env, key);
|
|
1392
|
+
if (val) {
|
|
1393
|
+
available.push({ provider: p, apiKey: val });
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return available;
|
|
1399
|
+
}
|
|
1400
|
+
function resolveSearchProvider(env) {
|
|
1401
|
+
const all = resolveSearchProviders(env);
|
|
1402
|
+
return all.length > 0 ? all[0] : null;
|
|
1403
|
+
}
|
|
1404
|
+
function isRateLimitError(err) {
|
|
1405
|
+
if (!(err instanceof Error))
|
|
1406
|
+
return false;
|
|
1407
|
+
const msg = err.message;
|
|
1408
|
+
return msg.includes("429") || msg.includes("rate") || msg.includes("quota") || msg.includes("limit");
|
|
1409
|
+
}
|
|
1410
|
+
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";
|
|
1411
|
+
function htmlToText(html) {
|
|
1412
|
+
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();
|
|
1413
|
+
}
|
|
1414
|
+
async function fetchPageContent(url) {
|
|
1415
|
+
const controller = new AbortController();
|
|
1416
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
1417
|
+
try {
|
|
1418
|
+
const res = await fetch(url, {
|
|
1419
|
+
headers: {
|
|
1420
|
+
"User-Agent": BROWSER_UA,
|
|
1421
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
1422
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
1423
|
+
},
|
|
1424
|
+
signal: controller.signal
|
|
1425
|
+
});
|
|
1426
|
+
if (!res.ok)
|
|
1427
|
+
return `(HTTP ${res.status}: ${res.statusText})`;
|
|
1428
|
+
const html = await res.text();
|
|
1429
|
+
const text = htmlToText(html);
|
|
1430
|
+
return text.length > 5e4 ? `${text.slice(0, 5e4)}
|
|
1431
|
+
|
|
1432
|
+
[Truncated]` : text;
|
|
1433
|
+
} catch (e) {
|
|
1434
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1435
|
+
return `(Error fetching ${url}: ${msg})`;
|
|
1436
|
+
} finally {
|
|
1437
|
+
clearTimeout(timeout);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
function formatSearchResults(results, providerLabel) {
|
|
1441
|
+
if (results.length === 0)
|
|
1442
|
+
return "No results found.";
|
|
1443
|
+
const header = `[${providerLabel}] ${results.length} result(s)
|
|
1444
|
+
`;
|
|
1445
|
+
return header + results.map((r, i) => {
|
|
1446
|
+
const lines = [
|
|
1447
|
+
`--- Result ${i + 1} ---`,
|
|
1448
|
+
`Title: ${r.title}`,
|
|
1449
|
+
`Link: ${r.link}`
|
|
1450
|
+
];
|
|
1451
|
+
if (r.age)
|
|
1452
|
+
lines.push(`Age: ${r.age}`);
|
|
1453
|
+
lines.push(`Snippet: ${r.snippet}`);
|
|
1454
|
+
if (r.content)
|
|
1455
|
+
lines.push(`Content:
|
|
1456
|
+
${r.content}`);
|
|
1457
|
+
return lines.join("\n");
|
|
1458
|
+
}).join("\n\n");
|
|
1459
|
+
}
|
|
1460
|
+
var webSearchSchema = {
|
|
1461
|
+
type: "object",
|
|
1462
|
+
required: ["query"],
|
|
1463
|
+
properties: {
|
|
1464
|
+
query: {
|
|
1465
|
+
type: "string",
|
|
1466
|
+
description: "Search query string"
|
|
1467
|
+
},
|
|
1468
|
+
count: {
|
|
1469
|
+
type: "number",
|
|
1470
|
+
description: "Number of results to return (default: 5, max: 20)"
|
|
1471
|
+
},
|
|
1472
|
+
freshness: {
|
|
1473
|
+
type: "string",
|
|
1474
|
+
description: 'Filter by time: "pd" (past day), "pw" (past week), "pm" (past month), "py" (past year), or "YYYY-MM-DDtoYYYY-MM-DD"'
|
|
1475
|
+
},
|
|
1476
|
+
country: {
|
|
1477
|
+
type: "string",
|
|
1478
|
+
description: "Two-letter country code for results (default: US)"
|
|
1479
|
+
},
|
|
1480
|
+
fetch_content: {
|
|
1481
|
+
type: "boolean",
|
|
1482
|
+
description: "If true, also fetch and include page content for each result (slower)"
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
var webFetchSchema = {
|
|
1487
|
+
type: "object",
|
|
1488
|
+
required: ["url"],
|
|
1489
|
+
properties: {
|
|
1490
|
+
url: {
|
|
1491
|
+
type: "string",
|
|
1492
|
+
description: "URL to fetch and extract readable content from"
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
function buildWebSearchTool(env) {
|
|
1497
|
+
const providers = resolveSearchProviders(env);
|
|
1498
|
+
if (providers.length === 0) {
|
|
1499
|
+
throw new Error("web_search: no search provider available. Set BRAVE_API_KEY or TAVILY_API_KEY.");
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
name: "web_search",
|
|
1503
|
+
label: "web search",
|
|
1504
|
+
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.",
|
|
1505
|
+
promptSnippet: "web_search(query, count?, freshness?, country?, fetch_content?) - search the web",
|
|
1506
|
+
promptGuidelines: [
|
|
1507
|
+
"Use web_search when you need current information, documentation, or facts not available locally.",
|
|
1508
|
+
"Set fetch_content=true only when you need the actual page text, not just snippets \u2014 it is slower.",
|
|
1509
|
+
"Prefer specific, focused queries over broad ones for better results."
|
|
1510
|
+
],
|
|
1511
|
+
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1512
|
+
parameters: webSearchSchema,
|
|
1513
|
+
async execute(_toolCallId, params, _signal, _onUpdate) {
|
|
1514
|
+
const p = params;
|
|
1515
|
+
const query = p.query;
|
|
1516
|
+
const count = p.count ?? 5;
|
|
1517
|
+
const country = p.country ?? "US";
|
|
1518
|
+
const freshness = p.freshness;
|
|
1519
|
+
const shouldFetchContent = p.fetch_content ?? false;
|
|
1520
|
+
let lastError;
|
|
1521
|
+
for (const { provider, apiKey } of providers) {
|
|
1522
|
+
try {
|
|
1523
|
+
const results = await provider.search({
|
|
1524
|
+
apiKey,
|
|
1525
|
+
query,
|
|
1526
|
+
count,
|
|
1527
|
+
country,
|
|
1528
|
+
freshness
|
|
1529
|
+
});
|
|
1530
|
+
if (shouldFetchContent) {
|
|
1531
|
+
for (const r of results) {
|
|
1532
|
+
r.content = await fetchPageContent(r.link);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
return {
|
|
1536
|
+
content: [
|
|
1537
|
+
{
|
|
1538
|
+
type: "text",
|
|
1539
|
+
text: formatSearchResults(results, provider.label)
|
|
1540
|
+
}
|
|
1541
|
+
],
|
|
1542
|
+
details: void 0
|
|
1543
|
+
};
|
|
1544
|
+
} catch (e) {
|
|
1545
|
+
lastError = e;
|
|
1546
|
+
if (isRateLimitError(e) && providers.length > 1) {
|
|
1547
|
+
console.error(`[sandagent:pi] ${provider.label} rate-limited, trying next provider...`);
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
break;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
1554
|
+
return {
|
|
1555
|
+
content: [
|
|
1556
|
+
{
|
|
1557
|
+
type: "text",
|
|
1558
|
+
text: `Web search error: ${msg}`
|
|
1559
|
+
}
|
|
1560
|
+
],
|
|
1561
|
+
details: void 0
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
function buildWebFetchTool() {
|
|
1567
|
+
return {
|
|
1568
|
+
name: "web_fetch",
|
|
1569
|
+
label: "web fetch",
|
|
1570
|
+
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.).",
|
|
1571
|
+
promptSnippet: "web_fetch(url) - fetch and extract content from a URL",
|
|
1572
|
+
promptGuidelines: [
|
|
1573
|
+
"Use web_fetch when you already have a URL and need its content.",
|
|
1574
|
+
"For finding URLs first, use web_search instead."
|
|
1575
|
+
],
|
|
1576
|
+
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1577
|
+
parameters: webFetchSchema,
|
|
1578
|
+
async execute(_toolCallId, params, _signal, _onUpdate) {
|
|
1579
|
+
const p = params;
|
|
1580
|
+
const url = p.url;
|
|
1581
|
+
try {
|
|
1582
|
+
const content = await fetchPageContent(url);
|
|
1583
|
+
return {
|
|
1584
|
+
content: [{ type: "text", text: content }],
|
|
1585
|
+
details: void 0
|
|
1586
|
+
};
|
|
1587
|
+
} catch (e) {
|
|
1588
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1589
|
+
return {
|
|
1590
|
+
content: [
|
|
1591
|
+
{ type: "text", text: `Error fetching URL: ${msg}` }
|
|
1592
|
+
],
|
|
1593
|
+
details: void 0
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
// ../../packages/runner-pi/dist/tool-overrides.js
|
|
1303
1601
|
function redactSecrets(text, secrets) {
|
|
1304
1602
|
if (Object.keys(secrets).length === 0)
|
|
1305
1603
|
return text;
|
|
1306
1604
|
let result = text;
|
|
1307
1605
|
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1308
|
-
const values = Object.values(secrets).filter((v) => v.length >=
|
|
1606
|
+
const values = Object.values(secrets).filter((v) => v.length >= 4).sort((a, b) => b.length - a.length);
|
|
1309
1607
|
for (const v of values) {
|
|
1310
1608
|
const ev = escapeRegex(v);
|
|
1311
1609
|
result = result.replace(new RegExp(`^\\S+=.*${ev}.*$\\n?`, "gm"), "");
|
|
@@ -1315,6 +1613,11 @@ function redactSecrets(text, secrets) {
|
|
|
1315
1613
|
result = result.replace(/\n{3,}/g, "\n\n");
|
|
1316
1614
|
return result.trim();
|
|
1317
1615
|
}
|
|
1616
|
+
function redactResultContent(result, secrets) {
|
|
1617
|
+
if (result?.content && Array.isArray(result.content)) {
|
|
1618
|
+
result.content = result.content.map((c) => c.type === "text" && typeof c.text === "string" ? { ...c, text: redactSecrets(c.text, secrets) } : c);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1318
1621
|
function buildEnvInjectedBashTool(cwd, extraEnv) {
|
|
1319
1622
|
const bashAgentTool = createBashTool(cwd, {
|
|
1320
1623
|
spawnHook: (ctx) => ({
|
|
@@ -1330,13 +1633,39 @@ function buildEnvInjectedBashTool(cwd, extraEnv) {
|
|
|
1330
1633
|
parameters: bashAgentTool.parameters,
|
|
1331
1634
|
async execute(toolCallId, params, signal, onUpdate) {
|
|
1332
1635
|
const result = await bashAgentTool.execute(toolCallId, params, signal, onUpdate);
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1636
|
+
redactResultContent(result, extraEnv);
|
|
1637
|
+
return result;
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function buildSecretRedactingReadTool(cwd, secrets) {
|
|
1642
|
+
const readAgentTool = createReadTool(cwd);
|
|
1643
|
+
return {
|
|
1644
|
+
name: readAgentTool.name,
|
|
1645
|
+
label: readAgentTool.label ?? "read",
|
|
1646
|
+
description: readAgentTool.description,
|
|
1647
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi internals
|
|
1648
|
+
parameters: readAgentTool.parameters,
|
|
1649
|
+
async execute(toolCallId, params, signal, onUpdate) {
|
|
1650
|
+
const result = await readAgentTool.execute(toolCallId, params, signal, onUpdate);
|
|
1651
|
+
redactResultContent(result, secrets);
|
|
1336
1652
|
return result;
|
|
1337
1653
|
}
|
|
1338
1654
|
};
|
|
1339
1655
|
}
|
|
1656
|
+
function buildSecretAwareTools(cwd, secrets) {
|
|
1657
|
+
const tools = [
|
|
1658
|
+
buildEnvInjectedBashTool(cwd, secrets),
|
|
1659
|
+
buildSecretRedactingReadTool(cwd, secrets),
|
|
1660
|
+
buildWebFetchTool()
|
|
1661
|
+
];
|
|
1662
|
+
if (resolveSearchProvider(secrets)) {
|
|
1663
|
+
tools.push(buildWebSearchTool(secrets));
|
|
1664
|
+
}
|
|
1665
|
+
return tools;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// ../../packages/runner-pi/dist/pi-runner.js
|
|
1340
1669
|
var LOG_PREFIX2 = "[sandagent:pi]";
|
|
1341
1670
|
function parseModelSpec(model) {
|
|
1342
1671
|
const trimmed = model.trim();
|
|
@@ -1511,7 +1840,7 @@ function createPiRunner(options = {}) {
|
|
|
1511
1840
|
if (resourceLoader) {
|
|
1512
1841
|
await resourceLoader.reload();
|
|
1513
1842
|
}
|
|
1514
|
-
const customTools = options.env && Object.keys(options.env).length > 0 ?
|
|
1843
|
+
const customTools = options.env && Object.keys(options.env).length > 0 ? buildSecretAwareTools(cwd, options.env) : [];
|
|
1515
1844
|
const { session } = await createAgentSession({
|
|
1516
1845
|
cwd,
|
|
1517
1846
|
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.21",
|
|
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-core": "0.1.1-beta.0",
|
|
56
57
|
"@sandagent/runner-codex": "0.6.2",
|
|
57
|
-
"@sandagent/runner-opencode": "0.6.2",
|
|
58
58
|
"@sandagent/runner-gemini": "0.6.2",
|
|
59
|
-
"@sandagent/runner-
|
|
60
|
-
"@sandagent/runner-
|
|
61
|
-
"@sandagent/runner-
|
|
59
|
+
"@sandagent/runner-opencode": "0.6.2",
|
|
60
|
+
"@sandagent/runner-claude": "0.6.2",
|
|
61
|
+
"@sandagent/runner-pi": "0.6.4-beta.0"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "tsc && pnpm bundle",
|