@bunny-agent/runner-cli 0.9.29-beta.5 → 0.9.29-beta.7

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 +386 -230
  2. package/package.json +4 -4
package/dist/bundle.mjs CHANGED
@@ -1274,9 +1274,9 @@ function createOpenCodeRunner(options = {}) {
1274
1274
 
1275
1275
  // ../../packages/runner-pi/dist/pi-runner.js
1276
1276
  import { appendFileSync as appendFileSync2, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "node:fs";
1277
- import { join as join7 } from "node:path";
1277
+ import { join as join8 } from "node:path";
1278
1278
  import { getModel } from "@mariozechner/pi-ai";
1279
- import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@mariozechner/pi-coding-agent";
1279
+ import { AuthStorage, createAgentSession, ModelRegistry, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
1280
1280
 
1281
1281
  // ../../packages/runner-pi/dist/bunny-agent-resource-loader.js
1282
1282
  import { existsSync as existsSync4 } from "node:fs";
@@ -1400,8 +1400,8 @@ var generateImageSchema = {
1400
1400
  },
1401
1401
  quality: {
1402
1402
  type: "string",
1403
- enum: ["standard", "hd"],
1404
- description: "Image quality (OpenAI only). Defaults to standard."
1403
+ enum: ["low", "medium", "high", "auto"],
1404
+ description: "Image quality. Defaults to auto."
1405
1405
  }
1406
1406
  },
1407
1407
  required: ["prompt"],
@@ -1563,7 +1563,7 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
1563
1563
  const p = params;
1564
1564
  const prompt = p.prompt;
1565
1565
  const size = p.size ?? "1024x1024";
1566
- const quality = p.quality ?? "standard";
1566
+ const quality = p.quality ?? "auto";
1567
1567
  const rawFilename = p.filename;
1568
1568
  const filename = rawFilename ? extname(rawFilename) ? rawFilename : `${rawFilename}.png` : `image_${Date.now()}.png`;
1569
1569
  const filePath = join6(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
@@ -1601,6 +1601,7 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
1601
1601
  ],
1602
1602
  details: {
1603
1603
  filePath: savedPath,
1604
+ ...json.usage != null ? { usage: { raw: { [imageModelId]: json.usage } } } : {},
1604
1605
  response: json
1605
1606
  }
1606
1607
  };
@@ -1788,6 +1789,7 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
1788
1789
  ],
1789
1790
  details: {
1790
1791
  filePath: savedPath,
1792
+ ...json.usage != null ? { usage: { raw: { [imageModelId]: json.usage } } } : {},
1791
1793
  response: json
1792
1794
  }
1793
1795
  };
@@ -1804,6 +1806,310 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
1804
1806
  };
1805
1807
  }
1806
1808
 
