@sandagent/runner-cli 0.9.9-beta.2 → 0.9.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/bundle.mjs +167 -177
- package/package.json +4 -4
package/dist/bundle.mjs
CHANGED
|
@@ -1398,20 +1398,19 @@ function createPiRunner(options = {}) {
|
|
|
1398
1398
|
}
|
|
1399
1399
|
const { provider, modelName } = parseModelSpec(modelSpec.trim());
|
|
1400
1400
|
const cwd = options.cwd || process.cwd();
|
|
1401
|
-
const apiKeyEnvKey = `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
1402
|
-
const inlineApiKey = typeof options.env?.[apiKeyEnvKey] === "string" && options.env[apiKeyEnvKey].length > 0 ? options.env[apiKeyEnvKey] : void 0;
|
|
1403
1401
|
const modelRegistry = new ModelRegistry(AuthStorage.create());
|
|
1404
1402
|
const defaultModel = getModel(provider, modelName);
|
|
1405
1403
|
let model = defaultModel ?? modelRegistry.find(provider, modelName);
|
|
1406
1404
|
if (model == null) {
|
|
1407
1405
|
const baseUrlEnvKey = `${provider.toUpperCase().replace(/-/g, "_")}_BASE_URL`;
|
|
1408
|
-
const
|
|
1406
|
+
const apiKeyEnvKey = `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
1407
|
+
const baseUrl = process.env[baseUrlEnvKey] ?? process.env.OPENAI_BASE_URL;
|
|
1409
1408
|
if (!baseUrl) {
|
|
1410
1409
|
throw new Error(`Pi runner: model "${modelSpec}" not found in built-in catalog. Set ${baseUrlEnvKey} (or OPENAI_BASE_URL) to auto-register it.`);
|
|
1411
1410
|
}
|
|
1412
1411
|
modelRegistry.registerProvider(provider, {
|
|
1413
1412
|
baseUrl,
|
|
1414
|
-
apiKey:
|
|
1413
|
+
apiKey: apiKeyEnvKey,
|
|
1415
1414
|
api: "openai-completions",
|
|
1416
1415
|
models: [
|
|
1417
1416
|
{
|
|
@@ -1434,222 +1433,213 @@ function createPiRunner(options = {}) {
|
|
|
1434
1433
|
applyModelOverrides(model, provider, options.env);
|
|
1435
1434
|
return {
|
|
1436
1435
|
async *run(userInput) {
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
const sessionManager = await (async () => {
|
|
1443
|
-
if (resume !== void 0 && resume !== "") {
|
|
1444
|
-
if (resume.includes("/")) {
|
|
1445
|
-
return SessionManager.open(resume);
|
|
1446
|
-
}
|
|
1447
|
-
const sessions = await SessionManager.list(cwd);
|
|
1448
|
-
const found = sessions.find((s) => s.id === resume);
|
|
1449
|
-
return found ? SessionManager.open(found.path) : SessionManager.create(cwd);
|
|
1436
|
+
const resume = options.sessionId?.trim();
|
|
1437
|
+
const sessionManager = await (async () => {
|
|
1438
|
+
if (resume !== void 0 && resume !== "") {
|
|
1439
|
+
if (resume.includes("/")) {
|
|
1440
|
+
return SessionManager.open(resume);
|
|
1450
1441
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
if (options.skillPaths && options.skillPaths.length > 0) {
|
|
1455
|
-
console.error(`${LOG_PREFIX2} runner: cwd=${cwd} skillPaths=${JSON.stringify(options.skillPaths)}`);
|
|
1456
|
-
}
|
|
1457
|
-
if (resourceLoader) {
|
|
1458
|
-
await resourceLoader.reload();
|
|
1442
|
+
const sessions = await SessionManager.list(cwd);
|
|
1443
|
+
const found = sessions.find((s) => s.id === resume);
|
|
1444
|
+
return found ? SessionManager.open(found.path) : SessionManager.create(cwd);
|
|
1459
1445
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1446
|
+
return SessionManager.create(cwd);
|
|
1447
|
+
})();
|
|
1448
|
+
const resourceLoader = options.skillPaths ? new SandagentResourceLoader({ cwd, skillPaths: options.skillPaths }) : void 0;
|
|
1449
|
+
if (options.skillPaths && options.skillPaths.length > 0) {
|
|
1450
|
+
console.error(`${LOG_PREFIX2} runner: cwd=${cwd} skillPaths=${JSON.stringify(options.skillPaths)}`);
|
|
1451
|
+
}
|
|
1452
|
+
if (resourceLoader) {
|
|
1453
|
+
await resourceLoader.reload();
|
|
1454
|
+
}
|
|
1455
|
+
const { session } = await createAgentSession({
|
|
1456
|
+
cwd,
|
|
1457
|
+
model,
|
|
1458
|
+
sessionManager,
|
|
1459
|
+
modelRegistry,
|
|
1460
|
+
resourceLoader
|
|
1461
|
+
});
|
|
1462
|
+
if (options.systemPrompt != null && options.systemPrompt !== "") {
|
|
1463
|
+
const existing = session.agent.state.systemPrompt ?? "";
|
|
1464
|
+
session.agent.setSystemPrompt(existing ? `${existing}
|
|
1470
1465
|
|
|
1471
1466
|
---
|
|
1472
1467
|
|
|
1473
1468
|
${options.systemPrompt}` : options.systemPrompt);
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
isComplete = true;
|
|
1487
|
-
}
|
|
1488
|
-
notify();
|
|
1489
|
-
});
|
|
1490
|
-
const abortSignal = options.abortController?.signal;
|
|
1491
|
-
const abortHandler = () => {
|
|
1492
|
-
aborted = true;
|
|
1469
|
+
}
|
|
1470
|
+
const eventQueue = [];
|
|
1471
|
+
let isComplete = false;
|
|
1472
|
+
let aborted = false;
|
|
1473
|
+
let wakeConsumer = null;
|
|
1474
|
+
const notify = () => {
|
|
1475
|
+
wakeConsumer?.();
|
|
1476
|
+
wakeConsumer = null;
|
|
1477
|
+
};
|
|
1478
|
+
const unsubscribe = session.subscribe((e) => {
|
|
1479
|
+
eventQueue.push(e);
|
|
1480
|
+
if (e.type === "agent_end") {
|
|
1493
1481
|
isComplete = true;
|
|
1494
|
-
void session.abort();
|
|
1495
|
-
notify();
|
|
1496
|
-
};
|
|
1497
|
-
if (abortSignal) {
|
|
1498
|
-
abortSignal.addEventListener("abort", abortHandler);
|
|
1499
|
-
if (abortSignal.aborted) {
|
|
1500
|
-
abortHandler();
|
|
1501
|
-
}
|
|
1502
1482
|
}
|
|
1483
|
+
notify();
|
|
1484
|
+
});
|
|
1485
|
+
const abortSignal = options.abortController?.signal;
|
|
1486
|
+
const abortHandler = () => {
|
|
1487
|
+
aborted = true;
|
|
1488
|
+
isComplete = true;
|
|
1489
|
+
void session.abort();
|
|
1490
|
+
notify();
|
|
1491
|
+
};
|
|
1492
|
+
if (abortSignal) {
|
|
1493
|
+
abortSignal.addEventListener("abort", abortHandler);
|
|
1494
|
+
if (abortSignal.aborted) {
|
|
1495
|
+
abortHandler();
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
try {
|
|
1499
|
+
traceRawMessage(cwd, null, true);
|
|
1500
|
+
let promptText = userInput;
|
|
1501
|
+
let images;
|
|
1503
1502
|
try {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
type: "image",
|
|
1516
|
-
data: p.data,
|
|
1517
|
-
mimeType: p.mimeType
|
|
1518
|
-
}));
|
|
1519
|
-
}
|
|
1503
|
+
if (userInput.startsWith("[") && userInput.endsWith("]")) {
|
|
1504
|
+
const parsed = JSON.parse(userInput);
|
|
1505
|
+
if (Array.isArray(parsed)) {
|
|
1506
|
+
promptText = parsed.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
1507
|
+
const imageParts = parsed.filter((p) => p.type === "image");
|
|
1508
|
+
if (imageParts.length > 0) {
|
|
1509
|
+
images = imageParts.map((p) => ({
|
|
1510
|
+
type: "image",
|
|
1511
|
+
data: p.data,
|
|
1512
|
+
mimeType: p.mimeType
|
|
1513
|
+
}));
|
|
1520
1514
|
}
|
|
1521
1515
|
}
|
|
1522
|
-
} catch (_e) {
|
|
1523
1516
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1517
|
+
} catch (_e) {
|
|
1518
|
+
}
|
|
1519
|
+
const promptPromise = session.prompt(promptText, images ? { images } : void 0);
|
|
1520
|
+
const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1521
|
+
const textId = `text_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1522
|
+
let hasStarted = false;
|
|
1523
|
+
let hasTextStarted = false;
|
|
1524
|
+
let hasFinished = false;
|
|
1525
|
+
const ensureStartEvent = async function* () {
|
|
1526
|
+
if (!hasStarted) {
|
|
1527
|
+
yield `data: ${JSON.stringify({ type: "start", messageId })}
|
|
1533
1528
|
|
|
1534
1529
|
`;
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1530
|
+
yield `data: ${JSON.stringify({
|
|
1531
|
+
type: "message-metadata",
|
|
1532
|
+
messageMetadata: { sessionId: session.sessionId }
|
|
1533
|
+
})}
|
|
1539
1534
|
|
|
1540
1535
|
`;
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1536
|
+
hasStarted = true;
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
const finishSuccess = async function* (usage) {
|
|
1540
|
+
if (hasTextStarted) {
|
|
1541
|
+
yield `data: ${JSON.stringify({ type: "text-end", id: textId })}
|
|
1547
1542
|
|
|
1548
1543
|
`;
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1544
|
+
}
|
|
1545
|
+
const finishPayload = { type: "finish", finishReason: "stop" };
|
|
1546
|
+
if (usage != null) {
|
|
1547
|
+
finishPayload.messageMetadata = {
|
|
1548
|
+
usage: usageToMessageMetadata(usage)
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
yield `data: ${JSON.stringify(finishPayload)}
|
|
1557
1552
|
|
|
1558
1553
|
`;
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1554
|
+
yield "data: [DONE]\n\n";
|
|
1555
|
+
hasFinished = true;
|
|
1556
|
+
};
|
|
1557
|
+
const finishError = async function* (errorText) {
|
|
1558
|
+
for (const chunk of emitStreamError(errorText)) {
|
|
1559
|
+
yield chunk;
|
|
1560
|
+
}
|
|
1561
|
+
hasFinished = true;
|
|
1562
|
+
};
|
|
1563
|
+
while (!isComplete || eventQueue.length > 0) {
|
|
1564
|
+
while (eventQueue.length > 0) {
|
|
1565
|
+
const event = eventQueue.shift();
|
|
1566
|
+
traceRawMessage(cwd, event);
|
|
1567
|
+
yield* ensureStartEvent();
|
|
1568
|
+
if (event.type === "message_update") {
|
|
1569
|
+
if (event.assistantMessageEvent.type === "text_delta") {
|
|
1570
|
+
const delta = event.assistantMessageEvent.delta;
|
|
1571
|
+
if (delta) {
|
|
1572
|
+
if (!hasTextStarted) {
|
|
1573
|
+
yield `data: ${JSON.stringify({ type: "text-start", id: textId })}
|
|
1579
1574
|
|
|
1580
1575
|
`;
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1576
|
+
hasTextStarted = true;
|
|
1577
|
+
}
|
|
1578
|
+
yield `data: ${JSON.stringify({ type: "text-delta", id: textId, delta })}
|
|
1584
1579
|
|
|
1585
1580
|
`;
|
|
1586
|
-
}
|
|
1587
1581
|
}
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1582
|
+
}
|
|
1583
|
+
} else if (event.type === "tool_execution_start") {
|
|
1584
|
+
yield `data: ${JSON.stringify({ type: "tool-input-start", toolCallId: event.toolCallId, toolName: event.toolName, dynamic: true, providerExecuted: true })}
|
|
1590
1585
|
|
|
1591
1586
|
`;
|
|
1592
|
-
|
|
1587
|
+
yield `data: ${JSON.stringify({ type: "tool-input-available", toolCallId: event.toolCallId, toolName: event.toolName, input: event.args, dynamic: true, providerExecuted: true })}
|
|
1593
1588
|
|
|
1594
1589
|
`;
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1590
|
+
} else if (event.type === "tool_execution_end") {
|
|
1591
|
+
const output = extractToolResultText(event.result);
|
|
1592
|
+
yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: event.toolCallId, output, isError: event.isError, dynamic: true, providerExecuted: true })}
|
|
1598
1593
|
|
|
1599
1594
|
`;
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1595
|
+
} else if (event.type === "agent_end") {
|
|
1596
|
+
if (aborted) {
|
|
1597
|
+
yield* finishError("Run aborted by signal.");
|
|
1598
|
+
} else {
|
|
1599
|
+
const errorMsg = getErrorFromAgentEndMessages(event.messages);
|
|
1600
|
+
if (errorMsg) {
|
|
1601
|
+
yield* finishError(errorMsg);
|
|
1603
1602
|
} else {
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
yield* finishError(errorMsg);
|
|
1607
|
-
} else {
|
|
1608
|
-
const usage = getUsageFromAgentEndMessages(event.messages);
|
|
1609
|
-
yield* finishSuccess(usage);
|
|
1610
|
-
}
|
|
1603
|
+
const usage = getUsageFromAgentEndMessages(event.messages);
|
|
1604
|
+
yield* finishSuccess(usage);
|
|
1611
1605
|
}
|
|
1612
1606
|
}
|
|
1613
1607
|
}
|
|
1614
|
-
if (aborted && !hasFinished) {
|
|
1615
|
-
yield* ensureStartEvent();
|
|
1616
|
-
yield* finishError("Run aborted by signal.");
|
|
1617
|
-
break;
|
|
1618
|
-
}
|
|
1619
|
-
if (!isComplete && eventQueue.length === 0) {
|
|
1620
|
-
await new Promise((resolve4) => {
|
|
1621
|
-
wakeConsumer = resolve4;
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
1608
|
}
|
|
1625
|
-
if (hasFinished) {
|
|
1626
|
-
|
|
1609
|
+
if (aborted && !hasFinished) {
|
|
1610
|
+
yield* ensureStartEvent();
|
|
1611
|
+
yield* finishError("Run aborted by signal.");
|
|
1612
|
+
break;
|
|
1627
1613
|
}
|
|
1628
|
-
|
|
1629
|
-
await
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
yield* ensureStartEvent();
|
|
1633
|
-
const message = error instanceof Error ? error.message : "Pi agent run failed.";
|
|
1634
|
-
yield* finishError(message);
|
|
1635
|
-
}
|
|
1636
|
-
return;
|
|
1614
|
+
if (!isComplete && eventQueue.length === 0) {
|
|
1615
|
+
await new Promise((resolve4) => {
|
|
1616
|
+
wakeConsumer = resolve4;
|
|
1617
|
+
});
|
|
1637
1618
|
}
|
|
1638
|
-
|
|
1619
|
+
}
|
|
1620
|
+
if (hasFinished) {
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
try {
|
|
1624
|
+
await promptPromise;
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
if (!hasFinished) {
|
|
1639
1627
|
yield* ensureStartEvent();
|
|
1640
|
-
|
|
1628
|
+
const message = error instanceof Error ? error.message : "Pi agent run failed.";
|
|
1629
|
+
yield* finishError(message);
|
|
1641
1630
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
session.dispose();
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (!hasFinished && session.agent.state.error) {
|
|
1634
|
+
yield* ensureStartEvent();
|
|
1635
|
+
yield* finishError(session.agent.state.error);
|
|
1648
1636
|
}
|
|
1649
1637
|
} finally {
|
|
1650
|
-
if (
|
|
1651
|
-
|
|
1638
|
+
if (abortSignal) {
|
|
1639
|
+
abortSignal.removeEventListener("abort", abortHandler);
|
|
1652
1640
|
}
|
|
1641
|
+
unsubscribe();
|
|
1642
|
+
session.dispose();
|
|
1653
1643
|
}
|
|
1654
1644
|
}
|
|
1655
1645
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sandagent/runner-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.10",
|
|
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.0",
|
|
56
57
|
"@sandagent/runner-claude": "0.6.2",
|
|
57
58
|
"@sandagent/runner-codex": "0.6.2",
|
|
58
|
-
"@sandagent/runner-core": "0.1.0",
|
|
59
59
|
"@sandagent/runner-gemini": "0.6.2",
|
|
60
|
-
"@sandagent/runner-
|
|
61
|
-
"@sandagent/runner-
|
|
60
|
+
"@sandagent/runner-opencode": "0.6.2",
|
|
61
|
+
"@sandagent/runner-pi": "0.6.3"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "tsc && pnpm bundle",
|