@bunny-agent/runner-cli 0.9.51-release.1 → 0.9.52-beta.1

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 +196 -57
  2. package/package.json +11 -10
package/dist/bundle.mjs CHANGED
@@ -700,7 +700,11 @@ function createSDKOptions(options) {
700
700
  ],
701
701
  cwd: options.cwd,
702
702
  env: options.env,
703
- resume: options.resume,
703
+ // forkFrom snapshot-clones the source session into a new session id;
704
+ // plain resume continues the same session.
705
+ resume: options.forkFrom ?? options.resume,
706
+ forkSession: options.forkFrom != null ? true : void 0,
707
+ ...options.skills ? { skills: options.skills } : {},
704
708
  settingSources: ["project", "user"],
705
709
  canUseTool: createCanUseToolCallback(options),
706
710
  permissionMode: isRoot || options.yolo ? "bypassPermissions" : "default",
@@ -894,18 +898,33 @@ function toToolEndPayload(event) {
894
898
  }
895
899
  return null;
896
900
  }
897
- function toAssistantText(event) {
898
- if (event.type === "item.completed" && event.item.type === "agent_message") {
899
- return event.item.text;
900
- }
901
- if (event.type === "item.completed" && event.item.type === "reasoning") {
902
- return `[Reasoning] ${event.item.text}`;
901
+ function sseData(obj) {
902
+ return `data: ${JSON.stringify(obj)}
903
+
904
+ `;
905
+ }
906
+ var ItemTextTracker = class {
907
+ emittedLength = /* @__PURE__ */ new Map();
908
+ started = /* @__PURE__ */ new Set();
909
+ /** Returns the new delta for this item, or empty string if nothing new. */
910
+ delta(itemId, fullText) {
911
+ const emitted = this.emittedLength.get(itemId) ?? 0;
912
+ if (fullText.length <= emitted)
913
+ return "";
914
+ this.emittedLength.set(itemId, fullText.length);
915
+ return fullText.slice(emitted);
916
+ }
917
+ /** Returns true the first time an item id is seen (caller emits text-start). */
918
+ markStarted(itemId) {
919
+ if (this.started.has(itemId))
920
+ return false;
921
+ this.started.add(itemId);
922
+ return true;
903
923
  }
904
- if (event.type === "item.completed" && event.item.type === "error") {
905
- return `[Error] ${event.item.message}`;
924
+ isStarted(itemId) {
925
+ return this.started.has(itemId);
906
926
  }
907
- return null;
908
- }
927
+ };
909
928
  function createCodexRunner(options) {
910
929
  const codex = new Codex({
911
930
  apiKey: process.env.CODEX_API_KEY || process.env.OPENAI_API_KEY,
@@ -954,62 +973,170 @@ function createCodexRunner(options) {
954
973
  }
955
974
  } catch (_e) {
956
975
  }
976
+ if (options.systemPrompt && !options.resume) {
977
+ if (typeof inputToCodex === "string") {
978
+ inputToCodex = `${options.systemPrompt}
979
+
980
+ ${inputToCodex}`;
981
+ } else {
982
+ inputToCodex = [
983
+ { type: "text", text: options.systemPrompt },
984
+ ...inputToCodex
985
+ ];
986
+ }
987
+ }
957
988
  const streamedTurn = await thread.runStreamed(inputToCodex, {
958
989
  signal: options.abortController?.signal
959
990
  });
991
+ let sessionId = options.resume ?? void 0;
992
+ let startEmitted = false;
993
+ let finished = false;
994
+ const textTracker = new ItemTextTracker();
995
+ const reasoningTracker = new ItemTextTracker();
996
+ const ensureStart = () => {
997
+ if (startEmitted)
998
+ return [];
999
+ startEmitted = true;
1000
+ return [
1001
+ sseData({ type: "start" }),
1002
+ sseData({
1003
+ type: "message-metadata",
1004
+ messageMetadata: { sessionId: sessionId ?? thread.id ?? void 0 }
1005
+ })
1006
+ ];
1007
+ };
960
1008
  for await (const event of streamedTurn.events) {
961
- const assistantText = toAssistantText(event);
962
- if (assistantText) {
963
- yield `data: ${JSON.stringify({ type: "text-delta", delta: assistantText })}
964
-
965
- `;
1009
+ if (event.type === "thread.started") {
1010
+ sessionId = event.thread_id;
1011
+ yield* ensureStart();
1012
+ continue;
1013
+ }
1014
+ yield* ensureStart();
1015
+ if ((event.type === "item.started" || event.type === "item.updated" || event.type === "item.completed") && event.item.type === "agent_message") {
1016
+ const item = event.item;
1017
+ if (textTracker.markStarted(item.id)) {
1018
+ yield sseData({ type: "text-start", id: item.id });
1019
+ }
1020
+ const delta = textTracker.delta(item.id, item.text);
1021
+ if (delta) {
1022
+ yield sseData({ type: "text-delta", id: item.id, delta });
1023
+ }
1024
+ if (event.type === "item.completed") {
1025
+ yield sseData({ type: "text-end", id: item.id });
1026
+ }
1027
+ continue;
1028
+ }
1029
+ if ((event.type === "item.updated" || event.type === "item.completed") && event.item.type === "reasoning") {
1030
+ const delta = reasoningTracker.delta(event.item.id, event.item.text);
1031
+ if (delta) {
1032
+ yield sseData({ type: "reasoning", text: delta });
1033
+ }
1034
+ continue;
1035
+ }
1036
+ if (event.type === "item.completed" && event.item.type === "error") {
1037
+ const id = `error-item-${event.item.id}`;
1038
+ yield sseData({ type: "text-start", id });
1039
+ yield sseData({
1040
+ type: "text-delta",
1041
+ id,
1042
+ delta: `[Error] ${event.item.message}`
1043
+ });
1044
+ yield sseData({ type: "text-end", id });
1045
+ continue;
1046
+ }
1047
+ if (event.type === "item.completed" && event.item.type === "file_change") {
1048
+ const item = event.item;
1049
+ yield sseData({
1050
+ type: "tool-input-start",
1051
+ toolCallId: item.id,
1052
+ toolName: "apply_patch"
1053
+ });
1054
+ yield sseData({
1055
+ type: "tool-input-available",
1056
+ toolCallId: item.id,
1057
+ toolName: "apply_patch",
1058
+ input: { changes: item.changes }
1059
+ });
1060
+ yield sseData({
1061
+ type: "tool-output-available",
1062
+ toolCallId: item.id,
1063
+ output: { status: item.status, changes: item.changes },
1064
+ isError: item.status === "failed"
1065
+ });
1066
+ continue;
966
1067
  }
967
1068
  const toolStart = toToolStartPayload(event);
968
1069
  if (toolStart) {
969
- yield `data: ${JSON.stringify({ type: "tool-input-start", toolCallId: toolStart.toolCallId, toolName: toolStart.toolName })}
970
-
971
- `;
972
- yield `data: ${JSON.stringify({ type: "tool-input-available", toolCallId: toolStart.toolCallId, toolName: toolStart.toolName, input: toolStart.args })}
973
-
974
- `;
1070
+ yield sseData({
1071
+ type: "tool-input-start",
1072
+ toolCallId: toolStart.toolCallId,
1073
+ toolName: toolStart.toolName
1074
+ });
1075
+ yield sseData({
1076
+ type: "tool-input-available",
1077
+ toolCallId: toolStart.toolCallId,
1078
+ toolName: toolStart.toolName,
1079
+ input: toolStart.args
1080
+ });
975
1081
  }
976
1082
  const toolEnd = toToolEndPayload(event);
977
1083
  if (toolEnd) {
978
- yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: toolEnd.toolCallId, output: toolEnd.result, isError: toolEnd.isError })}
979
-
980
- `;
1084
+ yield sseData({
1085
+ type: "tool-output-available",
1086
+ toolCallId: toolEnd.toolCallId,
1087
+ output: toolEnd.result,
1088
+ isError: toolEnd.isError
1089
+ });
981
1090
  }
982
1091
  if (event.type === "turn.completed") {
983
- yield `data: ${JSON.stringify({ type: "finish", finishReason: "stop", usage: event.usage })}
984
-
985
- `;
1092
+ yield sseData({
1093
+ type: "finish",
1094
+ finishReason: "stop",
1095
+ messageMetadata: {
1096
+ sessionId: sessionId ?? thread.id ?? void 0,
1097
+ usage: {
1098
+ input_tokens: event.usage.input_tokens,
1099
+ output_tokens: event.usage.output_tokens,
1100
+ cache_read_input_tokens: event.usage.cached_input_tokens
1101
+ }
1102
+ }
1103
+ });
986
1104
  yield `data: [DONE]
987
1105
 
988
1106
  `;
1107
+ finished = true;
989
1108
  }
990
1109
  if (event.type === "turn.failed") {
991
- yield `data: ${JSON.stringify({ type: "error", errorText: event.error.message })}
992
-
993
- `;
994
- yield `data: ${JSON.stringify({ type: "finish", finishReason: "error" })}
995
-
996
- `;
1110
+ yield sseData({ type: "error", errorText: event.error.message });
1111
+ yield sseData({ type: "finish", finishReason: "error" });
997
1112
  yield `data: [DONE]
998
1113
 
999
1114
  `;
1115
+ finished = true;
1000
1116
  }
1001
1117
  if (event.type === "error") {
1002
- yield `data: ${JSON.stringify({ type: "error", errorText: stringifyUnknown(event.message) })}
1003
-
1004
- `;
1005
- yield `data: ${JSON.stringify({ type: "finish", finishReason: "error" })}
1006
-
1007
- `;
1118
+ yield sseData({
1119
+ type: "error",
1120
+ errorText: stringifyUnknown(event.message)
1121
+ });
1122
+ yield sseData({ type: "finish", finishReason: "error" });
1008
1123
  yield `data: [DONE]
1009
1124
 
1010
1125
  `;
1126
+ finished = true;
1011
1127
  }
1012
1128
  }
1129
+ if (!finished) {
1130
+ yield* ensureStart();
1131
+ yield sseData({
1132
+ type: "error",
1133
+ errorText: "Codex stream ended unexpectedly before completing."
1134
+ });
1135
+ yield sseData({ type: "finish", finishReason: "error" });
1136
+ yield `data: [DONE]
1137
+
1138
+ `;
1139
+ }
1013
1140
  for (const tmpFile of tempFiles) {
1014
1141
  try {
1015
1142
  fs.unlinkSync(tmpFile);
@@ -1303,7 +1430,7 @@ function createOpenCodeRunner(options = {}) {
1303
1430
  // ../../packages/runner-pi/dist/pi-runner.js
1304
1431
  import { appendFileSync as appendFileSync2, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "node:fs";
1305
1432
  import { join as join8 } from "node:path";
1306
- import { getModel } from "@earendil-works/pi-ai";
1433
+ import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
1307
1434
  import { AuthStorage, createAgentSession, ModelRegistry, SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
1308
1435
 
1309
1436
  // ../../packages/runner-pi/dist/bunny-agent-resource-loader.js
@@ -2077,7 +2204,7 @@ function extractToolResultText(result) {
2077
2204
  return String(result);
2078
2205
  }
2079
2206
  }
2080
- function sseData(obj) {
2207
+ function sseData2(obj) {
2081
2208
  return "data: " + JSON.stringify(obj) + "\n\n";
2082
2209
  }
2083
2210
  var PiAISDKStreamConverter = class {
@@ -2121,13 +2248,13 @@ var PiAISDKStreamConverter = class {
2121
2248
  }
2122
2249
  if (event.type === "tool_execution_start") {
2123
2250
  chunks.push(...this.endTextStreamIfOpen());
2124
- chunks.push(sseData({
2251
+ chunks.push(sseData2({
2125
2252
  type: "tool-input-start",
2126
2253
  toolCallId: event.toolCallId,
2127
2254
  toolName: event.toolName,
2128
2255
  dynamic: true,
2129
2256
  providerExecuted: true
2130
- }), sseData({
2257
+ }), sseData2({
2131
2258
  type: "tool-input-available",
2132
2259
  toolCallId: event.toolCallId,
2133
2260
  toolName: event.toolName,
@@ -2143,7 +2270,7 @@ var PiAISDKStreamConverter = class {
2143
2270
  if (raw != null)
2144
2271
  accumulateToolUsage(this.toolUsageTally, raw);
2145
2272
  if (event.isError) {
2146
- chunks.push(sseData({
2273
+ chunks.push(sseData2({
2147
2274
  type: "tool-output-error",
2148
2275
  toolCallId: event.toolCallId,
2149
2276
  errorText: output,
@@ -2151,7 +2278,7 @@ var PiAISDKStreamConverter = class {
2151
2278
  providerExecuted: true
2152
2279
  }));
2153
2280
  } else {
2154
- chunks.push(sseData({
2281
+ chunks.push(sseData2({
2155
2282
  type: "tool-output-available",
2156
2283
  toolCallId: event.toolCallId,
2157
2284
  output,
@@ -2180,8 +2307,8 @@ var PiAISDKStreamConverter = class {
2180
2307
  return [];
2181
2308
  this.hasStarted = true;
2182
2309
  return [
2183
- sseData({ type: "start", messageId: this.messageId }),
2184
- sseData({
2310
+ sseData2({ type: "start", messageId: this.messageId }),
2311
+ sseData2({
2185
2312
  type: "message-metadata",
2186
2313
  messageMetadata: { sessionId: this.options.sessionId }
2187
2314
  })
@@ -2192,7 +2319,7 @@ var PiAISDKStreamConverter = class {
2192
2319
  }
2193
2320
  openTextStream() {
2194
2321
  this.activeTextPartId = this.newTextPartId();
2195
- return [sseData({ type: "text-start", id: this.activeTextPartId })];
2322
+ return [sseData2({ type: "text-start", id: this.activeTextPartId })];
2196
2323
  }
2197
2324
  emitTextDelta(rawDelta) {
2198
2325
  if (!rawDelta)
@@ -2200,7 +2327,7 @@ var PiAISDKStreamConverter = class {
2200
2327
  const startChunk = this.activeTextPartId == null ? this.openTextStream() : [];
2201
2328
  return [
2202
2329
  ...startChunk,
2203
- sseData({
2330
+ sseData2({
2204
2331
  type: "text-delta",
2205
2332
  id: this.activeTextPartId,
2206
2333
  delta: rawDelta
@@ -2212,7 +2339,7 @@ var PiAISDKStreamConverter = class {
2212
2339
  return [];
2213
2340
  const id = this.activeTextPartId;
2214
2341
  this.activeTextPartId = null;
2215
- return [sseData({ type: "text-end", id })];
2342
+ return [sseData2({ type: "text-end", id })];
2216
2343
  }
2217
2344
  finishSuccess(usage) {
2218
2345
  const chunks = [...this.endTextStreamIfOpen()];
@@ -2236,7 +2363,7 @@ var PiAISDKStreamConverter = class {
2236
2363
  if (usage) {
2237
2364
  finishPayload.messageMetadata = { usage: { ...chatUsage, raw } };
2238
2365
  }
2239
- chunks.push(sseData(finishPayload), "data: [DONE]\n\n");
2366
+ chunks.push(sseData2(finishPayload), "data: [DONE]\n\n");
2240
2367
  this.hasFinished = true;
2241
2368
  return chunks;
2242
2369
  }
@@ -5501,7 +5628,7 @@ function createPiRunner(options = {}) {
5501
5628
  const apiKeyEnvKey = `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
5502
5629
  const inlineApiKey = typeof options.env?.[apiKeyEnvKey] === "string" && options.env[apiKeyEnvKey].length > 0 ? options.env[apiKeyEnvKey] : void 0;
5503
5630
  const modelRegistry = ModelRegistry.inMemory(AuthStorage.create());
5504
- const defaultModel = getModel(provider, modelName);
5631
+ const defaultModel = getBuiltinModel(provider, modelName);
5505
5632
  let model = defaultModel ?? modelRegistry.find(provider, modelName);
5506
5633
  if (model == null) {
5507
5634
  const baseUrlEnvKey = `${provider.toUpperCase().replace(/-/g, "_")}_BASE_URL`;
@@ -5572,7 +5699,7 @@ function createPiRunner(options = {}) {
5572
5699
  return SessionManager2.create(cwd);
5573
5700
  })();
5574
5701
  stripLLMThoughtSignaturesFromSessionManager(sessionManager, model);
5575
- const resourceLoader = options.skillPaths ? new BunnyAgentResourceLoader({
5702
+ const resourceLoader = options.skillPaths || options.systemPrompt ? new BunnyAgentResourceLoader({
5576
5703
  cwd,
5577
5704
  skillPaths: options.skillPaths,
5578
5705
  appendSystemPrompt: options.systemPrompt
@@ -5790,10 +5917,21 @@ function dispatchRunner(runner, base, cwd, options) {
5790
5917
  const _env = base.env;
5791
5918
  switch (runner) {
5792
5919
  case "claude":
5793
- return createClaudeRunner(base).run(options.userInput);
5920
+ return createClaudeRunner({
5921
+ ...base,
5922
+ cwd,
5923
+ forkFrom: options.forkFrom
5924
+ }).run(options.userInput);
5794
5925
  case "codex": {
5795
5926
  const { outputFormat: _of, ...codexBase } = base;
5796
- return createCodexRunner({ ...codexBase, cwd }).run(options.userInput);
5927
+ const codexEfforts = ["minimal", "low", "medium", "high", "xhigh"];
5928
+ return createCodexRunner({
5929
+ ...codexBase,
5930
+ cwd,
5931
+ ...options.effort && codexEfforts.includes(options.effort) ? {
5932
+ modelReasoningEffort: options.effort
5933
+ } : {}
5934
+ }).run(options.userInput);
5797
5935
  }
5798
5936
  case "gemini":
5799
5937
  return createGeminiRunner({
@@ -5839,7 +5977,8 @@ async function* captureSessionId(stream, cwd) {
5839
5977
  writeSessionId(cwd, sessionId);
5840
5978
  }
5841
5979
  }
5842
- } catch {
5980
+ } catch (error) {
5981
+ console.debug("[captureSessionId] failed to parse chunk", error);
5843
5982
  }
5844
5983
  }
5845
5984
  yield chunk;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunny-agent/runner-cli",
3
- "version": "0.9.51-release.1",
3
+ "version": "0.9.52-beta.1",
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": {
@@ -40,11 +40,12 @@
40
40
  "anthropic"
41
41
  ],
42
42
  "dependencies": {
43
- "@anthropic-ai/claude-agent-sdk": "0.2.70",
44
- "@earendil-works/pi-agent-core": "0.78.0",
45
- "@earendil-works/pi-ai": "0.78.0",
46
- "@earendil-works/pi-coding-agent": "0.78.0",
47
- "@openai/codex-sdk": "^0.110.0",
43
+ "@anthropic-ai/claude-agent-sdk": "0.3.211",
44
+ "@anthropic-ai/sdk": "0.111.0",
45
+ "@earendil-works/pi-agent-core": "0.80.7",
46
+ "@earendil-works/pi-ai": "0.80.7",
47
+ "@earendil-works/pi-coding-agent": "0.80.7",
48
+ "@openai/codex-sdk": "^0.144.5",
48
49
  "dotenv": "^17.3.1",
49
50
  "zod": "^4.0.0"
50
51
  },
@@ -53,12 +54,12 @@
53
54
  "esbuild": "^0.27.2",
54
55
  "typescript": "^5.3.0",
55
56
  "vitest": "^1.6.1",
56
- "@bunny-agent/runner-harness": "0.1.1-beta.0",
57
57
  "@bunny-agent/runner-claude": "0.6.2",
58
- "@bunny-agent/runner-opencode": "0.6.2",
58
+ "@bunny-agent/runner-codex": "0.6.2",
59
59
  "@bunny-agent/runner-gemini": "0.6.2",
60
- "@bunny-agent/runner-pi": "0.6.4-beta.0",
61
- "@bunny-agent/runner-codex": "0.6.2"
60
+ "@bunny-agent/runner-opencode": "0.6.2",
61
+ "@bunny-agent/runner-harness": "0.1.1-beta.0",
62
+ "@bunny-agent/runner-pi": "0.6.4-beta.0"
62
63
  },
63
64
  "scripts": {
64
65
  "build": "tsc && pnpm bundle",