@sandagent/runner-cli 0.9.19-beta.5 → 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.
Files changed (2) hide show
  1. package/dist/bundle.mjs +315 -7
  2. package/package.json +3 -3
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, createBashTool, ModelRegistry, SessionManager } from "@mariozechner/pi-coding-agent";
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/pi-runner.js
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(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/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 >= 8).sort((a, b) => b.length - a.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
- if (result?.content && Array.isArray(result.content)) {
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 ? [buildEnvInjectedBashTool(cwd, options.env)] : [];
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.19-beta.5",
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,11 +53,11 @@
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-claude": "0.6.2",
57
58
  "@sandagent/runner-gemini": "0.6.2",
58
- "@sandagent/runner-core": "0.1.1-beta.0",
59
- "@sandagent/runner-pi": "0.6.4-beta.0",
60
59
  "@sandagent/runner-opencode": "0.6.2",
60
+ "@sandagent/runner-pi": "0.6.4-beta.0",
61
61
  "@sandagent/runner-codex": "0.6.2"
62
62
  },
63
63
  "scripts": {