@sandagent/runner-cli 0.8.1 → 0.8.3

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 +104 -22
  2. package/package.json +3 -3
package/dist/bundle.mjs CHANGED
@@ -477,20 +477,20 @@ function createCanUseToolCallback(claudeOptions) {
477
477
  }
478
478
  const cwd = claudeOptions.cwd || process.cwd();
479
479
  try {
480
- const fs = await import("node:fs");
481
- const path = await import("node:path");
482
- const approvalDir = path.join(cwd, ".sandagent", "approvals");
483
- const approvalFile = path.join(approvalDir, `${toolUseID}.json`);
480
+ const fs2 = await import("node:fs");
481
+ const path2 = await import("node:path");
482
+ const approvalDir = path2.join(cwd, ".sandagent", "approvals");
483
+ const approvalFile = path2.join(approvalDir, `${toolUseID}.json`);
484
484
  const timeout = Date.now() + 6e4;
485
485
  let lastApproval = null;
486
486
  while (Date.now() < timeout) {
487
487
  try {
488
- const data = fs.readFileSync(approvalFile, "utf-8");
488
+ const data = fs2.readFileSync(approvalFile, "utf-8");
489
489
  const approval = JSON.parse(data);
490
490
  lastApproval = approval;
491
491
  if (approval.status === "completed") {
492
492
  try {
493
- fs.unlinkSync(approvalFile);
493
+ fs2.unlinkSync(approvalFile);
494
494
  } catch {
495
495
  }
496
496
  return {
@@ -506,7 +506,7 @@ function createCanUseToolCallback(claudeOptions) {
506
506
  await new Promise((resolve3) => setTimeout(resolve3, 500));
507
507
  }
508
508
  try {
509
- fs.unlinkSync(approvalFile);
509
+ fs2.unlinkSync(approvalFile);
510
510
  } catch {
511
511
  }
512
512
  if (lastApproval && Object.keys(lastApproval.answers).length > 0) {
@@ -708,6 +708,9 @@ Documentation: https://platform.claude.com/docs/en/agent-sdk/typescript-v2-previ
708
708
  }
709
709
 
710
710
  // ../../packages/runner-codex/dist/codex-runner.js
711
+ import * as fs from "node:fs";
712
+ import * as os from "node:os";
713
+ import * as path from "node:path";
711
714
  import { Codex } from "@openai/codex-sdk";
712
715
  function normalizeCodexModel(model) {
713
716
  const trimmed = model.trim();
@@ -767,13 +770,15 @@ function toToolEndPayload(event) {
767
770
  status: item.status,
768
771
  exitCode: item.exit_code,
769
772
  output: item.aggregated_output
770
- }
773
+ },
774
+ isError: item.exit_code !== 0
771
775
  };
772
776
  }
773
777
  if (item.type === "mcp_tool_call") {
774
778
  return {
775
779
  toolCallId: item.id,
776
- result: item.result ?? item.error ?? { status: item.status }
780
+ result: item.result ?? item.error ?? { status: item.status },
781
+ isError: item.error != null || item.status === "failed"
777
782
  };
778
783
  }
779
784
  if (item.type === "web_search") {
@@ -815,7 +820,36 @@ function createCodexRunner(options) {
815
820
  approvalPolicy: options.approvalPolicy
816
821
  };
817
822
  const thread = options.resume ? codex.resumeThread(options.resume, threadOptions) : codex.startThread(threadOptions);
818
- const streamedTurn = await thread.runStreamed(userInput, {
823
+ let inputToCodex = userInput;
824
+ const tempFiles = [];
825
+ try {
826
+ if (userInput.startsWith("[") && userInput.endsWith("]")) {
827
+ const parsed = JSON.parse(userInput);
828
+ if (Array.isArray(parsed)) {
829
+ const parts = [];
830
+ for (const p of parsed) {
831
+ if (p.type === "image" && typeof p.data === "string") {
832
+ const match = /^data:([^;]+);base64,(.+)$/.exec(p.data);
833
+ if (match) {
834
+ const ext = match[1].split("/")[1] ?? "png";
835
+ const tmpPath = path.join(os.tmpdir(), `sandagent-img-${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}`);
836
+ fs.writeFileSync(tmpPath, Buffer.from(match[2], "base64"));
837
+ tempFiles.push(tmpPath);
838
+ parts.push({ type: "local_image", path: tmpPath });
839
+ }
840
+ } else {
841
+ const text = typeof p.text === "string" ? p.text : JSON.stringify(p);
842
+ parts.push({ type: "text", text });
843
+ }
844
+ }
845
+ if (parts.length > 0) {
846
+ inputToCodex = parts;
847
+ }
848
+ }
849
+ }
850
+ } catch (e) {
851
+ }
852
+ const streamedTurn = await thread.runStreamed(inputToCodex, {
819
853
  signal: options.abortController?.signal
820
854
  });
821
855
  for await (const event of streamedTurn.events) {
@@ -836,7 +870,7 @@ function createCodexRunner(options) {
836
870
  }
837
871
  const toolEnd = toToolEndPayload(event);
838
872
  if (toolEnd) {
839
- yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: toolEnd.toolCallId, output: toolEnd.result })}
873
+ yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: toolEnd.toolCallId, output: toolEnd.result, isError: toolEnd.isError })}
840
874
 
841
875
  `;
842
876
  }
@@ -871,6 +905,12 @@ function createCodexRunner(options) {
871
905
  `;
872
906
  }
873
907
  }