1809
+ // ../../packages/runner-pi/dist/session-utils.js
1810
+ import { closeSync, fstatSync, openSync, readdirSync as readdirSync2, readSync, statSync as statSync2 } from "node:fs";
1811
+ import { join as join7 } from "node:path";
1812
+ import { SessionManager } from "@mariozechner/pi-coding-agent";
1813
+ var MAX_SESSION_FILE_BYTES = Number(process.env.SANDAGENT_MAX_SESSION_BYTES) || 10 * 1024 * 1024;
1814
+ function resolveSessionPathById(cwd, sessionId) {
1815
+ const tempMgr = SessionManager.create(cwd);
1816
+ const sessionsDir = tempMgr.getSessionDir();
1817
+ try {
1818
+ const suffix = `_${sessionId}.jsonl`;
1819
+ const match = readdirSync2(sessionsDir).find((f) => f.endsWith(suffix));
1820
+ return match ? join7(sessionsDir, match) : void 0;
1821
+ } catch {
1822
+ return void 0;
1823
+ }
1824
+ }
1825
+ function isSessionFileTooLarge(sessionPath2) {
1826
+ try {
1827
+ return statSync2(sessionPath2).size > MAX_SESSION_FILE_BYTES;
1828
+ } catch {
1829
+ return false;
1830
+ }
1831
+ }
1832
+ function readTailEntries(sessionPath2, tailBytes = 1024 * 1024) {
1833
+ let fd;
1834
+ try {
1835
+ fd = openSync(sessionPath2, "r");
1836
+ } catch {
1837
+ return [];
1838
+ }
1839
+ try {
1840
+ const fileSize = fstatSync(fd).size;
1841
+ const readStart = Math.max(0, fileSize - tailBytes);
1842
+ const readLen = fileSize - readStart;
1843
+ const buf = Buffer.alloc(readLen);
1844
+ readSync(fd, buf, 0, readLen, readStart);
1845
+ const tail = buf.toString("utf8");
1846
+ const entries = [];
1847
+ for (const line of tail.split("\n")) {
1848
+ const trimmed = line.trim();
1849
+ if (!trimmed)
1850
+ continue;
1851
+ try {
1852
+ entries.push(JSON.parse(trimmed));
1853
+ } catch {
1854
+ }
1855
+ }
1856
+ return entries;
1857
+ } finally {
1858
+ closeSync(fd);
1859
+ }
1860
+ }
1861
+ function extractSessionContext(sessionPath2) {
1862
+ const entries = readTailEntries(sessionPath2);
1863
+ if (entries.length === 0)
1864
+ return void 0;
1865
+ for (let i = entries.length - 1; i >= 0; i--) {
1866
+ const e = entries[i];
1867
+ if (e.type === "compaction" && typeof e.summary === "string") {
1868
+ return e.summary;
1869
+ }
1870
+ }
1871
+ const recentMessages = [];
1872
+ const MAX_MESSAGES = 6;
1873
+ for (let i = entries.length - 1; i >= 0 && recentMessages.length < MAX_MESSAGES; i--) {
1874
+ const e = entries[i];
1875
+ if (e.type !== "message")
1876
+ continue;
1877
+ const msg = e.message;
1878
+ if (!msg)
1879
+ continue;
1880
+ if (msg.role !== "user" && msg.role !== "assistant")
1881
+ continue;
1882
+ let text = "";
1883
+ if (typeof msg.content === "string") {
1884
+ text = msg.content;
1885
+ } else if (Array.isArray(msg.content)) {
1886
+ text = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
1887
+ }
1888
+ if (text) {
1889
+ recentMessages.unshift(`[${msg.role}]: ${text}`);
1890
+ }
1891
+ }
1892
+ if (recentMessages.length === 0)
1893
+ return void 0;
1894
+ return "## Previous Session Context (auto-extracted)\n\nThe following is the tail of the previous conversation:\n\n" + recentMessages.join("\n\n");
1895
+ }
1896
+
1897
+ // ../../packages/runner-pi/dist/usage-metadata.js
1898
+ function usageToMessageMetadata(usage) {
1899
+ return {
1900
+ input_tokens: usage.input,
1901
+ output_tokens: usage.output,
1902
+ cache_read_input_tokens: usage.cacheRead,
1903
+ cache_creation_input_tokens: usage.cacheWrite
1904
+ };
1905
+ }
1906
+ function accumulateToolUsage(tally, raw) {
1907
+ for (const [key, row] of Object.entries(raw)) {
1908
+ const existing = tally[key];
1909
+ if (existing) {
1910
+ for (const [field, val] of Object.entries(row)) {
1911
+ if (typeof val === "number")
1912
+ existing[field] = (existing[field] ?? 0) + val;
1913
+ }
1914
+ } else {
1915
+ const nums = {};
1916
+ for (const [field, val] of Object.entries(row)) {
1917
+ if (typeof val === "number")
1918
+ nums[field] = val;
1919
+ }
1920
+ tally[key] = nums;
1921
+ }
1922
+ }
1923
+ }
1924
+ function getUsageFromAgentEndMessages(messages) {
1925
+ for (let i = messages.length - 1; i >= 0; i--) {
1926
+ const m = messages[i];
1927
+ if (m.role === "assistant" && m.usage != null)
1928
+ return m.usage;
1929
+ }
1930
+ return void 0;
1931
+ }
1932
+
1933
+ // ../../packages/runner-pi/dist/stream-converter.js
1934
+ function emitStreamError(errorText) {
1935
+ const errorLine = "data: " + JSON.stringify({ type: "error", errorText }) + "\n\n";
1936
+ const finishLine = "data: " + JSON.stringify({ type: "finish", finishReason: "error" }) + "\n\n";
1937
+ return [errorLine, finishLine, "data: [DONE]\n\n"];
1938
+ }
1939
+ function extractToolResultText(result) {
1940
+ if (result !== null && typeof result === "object") {
1941
+ const r = result;
1942
+ if (Array.isArray(r.content) && r.content.length > 0) {
1943
+ const text = r.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
1944
+ if (text.length > 0)
1945
+ return text;
1946
+ }
1947
+ }
1948
+ if (typeof result === "string")
1949
+ return result;
1950
+ try {
1951
+ return JSON.stringify(result);
1952
+ } catch {
1953
+ return String(result);
1954
+ }
1955
+ }
1956
+ function sseData(obj) {
1957
+ return "data: " + JSON.stringify(obj) + "\n\n";
1958
+ }
1959
+ var PiAISDKStreamConverter = class {
1960
+ constructor(options) {
1961
+ this.options = options;
1962
+ this.messageId = "msg_" + Date.now() + "_" + Math.random().toString(36).slice(2);
1963
+ this.toolUsageTally = {};
1964
+ this.activeTextPartId = null;
1965
+ this.hasStarted = false;
1966
+ this.hasFinished = false;
1967
+ }
1968
+ get finished() {
1969
+ return this.hasFinished;
1970
+ }
1971
+ forceError(errorText) {
1972
+ if (this.hasFinished)
1973
+ return [];
1974
+ return [...this.ensureStart(), ...this.finishError(errorText)];
1975
+ }
1976
+ handleEvent(event, aborted) {
1977
+ if (this.hasFinished)
1978
+ return [];
1979
+ const chunks = [...this.ensureStart()];
1980
+ if (event.type === "message_start") {
1981
+ const msg = event.message;
1982
+ if (msg?.role === "assistant")
1983
+ chunks.push(...this.endTextStreamIfOpen());
1984
+ return chunks;
1985
+ }
1986
+ if (event.type === "message_end")
1987
+ return chunks;
1988
+ if (event.type === "message_update") {
1989
+ const sub = event.assistantMessageEvent;
1990
+ if (sub.type === "text_start")
1991
+ chunks.push(...this.endTextStreamIfOpen(), ...this.openTextStream());
1992
+ else if (sub.type === "text_delta")
1993
+ chunks.push(...this.emitTextDelta(sub.delta));
1994
+ else if (sub.type === "toolcall_start")
1995
+ chunks.push(...this.endTextStreamIfOpen());
1996
+ return chunks;
1997
+ }
1998
+ if (event.type === "tool_execution_start") {
1999
+ chunks.push(...this.endTextStreamIfOpen());
2000
+ chunks.push(sseData({
2001
+ type: "tool-input-start",
2002
+ toolCallId: event.toolCallId,
2003
+ toolName: event.toolName,
2004
+ dynamic: true,
2005
+ providerExecuted: true
2006
+ }), sseData({
2007
+ type: "tool-input-available",
2008
+ toolCallId: event.toolCallId,
2009
+ toolName: event.toolName,
2010
+ input: event.args,
2011
+ dynamic: true,
2012
+ providerExecuted: true
2013
+ }));
2014
+ return chunks;
2015
+ }
2016
+ if (event.type === "tool_execution_end") {
2017
+ const output = this.options.redactText(this.options.normalizeToolOutput(event.result));
2018
+ const raw = event.result?.details?.usage?.raw;
2019
+ if (raw != null)
2020
+ accumulateToolUsage(this.toolUsageTally, raw);
2021
+ chunks.push(sseData({
2022
+ type: "tool-output-available",
2023
+ toolCallId: event.toolCallId,
2024
+ output,
2025
+ isError: event.isError,
2026
+ dynamic: true,
2027
+ providerExecuted: true
2028
+ }));
2029
+ return chunks;
2030
+ }
2031
+ if (event.type === "agent_end") {
2032
+ if (aborted) {
2033
+ chunks.push(...this.finishError("Run aborted by signal."));
2034
+ } else {
2035
+ const errorMsg = this.options.getErrorFromAgentEndMessages(event.messages);
2036
+ if (errorMsg)
2037
+ chunks.push(...this.finishError(errorMsg));
2038
+ else
2039
+ chunks.push(...this.finishSuccess(this.options.getUsageFromAgentEndMessages(event.messages)));
2040
+ }
2041
+ return chunks;
2042
+ }
2043
+ return chunks;
2044
+ }
2045
+ ensureStart() {
2046
+ if (this.hasStarted)
2047
+ return [];
2048
+ this.hasStarted = true;
2049
+ return [
2050
+ sseData({ type: "start", messageId: this.messageId }),
2051
+ sseData({
2052
+ type: "message-metadata",
2053
+ messageMetadata: { sessionId: this.options.sessionId }
2054
+ })
2055
+ ];
2056
+ }
2057
+ newTextPartId() {
2058
+ return "text_" + Date.now() + "_" + Math.random().toString(36).slice(2) + "_" + Math.random().toString(36).slice(2);
2059
+ }
2060
+ openTextStream() {
2061
+ this.activeTextPartId = this.newTextPartId();
2062
+ return [sseData({ type: "text-start", id: this.activeTextPartId })];
2063
+ }
2064
+ emitTextDelta(rawDelta) {
2065
+ const delta = rawDelta ? this.options.redactText(rawDelta) : void 0;
2066
+ if (!delta)
2067
+ return [];
2068
+ const startChunk = this.activeTextPartId == null ? this.openTextStream() : [];
2069
+ return [
2070
+ ...startChunk,
2071
+ sseData({ type: "text-delta", id: this.activeTextPartId, delta })
2072
+ ];
2073
+ }
2074
+ endTextStreamIfOpen() {
2075
+ if (this.activeTextPartId == null)
2076
+ return [];
2077
+ const id = this.activeTextPartId;
2078
+ this.activeTextPartId = null;
2079
+ return [sseData({ type: "text-end", id })];
2080
+ }
2081
+ finishSuccess(usage) {
2082
+ const chunks = [...this.endTextStreamIfOpen()];
2083
+ const raw = {};
2084
+ let chatUsage;
2085
+ if (usage) {
2086
+ const { id } = this.options.model;
2087
+ chatUsage = {
2088
+ type: "chat",
2089
+ ...usageToMessageMetadata(usage)
2090
+ };
2091
+ raw[id] = chatUsage;
2092
+ }
2093
+ for (const [key, tally] of Object.entries(this.toolUsageTally)) {
2094
+ raw[key] = { ...tally };
2095
+ }
2096
+ const finishPayload = {
2097
+ type: "finish",
2098
+ finishReason: "stop"
2099
+ };
2100
+ if (usage) {
2101
+ finishPayload.messageMetadata = { usage: { ...chatUsage, raw } };
2102
+ }
2103
+ chunks.push(sseData(finishPayload), "data: [DONE]\n\n");
2104
+ this.hasFinished = true;
2105
+ return chunks;
2106
+ }
2107
+ finishError(errorText) {
2108
+ this.hasFinished = true;
2109
+ return emitStreamError(errorText);
2110
+ }
2111
+ };
2112
+
1807
2113
  // ../../packages/runner-pi/dist/tool-overrides.js
1808
2114
  import { createBashTool, createReadTool } from "@mariozechner/pi-coding-agent";
1809
2115
 
@@ -1848,7 +2154,7 @@ ${body}`);
1848
2154
  });
1849
2155
  }
1850
2156
  }
1851
- return results;
2157
+ return { results };
1852
2158
  }
1853
2159
  };
1854
2160
  var tavilyProvider = {
@@ -1883,7 +2189,7 @@ ${body}`);
1883
2189
  });
1884
2190
  }
1885
2191
  }
1886
- return results;
2192
+ return { results };
1887
2193
  }
1888
2194
  };
1889
2195
  var AUTO_DETECT_ORDER = [braveProvider, tavilyProvider];
@@ -2030,7 +2336,7 @@ function buildWebSearchTool(env) {
2030
2336
  let lastError;
2031
2337
  for (const { provider, apiKey } of providers) {
2032
2338
  try {
2033
- const results = await provider.search({
2339
+ const { results } = await provider.search({
2034
2340
  apiKey,
2035
2341
  query,
2036
2342
  count,
@@ -2038,11 +2344,21 @@ function buildWebSearchTool(env) {
2038
2344
  freshness,
2039
2345
  signal
2040
2346
  });
2347
+ let fetchedPages = 0;
2041
2348
  if (shouldFetchContent) {
2042
2349
  for (const r of results) {
2043
2350
  r.content = await fetchPageContent(r.link, signal);
2351
+ fetchedPages += 1;
2044
2352
  }
2045
2353
  }
2354
+ const usage = {
2355
+ raw: {
2356
+ [provider.id]: {
2357
+ requests: 1,
2358
+ fetchedPages
2359
+ }
2360
+ }
2361
+ };
2046
2362
  return {
2047
2363
  content: [
2048
2364
  {
@@ -2050,7 +2366,9 @@ function buildWebSearchTool(env) {
2050
2366
  text: formatSearchResults(results, provider.label)
2051
2367
  }
2052
2368
  ],
2053
- details: void 0
2369
+ details: {
2370
+ usage
2371
+ }
2054
2372
  };
2055
2373
  } catch (e) {
2056
2374
  lastError = e;
@@ -2233,52 +2551,6 @@ function applyModelOverrides(model, provider, optionsEnv) {
2233
2551
  model.baseUrl = anthropicBaseUrl;
2234
2552
  }
2235
2553
  }
2236
- function emitStreamError(errorText) {
2237
- return [
2238
- `data: ${JSON.stringify({ type: "error", errorText })}
2239
-
2240
- `,
2241
- `data: ${JSON.stringify({ type: "finish", finishReason: "error" })}
2242
-
2243
- `,
2244
- "data: [DONE]\n\n"
2245
- ];
2246
- }
2247
- function extractToolResultText(result) {
2248
- if (result !== null && typeof result === "object") {
2249
- const r = result;
2250
- if (Array.isArray(r.content) && r.content.length > 0) {
2251
- const text = r.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
2252
- if (text.length > 0) {
2253
- return text;
2254
- }
2255
- }
2256
- }
2257
- if (typeof result === "string")
2258
- return result;
2259
- try {
2260
- return JSON.stringify(result);
2261
- } catch {
2262
- return String(result);
2263
- }
2264
- }
2265
- function usageToMessageMetadata(usage) {
2266
- return {
2267
- input_tokens: usage.input,
2268
- output_tokens: usage.output,
2269
- cache_read_input_tokens: usage.cacheRead,
2270
- cache_creation_input_tokens: usage.cacheWrite
2271
- };
2272
- }
2273
- function getUsageFromAgentEndMessages(messages) {
2274
- for (let i = messages.length - 1; i >= 0; i--) {
2275
- const m = messages[i];
2276
- if (m.role === "assistant" && m.usage != null) {
2277
- return m.usage;
2278
- }
2279
- }
2280
- return void 0;
2281
- }
2282
2554
  function getErrorFromAgentEndMessages(messages) {
2283
2555
  for (let i = messages.length - 1; i >= 0; i--) {
2284
2556
  const m = messages[i];
@@ -2294,7 +2566,7 @@ function traceRawMessage(debugCwd, data, reset = false, optionsEnv) {
2294
2566
  if (!enabled)
2295
2567
  return;
2296
2568
  try {
2297
- const file = join7(debugCwd, "pi-message-stream-debug.json");
2569
+ const file = join8(debugCwd, "pi-message-stream-debug.json");
2298
2570
  if (reset && existsSync5(file))
2299
2571
  unlinkSync3(file);
2300
2572
  const type = data !== null && typeof data === "object" ? data.type : void 0;
@@ -2361,13 +2633,26 @@ function createPiRunner(options = {}) {
2361
2633
  const sessionManager = await (async () => {
2362
2634
  if (resume !== void 0 && resume !== "") {
2363
2635
  if (resume.includes("/")) {
2364
- return SessionManager.open(resume);
2636
+ return SessionManager2.open(resume);
2365
2637
  }
2366
- const sessions = await SessionManager.list(cwd);
2367
- const found = sessions.find((s) => s.id === resume);
2368
- return found ? SessionManager.open(found.path) : SessionManager.create(cwd);
2638
+ const sessionPath2 = resolveSessionPathById(cwd, resume);
2639
+ console.error(`${LOG_PREFIX2} resume: id=${resume} path=${sessionPath2 ?? "(not found)"}`);
2640
+ if (sessionPath2) {
2641
+ if (isSessionFileTooLarge(sessionPath2)) {
2642
+ const context = extractSessionContext(sessionPath2);
2643
+ console.error(`${LOG_PREFIX2} session file too large, starting fresh${context ? " (with context)" : ""}`);
2644
+ const newMgr = SessionManager2.create(cwd);
2645
+ if (context) {
2646
+ const firstId = newMgr.getEntries()[0]?.id ?? "";
2647
+ newMgr.appendCompaction(context, firstId, 0);
2648
+ }
2649
+ return newMgr;
2650
+ }
2651
+ return SessionManager2.open(sessionPath2);
2652
+ }
2653
+ return SessionManager2.create(cwd);
2369
2654
  }
2370
- return SessionManager.create(cwd);
2655
+ return SessionManager2.create(cwd);
2371
2656
  })();
2372
2657
  const resourceLoader = options.skillPaths ? new BunnyAgentResourceLoader({
2373
2658
  cwd,
@@ -2423,165 +2708,34 @@ function createPiRunner(options = {}) {
2423
2708
  }
2424
2709
  try {
2425
2710
  traceRawMessage(cwd, null, true, options.env);
2426
- let promptText = userInput;
2427
- let images;
2428
- try {
2429
- if (userInput.startsWith("[") && userInput.endsWith("]")) {
2430
- const parsed = JSON.parse(userInput);
2431
- if (Array.isArray(parsed)) {
2432
- promptText = parsed.filter((p) => p.type === "text").map((p) => p.text).join("\n");
2433
- const imageParts = parsed.filter((p) => p.type === "image");
2434
- if (imageParts.length > 0) {
2435
- images = imageParts.map((p) => ({
2436
- type: "image",
2437
- data: p.data,
2438
- mimeType: p.mimeType
2439
- }));
2440
- }
2711
+ const promptText = userInput;
2712
+ const promptPromise = session.prompt(promptText);
2713
+ const streamConverter = new PiAISDKStreamConverter({
2714
+ sessionId: session.sessionId,
2715
+ model,
2716
+ redactText: (value) => {
2717
+ if (options.env && Object.keys(options.env).length > 0) {
2718
+ return redactSecrets(value, options.env);
2441
2719
  }
2442
- }
2443
- } catch (_e) {
2444
- }
2445
- const promptPromise = session.prompt(promptText, images ? { images } : void 0);
2446
- const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
2447
- let hasStarted = false;
2448
- let hasFinished = false;
2449
- const imageToolUsage = { input_tokens: 0, output_tokens: 0 };
2450
- const newTextPartId = () => `text_${Date.now()}_${Math.random().toString(36).slice(2)}_${Math.random().toString(36).slice(2)}`;
2451
- let activeTextPartId = null;
2452
- let textStreamOpen = false;
2453
- const endTextStreamIfOpen = function* () {
2454
- if (textStreamOpen && activeTextPartId != null) {
2455
- yield `data: ${JSON.stringify({ type: "text-end", id: activeTextPartId })}
2456
-
2457
- `;
2458
- textStreamOpen = false;
2459
- activeTextPartId = null;
2460
- }
2461
- };
2462
- const beginTextStream = function* () {
2463
- activeTextPartId = newTextPartId();
2464
- yield `data: ${JSON.stringify({ type: "text-start", id: activeTextPartId })}
2465
-
2466
- `;
2467
- textStreamOpen = true;
2468
- };
2469
- const ensureStartEvent = async function* () {
2470
- if (!hasStarted) {
2471
- yield `data: ${JSON.stringify({ type: "start", messageId })}
2472
-
2473
- `;
2474
- yield `data: ${JSON.stringify({
2475
- type: "message-metadata",
2476
- messageMetadata: { sessionId: session.sessionId }
2477
- })}
2478
-
2479
- `;
2480
- hasStarted = true;
2481
- }
2482
- };
2483
- const finishSuccess = async function* (usage) {
2484
- yield* endTextStreamIfOpen();
2485
- const finishPayload = { type: "finish", finishReason: "stop" };
2486
- const hasImageUsage = imageToolUsage.input_tokens > 0 || imageToolUsage.output_tokens > 0;
2487
- if (usage != null || hasImageUsage) {
2488
- const base = usage != null ? usageToMessageMetadata(usage) : {};
2489
- finishPayload.messageMetadata = {
2490
- usage: {
2491
- ...base,
2492
- input_tokens: (base.input_tokens ?? 0) + imageToolUsage.input_tokens,
2493
- output_tokens: (base.output_tokens ?? 0) + imageToolUsage.output_tokens
2494
- }
2495
- };
2496
- }
2497
- yield `data: ${JSON.stringify(finishPayload)}
2498
-
2499
- `;
2500
- yield "data: [DONE]\n\n";
2501
- hasFinished = true;
2502
- };
2503
- const finishError = async function* (errorText) {
2504
- for (const chunk of emitStreamError(errorText)) {
2505
- yield chunk;
2506
- }
2507
- hasFinished = true;
2508
- };
2720
+ return value;
2721
+ },
2722
+ normalizeToolOutput: extractToolResultText,
2723
+ getUsageFromAgentEndMessages,
2724
+ getErrorFromAgentEndMessages
2725
+ });
2509
2726
  while (!isComplete || eventQueue.length > 0) {
2510
2727
  while (eventQueue.length > 0) {
2511
2728
  const event = eventQueue.shift();
2512
2729
  traceRawMessage(cwd, event, false, options.env);
2513
- yield* ensureStartEvent();
2514
- if (event.type === "message_start") {
2515
- const msg = event.message;
2516
- if (msg?.role === "assistant") {
2517
- yield* endTextStreamIfOpen();
2518
- }
2519
- } else if (event.type === "message_update") {
2520
- const sub = event.assistantMessageEvent;
2521
- if (sub.type === "text_start") {
2522
- yield* endTextStreamIfOpen();
2523
- yield* beginTextStream();
2524
- } else if (sub.type === "text_delta") {
2525
- let delta = sub.delta;
2526
- if (delta) {
2527
- if (options.env && Object.keys(options.env).length > 0) {
2528
- delta = redactSecrets(delta, options.env);
2529
- }
2530
- if (!textStreamOpen) {
2531
- yield* beginTextStream();
2532
- }
2533
- yield `data: ${JSON.stringify({
2534
- type: "text-delta",
2535
- id: activeTextPartId,
2536
- delta
2537
- })}
2538
-
2539
- `;
2540
- }
2541
- } else if (sub.type === "toolcall_start") {
2542
- yield* endTextStreamIfOpen();
2543
- }
2544
- } else if (event.type === "tool_execution_start") {
2545
- yield* endTextStreamIfOpen();
2546
- yield `data: ${JSON.stringify({ type: "tool-input-start", toolCallId: event.toolCallId, toolName: event.toolName, dynamic: true, providerExecuted: true })}
2547
-
2548
- `;
2549
- yield `data: ${JSON.stringify({ type: "tool-input-available", toolCallId: event.toolCallId, toolName: event.toolName, input: event.args, dynamic: true, providerExecuted: true })}
2550
-
2551
- `;
2552
- } else if (event.type === "tool_execution_end") {
2553
- let output = extractToolResultText(event.result);
2554
- if (options.env && Object.keys(options.env).length > 0) {
2555
- output = redactSecrets(output, options.env);
2556
- }
2557
- if ((event.toolName === "generate_image" || event.toolName === "edit_image") && event.result !== null && typeof event.result === "object") {
2558
- const details = event.result.details;
2559
- const u = details?.response?.usage;
2560
- if (u) {
2561
- imageToolUsage.input_tokens += u.input_tokens ?? 0;
2562
- imageToolUsage.output_tokens += u.output_tokens ?? 0;
2563
- }
2564
- }
2565
- yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: event.toolCallId, output, isError: event.isError, dynamic: true, providerExecuted: true })}
2566
-
2567
- `;
2568
- } else if (event.type === "agent_end") {
2569
- if (aborted) {
2570
- yield* finishError("Run aborted by signal.");
2571
- } else {
2572
- const errorMsg = getErrorFromAgentEndMessages(event.messages);
2573
- if (errorMsg) {
2574
- yield* finishError(errorMsg);
2575
- } else {
2576
- const usage = getUsageFromAgentEndMessages(event.messages);
2577
- yield* finishSuccess(usage);
2578
- }
2579
- }
2730
+ const chunks = streamConverter.handleEvent(event, aborted);
2731
+ for (const chunk of chunks) {
2732
+ yield chunk;
2580
2733
  }
2581
2734
  }
2582
- if (aborted && !hasFinished) {
2583
- yield* ensureStartEvent();
2584
- yield* finishError("Run aborted by signal.");
2735
+ if (aborted && !streamConverter.finished) {
2736
+ for (const chunk of streamConverter.forceError("Run aborted by signal.")) {
2737
+ yield chunk;
2738
+ }
2585
2739
  break;
2586
2740
  }
2587
2741
  if (!isComplete && eventQueue.length === 0) {
@@ -2590,22 +2744,24 @@ function createPiRunner(options = {}) {
2590
2744
  });
2591
2745
  }
2592
2746
  }
2593
- if (hasFinished) {
2747
+ if (streamConverter.finished) {
2594
2748
  return;
2595
2749
  }
2596
2750
  try {
2597
2751
  await promptPromise;
2598
2752
  } catch (error) {
2599
- if (!hasFinished) {
2600
- yield* ensureStartEvent();
2753
+ if (!streamConverter.finished) {
2601
2754
  const message = error instanceof Error ? error.message : "Pi agent run failed.";
2602
- yield* finishError(message);
2755
+ for (const chunk of streamConverter.forceError(message)) {
2756
+ yield chunk;
2757
+ }
2603
2758
  }
2604
2759
  return;
2605
2760
  }
2606
- if (!hasFinished && session.agent.state.error) {
2607
- yield* ensureStartEvent();
2608
- yield* finishError(session.agent.state.error);
2761
+ if (!streamConverter.finished && session.agent.state.error) {
2762
+ for (const chunk of streamConverter.forceError(session.agent.state.error)) {
2763
+ yield chunk;
2764
+ }
2609
2765
  }
2610
2766
  } finally {
2611
2767
  if (abortSignal) {
@@ -2625,11 +2781,11 @@ function createPiRunner(options = {}) {
2625
2781
 
2626
2782
  // ../../packages/runner-harness/dist/session.js
2627
2783
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
2628
- import { join as join8 } from "node:path";
2784
+ import { join as join9 } from "node:path";
2629
2785
  var DIR = ".bunny-agent";
2630
2786
  var FILE = "session-id";
2631
2787
  function sessionPath(cwd) {
2632
- return join8(cwd, DIR, FILE);
2788
+ return join9(cwd, DIR, FILE);
2633
2789
  }
2634
2790
  function readSessionId(cwd) {
2635
2791
  try {
@@ -2643,28 +2799,28 @@ function readSessionId(cwd) {
2643
2799
  }
2644
2800
  function writeSessionId(cwd, id) {
2645
2801
  try {
2646
- mkdirSync3(join8(cwd, DIR), { recursive: true });
2802
+ mkdirSync3(join9(cwd, DIR), { recursive: true });
2647
2803
  writeFileSync4(sessionPath(cwd), id, "utf8");
2648
2804
  } catch {
2649
2805
  }
2650
2806
  }
2651
2807
 
2652
2808
  // ../../packages/runner-harness/dist/skills.js
2653
- import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
2809
+ import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
2654
2810
  import { homedir as homedir2 } from "node:os";
2655
- import { join as join9 } from "node:path";
2811
+ import { join as join10 } from "node:path";
2656
2812
  function discoverSkillPaths(cwd) {
2657
2813
  const paths = [];
2658
2814
  for (const base of [
2659
- join9(cwd, "skills"),
2660
- join9(homedir2(), ".bunny-agent", "skills")
2815
+ join10(cwd, "skills"),
2816
+ join10(homedir2(), ".bunny-agent", "skills")
2661
2817
  ]) {
2662
2818
  if (!existsSync7(base))
2663
2819
  continue;
2664
2820
  try {
2665
- for (const entry of readdirSync2(base)) {
2666
- const full = join9(base, entry);
2667
- if (statSync2(full).isDirectory() && existsSync7(join9(full, "SKILL.md"))) {
2821
+ for (const entry of readdirSync3(base)) {
2822
+ const full = join10(base, entry);
2823
+ if (statSync3(full).isDirectory() && existsSync7(join10(full, "SKILL.md"))) {
2668
2824
  paths.push(full);
2669
2825
  }
2670
2826
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunny-agent/runner-cli",
3
- "version": "0.9.29-beta.5",
3
+ "version": "0.9.29-beta.7",
4
4
  "description": "BunnyAgent 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": {
@@ -54,11 +54,11 @@
54
54
  "typescript": "^5.3.0",
55
55
  "vitest": "^1.6.1",
56
56
  "@bunny-agent/runner-harness": "0.1.1-beta.0",
57
- "@bunny-agent/runner-codex": "0.6.2",
58
57
  "@bunny-agent/runner-claude": "0.6.2",
59
- "@bunny-agent/runner-gemini": "0.6.2",
58
+ "@bunny-agent/runner-codex": "0.6.2",
60
59
  "@bunny-agent/runner-opencode": "0.6.2",
61
- "@bunny-agent/runner-pi": "0.6.4-beta.0"
60
+ "@bunny-agent/runner-pi": "0.6.4-beta.0",
61
+ "@bunny-agent/runner-gemini": "0.6.2"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "tsc && pnpm bundle",