908
+ for (const tmpFile of tempFiles) {
909
+ try {
910
+ fs.unlinkSync(tmpFile);
911
+ } catch {
912
+ }
913
+ }
874
914
  }
875
915
  };
876
916
  }
@@ -1156,19 +1196,19 @@ function createOpenCodeRunner(options = {}) {
1156
1196
  }
1157
1197
 
1158
1198
  // ../../packages/runner-pi/dist/pi-runner.js
1159
- import { appendFileSync as appendFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "node:fs";
1160
- import { join as join4 } from "node:path";
1199
+ import { appendFileSync as appendFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync3 } from "node:fs";
1200
+ import { join as join5 } from "node:path";
1161
1201
  import { getModel } from "@mariozechner/pi-ai";
1162
1202
  import { AuthStorage, ModelRegistry, SessionManager, createAgentSession } from "@mariozechner/pi-coding-agent";
1163
1203
 
1164
1204
  // ../../packages/runner-pi/dist/sandagent-resource-loader.js
1165
- import { join as join3 } from "node:path";
1166
1205
  import { homedir } from "node:os";
1206
+ import { join as join4 } from "node:path";
1167
1207
  import { DefaultResourceLoader, loadSkills } from "@mariozechner/pi-coding-agent";
1168
1208
  var SandagentResourceLoader = class {
1169
1209
  constructor(options = {}) {
1170
1210
  this.cwd = options.cwd ?? process.cwd();
1171
- this.agentDir = options.agentDir ?? join3(homedir(), ".pi", "agent");
1211
+ this.agentDir = options.agentDir ?? join4(homedir(), ".pi", "agent");
1172
1212
  this.skillPaths = options.skillPaths ?? [];
1173
1213
  this.delegate = new DefaultResourceLoader({
1174
1214
  cwd: this.cwd,
@@ -1257,6 +1297,24 @@ function emitStreamError(errorText) {
1257
1297
  "data: [DONE]\n\n"
1258
1298
  ];
1259
1299
  }
1300
+ function extractToolResultText(result) {
1301
+ if (result !== null && typeof result === "object") {
1302
+ const r = result;
1303
+ if (Array.isArray(r.content) && r.content.length > 0) {
1304
+ const text = r.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
1305
+ if (text.length > 0) {
1306
+ return text;
1307
+ }
1308
+ }
1309
+ }
1310
+ if (typeof result === "string")
1311
+ return result;
1312
+ try {
1313
+ return JSON.stringify(result);
1314
+ } catch {
1315
+ return String(result);
1316
+ }
1317
+ }
1260
1318
  function usageToMessageMetadata(usage) {
1261
1319
  return {
1262
1320
  input_tokens: usage.input,
@@ -1288,9 +1346,9 @@ function traceRawMessage(debugCwd, data, reset = false) {
1288
1346
  if (!enabled)
1289
1347
  return;
1290
1348
  try {
1291
- const file = join4(debugCwd, "pi-message-stream-debug.json");
1349
+ const file = join5(debugCwd, "pi-message-stream-debug.json");
1292
1350
  if (reset && existsSync3(file))
1293
- unlinkSync2(file);
1351
+ unlinkSync3(file);
1294
1352
  const type = data !== null && typeof data === "object" ? data.type : void 0;
1295
1353
  let payload = data;
1296
1354
  try {
@@ -1353,16 +1411,20 @@ function createPiRunner(options = {}) {
1353
1411
  }
1354
1412
  const sessions = await SessionManager.list(cwd);
1355
1413
  const found = sessions.find((s) => s.id === resume);
1356
- return found ? SessionManager.open(found.path) : SessionManager.continueRecent(cwd);
1414
+ return found ? SessionManager.open(found.path) : SessionManager.create(cwd);
1357
1415
  }
1358
- return SessionManager.continueRecent(cwd);
1416
+ return SessionManager.create(cwd);
1359
1417
  })();
1418
+ const resourceLoader = options.skillPaths ? new SandagentResourceLoader({ cwd, skillPaths: options.skillPaths }) : void 0;
1419
+ if (resourceLoader) {
1420
+ await resourceLoader.reload();
1421
+ }
1360
1422
  const { session } = await createAgentSession({
1361
1423
  cwd,
1362
1424
  model,
1363
1425
  sessionManager,
1364
1426
  modelRegistry,
1365
- resourceLoader: options.skillPaths ? new SandagentResourceLoader({ cwd, skillPaths: options.skillPaths }) : void 0
1427
+ resourceLoader
1366
1428
  });
1367
1429
  if (options.systemPrompt != null && options.systemPrompt !== "") {
1368
1430
  session.agent.setSystemPrompt(options.systemPrompt);
@@ -1399,7 +1461,26 @@ function createPiRunner(options = {}) {
1399
1461
  }
1400
1462
  try {
1401
1463
  traceRawMessage(cwd, null, true);
1402
- const promptPromise = session.prompt(userInput);
1464
+ let promptText = userInput;
1465
+ let images = void 0;
1466
+ try {
1467
+ if (userInput.startsWith("[") && userInput.endsWith("]")) {
1468
+ const parsed = JSON.parse(userInput);
1469
+ if (Array.isArray(parsed)) {
1470
+ promptText = parsed.filter((p) => p.type === "text").map((p) => p.text).join("\n");
1471
+ const imageParts = parsed.filter((p) => p.type === "image");
1472
+ if (imageParts.length > 0) {
1473
+ images = imageParts.map((p) => ({
1474
+ type: "image",
1475
+ data: p.data,
1476
+ mimeType: p.mimeType
1477
+ }));
1478
+ }
1479
+ }
1480
+ }
1481
+ } catch (e) {
1482
+ }
1483
+ const promptPromise = session.prompt(promptText, images ? { images } : void 0);
1403
1484
  const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
1404
1485
  const textId = `text_${Date.now()}_${Math.random().toString(36).slice(2)}`;
1405
1486
  let hasStarted = false;
@@ -1471,7 +1552,8 @@ function createPiRunner(options = {}) {
1471
1552
 
1472
1553
  `;
1473
1554
  } else if (event.type === "tool_execution_end") {
1474
- yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: event.toolCallId, output: event.result, dynamic: true })}
1555
+ const output = extractToolResultText(event.result);
1556
+ yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: event.toolCallId, output, isError: event.isError, dynamic: true })}
1475
1557
 
1476
1558
  `;
1477
1559
  } else if (event.type === "agent_end") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandagent/runner-cli",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
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": {
@@ -55,9 +55,9 @@
55
55
  "vitest": "^1.6.1",
56
56
  "@sandagent/runner-claude": "0.6.2",
57
57
  "@sandagent/runner-codex": "0.6.2",
58
+ "@sandagent/runner-gemini": "0.6.2",
58
59
  "@sandagent/runner-opencode": "0.6.2",
59
- "@sandagent/runner-pi": "0.6.3",
60
- "@sandagent/runner-gemini": "0.6.2"
60
+ "@sandagent/runner-pi": "0.6.3"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsc && pnpm bundle",