@bunny-agent/runner-cli 0.10.0-release.6 → 0.10.1-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 +1060 -175
  2. package/package.json +6 -6
package/dist/bundle.mjs CHANGED
@@ -6,12 +6,12 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/cli.ts
9
- import { resolve as resolve4 } from "node:path";
9
+ import { resolve as resolve5 } from "node:path";
10
10
  import { config as config3 } from "dotenv";
11
11
  import { parseArgs } from "node:util";
12
12
 
13
13
  // ../../packages/server-acp/dist/acp-agent.js
14
- import { randomUUID } from "node:crypto";
14
+ import { randomUUID as randomUUID3 } from "node:crypto";
15
15
 
16
16
  // ../../node_modules/.pnpm/@agentclientprotocol+sdk@1.2.1_zod@4.6.7/node_modules/@agentclientprotocol/sdk/dist/schema/index.js
17
17
  var AGENT_METHODS = {
@@ -5132,7 +5132,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
5132
5132
  var NONE = 0;
5133
5133
  var ASSUMED = 1;
5134
5134
  var PROVEN = 2;
5135
- function isRecursive(inst, stack, resolve5) {
5135
+ function isRecursive(inst, stack, resolve6) {
5136
5136
  const cached3 = recursive.get(inst);
5137
5137
  if (cached3 !== void 0)
5138
5138
  return cached3 ? PROVEN : NONE;
@@ -5142,7 +5142,7 @@ function isRecursive(inst, stack, resolve5) {
5142
5142
  let result = NONE;
5143
5143
  const check3 = (child) => {
5144
5144
  if (result !== PROVEN && child?._zod) {
5145
- const answer = isRecursive(child, stack, resolve5);
5145
+ const answer = isRecursive(child, stack, resolve6);
5146
5146
  if (answer > result)
5147
5147
  result = answer;
5148
5148
  }
@@ -5153,7 +5153,7 @@ function isRecursive(inst, stack, resolve5) {
5153
5153
  const desc = Object.getOwnPropertyDescriptor(sh, key);
5154
5154
  if (spread && !desc.enumerable)
5155
5155
  continue;
5156
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
5156
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve6) : NONE;
5157
5157
  if (child > answer)
5158
5158
  answer = child;
5159
5159
  }
@@ -5217,7 +5217,7 @@ function isRecursive(inst, stack, resolve5) {
5217
5217
  break;
5218
5218
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
5219
5219
  case "lazy": {
5220
- const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
5220
+ const inner = def._cachedInner ?? (resolve6 ? inst._zod.innerType : void 0);
5221
5221
  merge3(inner ? isRecursive(inner, stack, false) : ASSUMED);
5222
5222
  break;
5223
5223
  }
@@ -21764,12 +21764,12 @@ var Connection = class {
21764
21764
  const id = this.nextRequestId++;
21765
21765
  let cancel = () => {
21766
21766
  };
21767
- const responsePromise = new Promise((resolve5, reject) => {
21767
+ const responsePromise = new Promise((resolve6, reject) => {
21768
21768
  const pendingResponse = {
21769
21769
  resolve: (response) => {
21770
21770
  try {
21771
21771
  const value = mapResponse ? mapResponse(response) : response;
21772
- resolve5(value);
21772
+ resolve6(value);
21773
21773
  } catch (error109) {
21774
21774
  reject(error109);
21775
21775
  }
@@ -21847,8 +21847,8 @@ var Connection = class {
21847
21847
  initialize(stream, handlers) {
21848
21848
  this.stream = stream;
21849
21849
  this.staticHandlers = handlers;
21850
- this.closedPromise = new Promise((resolve5) => {
21851
- this.abortController.signal.addEventListener("abort", () => resolve5());
21850
+ this.closedPromise = new Promise((resolve6) => {
21851
+ this.abortController.signal.addEventListener("abort", () => resolve6());
21852
21852
  });
21853
21853
  void this.receive();
21854
21854
  }
@@ -22636,8 +22636,8 @@ var AsyncQueue = class {
22636
22636
  if (this.failed) {
22637
22637
  return Promise.reject(this.failure);
22638
22638
  }
22639
- return new Promise((resolve5, reject) => {
22640
- this.waiters.push({ resolve: resolve5, reject });
22639
+ return new Promise((resolve6, reject) => {
22640
+ this.waiters.push({ resolve: resolve6, reject });
22641
22641
  });
22642
22642
  }
22643
22643
  };
@@ -23929,6 +23929,12 @@ var AISDKStreamConverter = class {
23929
23929
  if (message.type === "stream_event") {
23930
23930
  const streamEvent = message;
23931
23931
  const event = streamEvent.event;
23932
+ const parentToolCallId = message.parent_tool_use_id;
23933
+ if (!parentToolCallId && event.type === "message_start") {
23934
+ yield this.emit({ type: "start-step", id: event.message.id });
23935
+ } else if (!parentToolCallId && event.type === "message_stop") {
23936
+ yield this.emit({ type: "finish-step" });
23937
+ }
23932
23938
  if (event.type === "message_start" && !this.hasEmittedStart) {
23933
23939
  this.hasEmittedStart = true;
23934
23940
  yield this.emit({ type: "start", messageId: event.message.id });
@@ -23959,7 +23965,8 @@ var AISDKStreamConverter = class {
23959
23965
  toolName: tool.name,
23960
23966
  input: tool.input,
23961
23967
  dynamic: true,
23962
- providerExecuted: true
23968
+ providerExecuted: true,
23969
+ parentToolCallId: message.parent_tool_use_id
23963
23970
  });
23964
23971
  }
23965
23972
  }
@@ -23973,7 +23980,8 @@ var AISDKStreamConverter = class {
23973
23980
  toolCallId: part.tool_use_id,
23974
23981
  output: message.tool_use_result || part.content,
23975
23982
  dynamic: true,
23976
- providerExecuted: true
23983
+ providerExecuted: true,
23984
+ parentToolCallId: message.parent_tool_use_id
23977
23985
  });
23978
23986
  }
23979
23987
  }
@@ -38037,6 +38045,21 @@ function createCanUseToolCallback(claudeOptions) {
38037
38045
  updatedInput: input2
38038
38046
  };
38039
38047
  }
38048
+ if (claudeOptions.toolInteraction) {
38049
+ const decision = await claudeOptions.toolInteraction({
38050
+ toolCallId: toolUseID,
38051
+ toolName,
38052
+ input: input2,
38053
+ signal: options.signal,
38054
+ ...toolName === "AskUserQuestion" ? {
38055
+ timeoutMs: claudeOptions.askUserQuestionTimeoutMs ?? DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS
38056
+ } : {}
38057
+ });
38058
+ return decision.behavior === "allow" ? {
38059
+ behavior: "allow",
38060
+ updatedInput: decision.updatedInput ?? input2
38061
+ } : decision;
38062
+ }
38040
38063
  const cwd = claudeOptions.cwd || process.cwd();
38041
38064
  try {
38042
38065
  const fs3 = await import("node:fs");
@@ -38086,7 +38109,7 @@ function createCanUseToolCallback(claudeOptions) {
38086
38109
  }
38087
38110
  } catch {
38088
38111
  }
38089
- await new Promise((resolve5) => setTimeout(resolve5, 500));
38112
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
38090
38113
  }
38091
38114
  try {
38092
38115
  fs3.unlinkSync(approvalFile);
@@ -38279,7 +38302,7 @@ Documentation: https://platform.claude.com/docs/en/agent-sdk/typescript-v2-previ
38279
38302
  id: textId,
38280
38303
  delta: word + " "
38281
38304
  });
38282
- await new Promise((resolve5) => setTimeout(resolve5, 20));
38305
+ await new Promise((resolve6) => setTimeout(resolve6, 20));
38283
38306
  }
38284
38307
  yield formatDataStream({ type: "text-end", id: textId });
38285
38308
  yield formatDataStream({
@@ -38709,6 +38732,9 @@ ${inputToCodex}`;
38709
38732
  continue;
38710
38733
  }
38711
38734
  yield* ensureStart();
38735
+ if (event.type === "turn.started") {
38736
+ yield sseData({ type: "start-step" });
38737
+ }
38712
38738
  if ((event.type === "item.started" || event.type === "item.updated" || event.type === "item.completed") && event.item.type === "agent_message") {
38713
38739
  const item = event.item;
38714
38740
  if (textTracker.markStarted(item.id)) {
@@ -38786,6 +38812,7 @@ ${inputToCodex}`;
38786
38812
  });
38787
38813
  }
38788
38814
  if (event.type === "turn.completed") {
38815
+ yield sseData({ type: "finish-step" });
38789
38816
  yield sseData({
38790
38817
  type: "finish",
38791
38818
  finishReason: "stop",
@@ -38804,6 +38831,7 @@ ${inputToCodex}`;
38804
38831
  finished = true;
38805
38832
  }
38806
38833
  if (event.type === "turn.failed") {
38834
+ yield sseData({ type: "finish-step" });
38807
38835
  yield sseData({ type: "error", errorText: event.error.message });
38808
38836
  yield sseData({ type: "finish", finishReason: "error" });
38809
38837
  yield `data: [DONE]
@@ -38845,6 +38873,7 @@ ${inputToCodex}`;
38845
38873
  }
38846
38874
 
38847
38875
  // ../../packages/runner-copilot/dist/copilot-runner.js
38876
+ import { randomUUID } from "node:crypto";
38848
38877
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
38849
38878
  import { join as join6 } from "node:path";
38850
38879
  import { approveAll, CopilotClient } from "@github/copilot-sdk";
@@ -38889,8 +38918,8 @@ var AsyncEventQueue = class {
38889
38918
  throw this.error;
38890
38919
  if (this.ended)
38891
38920
  return { value: void 0, done: true };
38892
- return new Promise((resolve5, reject) => {
38893
- this.waiters.push({ resolve: resolve5, reject });
38921
+ return new Promise((resolve6, reject) => {
38922
+ this.waiters.push({ resolve: resolve6, reject });
38894
38923
  });
38895
38924
  }
38896
38925
  };
@@ -38961,7 +38990,7 @@ function createFilePermissionHandler(cwd) {
38961
38990
  }
38962
38991
  } catch {
38963
38992
  }
38964
- await new Promise((resolve5) => setTimeout(resolve5, 500));
38993
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
38965
38994
  }
38966
38995
  return { kind: "reject", feedback: "Approval timed out." };
38967
38996
  } finally {
@@ -39009,7 +39038,46 @@ function createCopilotRunner(options) {
39009
39038
  enableConfigDiscovery: true,
39010
39039
  availableTools: options.allowedTools,
39011
39040
  systemMessage: options.systemPrompt ? { mode: "append", content: options.systemPrompt } : void 0,
39012
- onPermissionRequest: options.yolo ? approveAll : createFilePermissionHandler(cwd)
39041
+ onPermissionRequest: options.yolo ? approveAll : options.toolInteraction ? async (request) => {
39042
+ const decision = await options.toolInteraction({
39043
+ toolCallId: request.toolCallId ?? randomUUID(),
39044
+ toolName: permissionToolName(request),
39045
+ input: request,
39046
+ signal: options.abortController?.signal
39047
+ });
39048
+ return decision.behavior === "allow" ? { kind: "approved" } : { kind: "reject", feedback: decision.message };
39049
+ } : createFilePermissionHandler(cwd),
39050
+ ...options.toolInteraction ? {
39051
+ onUserInputRequest: async (request) => {
39052
+ const decision = await options.toolInteraction({
39053
+ toolCallId: randomUUID(),
39054
+ toolName: "ask_user_question",
39055
+ input: {
39056
+ questions: [
39057
+ {
39058
+ question: request.question,
39059
+ allowFreeform: request.allowFreeform,
39060
+ ...request.choices ? {
39061
+ options: request.choices.map((label) => ({
39062
+ label
39063
+ }))
39064
+ } : {}
39065
+ }
39066
+ ]
39067
+ },
39068
+ signal: options.abortController?.signal
39069
+ });
39070
+ if (decision.behavior === "deny")
39071
+ throw new Error(decision.message);
39072
+ const answer = decision.updatedInput?.answers?.[request.question];
39073
+ if (typeof answer !== "string")
39074
+ throw new Error("The client did not supply an answer.");
39075
+ return {
39076
+ answer,
39077
+ wasFreeform: !request.choices?.includes(answer)
39078
+ };
39079
+ }
39080
+ } : {}
39013
39081
  };
39014
39082
  session = options.resume ? await client2.resumeSession(options.resume, sessionConfig) : await client2.createSession(sessionConfig);
39015
39083
  activeSession = session;
@@ -39042,6 +39110,14 @@ function createCopilotRunner(options) {
39042
39110
  const turn = session.sendAndWait(userInput).then(() => events.close()).catch((error109) => events.fail(error109));
39043
39111
  try {
39044
39112
  for await (const event of events) {
39113
+ if (!event.agentId && event.type === "assistant.turn_start") {
39114
+ yield sseData2({ type: "start-step", id: event.data.turnId });
39115
+ continue;
39116
+ }
39117
+ if (!event.agentId && event.type === "assistant.turn_end") {
39118
+ yield sseData2({ type: "finish-step" });
39119
+ continue;
39120
+ }
39045
39121
  if (event.type === "assistant.message_delta") {
39046
39122
  const { messageId, deltaContent } = event.data;
39047
39123
  if (!openText.has(messageId)) {
@@ -39264,8 +39340,8 @@ var AsyncQueue2 = class {
39264
39340
  if (this.ended) {
39265
39341
  return { value: void 0, done: true };
39266
39342
  }
39267
- return new Promise((resolve5, reject) => {
39268
- this.waiters.push({ resolve: resolve5, reject });
39343
+ return new Promise((resolve6, reject) => {
39344
+ this.waiters.push({ resolve: resolve6, reject });
39269
39345
  });
39270
39346
  }
39271
39347
  };
@@ -39293,9 +39369,10 @@ function usageMetadata(usage) {
39293
39369
  return {
39294
39370
  input_tokens: usage.inputTokens,
39295
39371
  output_tokens: usage.outputTokens,
39296
- cache_read_input_tokens: usage.cachedReadTokens ?? 0,
39297
- cache_creation_input_tokens: usage.cachedWriteTokens ?? 0,
39298
- reasoning_tokens: usage.thoughtTokens ?? 0
39372
+ total_tokens: usage.totalTokens,
39373
+ ...typeof usage.cachedReadTokens === "number" ? { cache_read_input_tokens: usage.cachedReadTokens } : {},
39374
+ ...typeof usage.cachedWriteTokens === "number" ? { cache_creation_input_tokens: usage.cachedWriteTokens } : {},
39375
+ ...typeof usage.thoughtTokens === "number" ? { reasoning_tokens: usage.thoughtTokens } : {}
39299
39376
  };
39300
39377
  }
39301
39378
  async function waitForFileApproval(cwd, request) {
@@ -39323,7 +39400,7 @@ async function waitForFileApproval(cwd, request) {
39323
39400
  return false;
39324
39401
  } catch {
39325
39402
  }
39326
- await new Promise((resolve5) => setTimeout(resolve5, 500));
39403
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
39327
39404
  }
39328
39405
  return false;
39329
39406
  } finally {
@@ -39388,6 +39465,7 @@ function createAcpProcessRunner(options) {
39388
39465
  startedText.clear();
39389
39466
  };
39390
39467
  const handleUpdate = (update) => {
39468
+ output2.push(sseData3({ type: "data-acp-session-update", data: update }));
39391
39469
  if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
39392
39470
  const id = update.messageId ?? "acp-message";
39393
39471
  if (!startedText.has(id)) {
@@ -39456,13 +39534,34 @@ function createAcpProcessRunner(options) {
39456
39534
  finishReason: aborted3 ? "error" : finishReason(stopReason),
39457
39535
  messageMetadata: {
39458
39536
  sessionId,
39537
+ acpStopReason: stopReason,
39459
39538
  usage: usageMetadata(usage)
39460
39539
  }
39461
39540
  }));
39462
39541
  output2.push("data: [DONE]\n\n");
39463
39542
  output2.close();
39464
39543
  };
39465
- const app = client({ name: "bunny-agent" }).onRequest(methods.client.session.requestPermission, ({ params }) => resolvePermission(cwd, options.yolo ?? false, params)).onNotification(methods.client.session.update, ({ params }) => {
39544
+ const app = client({ name: "bunny-agent" }).onRequest(methods.client.session.requestPermission, async ({ params }) => {
39545
+ if (!options.toolInteraction || options.yolo) {
39546
+ return resolvePermission(cwd, options.yolo ?? false, params);
39547
+ }
39548
+ const decision = await options.toolInteraction({
39549
+ toolCallId: params.toolCall.toolCallId,
39550
+ toolName: params.toolCall.title ?? "tool",
39551
+ input: params.toolCall.rawInput,
39552
+ permission: {
39553
+ toolCall: params.toolCall,
39554
+ options: params.options,
39555
+ ...params._meta ? { _meta: params._meta } : {}
39556
+ },
39557
+ signal: options.abortController?.signal
39558
+ });
39559
+ const wanted = decision.behavior === "allow" ? "allow_once" : "reject_once";
39560
+ const selected = params.options.find((option) => decision.optionId ? option.optionId === decision.optionId && (decision.behavior === "allow" ? ["allow_once", "allow_always"].includes(option.kind) : ["reject_once", "reject_always"].includes(option.kind)) : option.kind === wanted);
39561
+ return {
39562
+ outcome: selected ? { outcome: "selected", optionId: selected.optionId } : { outcome: "cancelled" }
39563
+ };
39564
+ }).onNotification(methods.client.session.update, ({ params }) => {
39466
39565
  if (acceptingLoadedUpdates && params.sessionId === loadedSessionId) {
39467
39566
  handleUpdate(params.update);
39468
39567
  }
@@ -39564,7 +39663,8 @@ function createGeminiRunner(options = {}) {
39564
39663
  abortController: options.abortController,
39565
39664
  systemPrompt: options.systemPrompt,
39566
39665
  resume: options.resume,
39567
- yolo: options.yolo
39666
+ yolo: options.yolo,
39667
+ toolInteraction: options.toolInteraction
39568
39668
  });
39569
39669
  }
39570
39670
 
@@ -39582,7 +39682,8 @@ function createOpenCodeRunner(options = {}) {
39582
39682
  abortController: options.abortController,
39583
39683
  systemPrompt: options.systemPrompt,
39584
39684
  resume: options.resume,
39585
- yolo: options.yolo
39685
+ yolo: options.yolo,
39686
+ toolInteraction: options.toolInteraction
39586
39687
  });
39587
39688
  }
39588
39689
 
@@ -41045,7 +41146,7 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
41045
41146
  parameters: editImageSchema,
41046
41147
  async execute(_toolCallId, params, signal, _onUpdate) {
41047
41148
  const { readFileSync: readFileSync8, existsSync: existsSync12 } = await import("node:fs");
41048
- const { resolve: resolve5, basename: basename3 } = await import("node:path");
41149
+ const { resolve: resolve6, basename: basename3 } = await import("node:path");
41049
41150
  const p = params;
41050
41151
  const imagePath = p.image;
41051
41152
  const prompt = p.prompt;
@@ -41056,7 +41157,7 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
41056
41157
  const imageSize = p.imageSize;
41057
41158
  const rawFilename = p.filename;
41058
41159
  const safePrompt = buildPolicySafeEditPrompt(prompt);
41059
- const resolvedImage = resolve5(cwd, imagePath);
41160
+ const resolvedImage = resolve6(cwd, imagePath);
41060
41161
  if (!existsSync12(resolvedImage)) {
41061
41162
  return {
41062
41163
  content: [
@@ -41100,7 +41201,7 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
41100
41201
  }
41101
41202
  ];
41102
41203
  if (maskPath) {
41103
- const resolvedMask = resolve5(cwd, maskPath);
41204
+ const resolvedMask = resolve6(cwd, maskPath);
41104
41205
  if (existsSync12(resolvedMask)) {
41105
41206
  files.push({
41106
41207
  name: "mask",
@@ -41759,6 +41860,7 @@ var PiAISDKStreamConverter = class {
41759
41860
  this.emittedSources = /* @__PURE__ */ new Set();
41760
41861
  this.lastEndedContentIndex = -1;
41761
41862
  this.textInputEnded = false;
41863
+ this.stepNumber = 0;
41762
41864
  this.registry = options.sourceRegistry ?? new SourceRegistry();
41763
41865
  }
41764
41866
  /** Ingest only against an active text part in its already selected mode. */
@@ -41781,6 +41883,10 @@ var PiAISDKStreamConverter = class {
41781
41883
  if (event.type === "message_start") {
41782
41884
  const msg = event.message;
41783
41885
  if (msg?.role === "assistant") {
41886
+ chunks.push(sseData4({
41887
+ type: "start-step",
41888
+ id: `${this.messageId}:step:${++this.stepNumber}`
41889
+ }));
41784
41890
  chunks.push(...this.endTextStreamIfOpen());
41785
41891
  this.currentContentIndex = void 0;
41786
41892
  this.lastEndedContentIndex = -1;
@@ -41788,6 +41894,12 @@ var PiAISDKStreamConverter = class {
41788
41894
  return chunks;
41789
41895
  }
41790
41896
  if (event.type === "message_end") {
41897
+ if (event.message.role === "assistant") {
41898
+ chunks.push(sseData4({
41899
+ type: "finish-step",
41900
+ id: `${this.messageId}:step:${this.stepNumber}`
41901
+ }));
41902
+ }
41791
41903
  if (this.options.searchContractVersion === 1 && event.message.role === "assistant") {
41792
41904
  chunks.push(...this.endTextStreamIfOpen(aborted3 || ["length", "aborted", "error"].includes(event.message.stopReason)));
41793
41905
  }
@@ -44663,6 +44775,25 @@ function approvalFilePath(cwd, toolCallId) {
44663
44775
  return path2.join(cwd, ".bunny-agent", "approvals", approvalFileName(toolCallId));
44664
44776
  }
44665
44777
  async function waitForApproval(options) {
44778
+ if (options.toolInteraction) {
44779
+ const decision = await options.toolInteraction({
44780
+ toolCallId: options.toolCallId,
44781
+ toolName: options.toolName,
44782
+ input: options.input,
44783
+ signal: options.signal,
44784
+ timeoutMs: options.timeoutMs
44785
+ });
44786
+ if (decision.behavior === "deny")
44787
+ return decision;
44788
+ const updatedInput = decision.updatedInput ?? {};
44789
+ return {
44790
+ behavior: "allow",
44791
+ updatedInput: {
44792
+ questions: updatedInput.questions ?? options.input?.questions,
44793
+ answers: updatedInput.answers ?? {}
44794
+ }
44795
+ };
44796
+ }
44666
44797
  const { cwd, toolCallId, toolName, input: input2, signal, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, timeoutMs } = options;
44667
44798
  const approvalFile = approvalFilePath(cwd, toolCallId);
44668
44799
  const cleanup = () => {
@@ -44730,18 +44861,18 @@ async function waitForApproval(options) {
44730
44861
  }
44731
44862
  }
44732
44863
  function sleepWithAbort(ms, signal) {
44733
- return new Promise((resolve5) => {
44864
+ return new Promise((resolve6) => {
44734
44865
  if (signal?.aborted) {
44735
- resolve5();
44866
+ resolve6();
44736
44867
  return;
44737
44868
  }
44738
44869
  const timer = setTimeout(() => {
44739
44870
  signal?.removeEventListener("abort", onAbort);
44740
- resolve5();
44871
+ resolve6();
44741
44872
  }, ms);
44742
44873
  const onAbort = () => {
44743
44874
  clearTimeout(timer);
44744
- resolve5();
44875
+ resolve6();
44745
44876
  };
44746
44877
  signal?.addEventListener("abort", onAbort, { once: true });
44747
44878
  });
@@ -44813,7 +44944,8 @@ function buildAskUserQuestionTool(options) {
44813
44944
  input: params,
44814
44945
  signal: signal ?? options.fallbackSignal,
44815
44946
  pollIntervalMs: options.pollIntervalMs,
44816
- timeoutMs: options.timeoutMs ?? DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS2
44947
+ timeoutMs: options.timeoutMs ?? DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS2,
44948
+ toolInteraction: options.toolInteraction
44817
44949
  });
44818
44950
  if (decision.behavior === "deny") {
44819
44951
  throw new Error(decision.message);
@@ -44846,7 +44978,8 @@ function createToolApprovalExtension(options) {
44846
44978
  input: event.input,
44847
44979
  signal: ctx.signal ?? options.fallbackSignal,
44848
44980
  pollIntervalMs: options.pollIntervalMs,
44849
- timeoutMs: options.timeoutMs
44981
+ timeoutMs: options.timeoutMs,
44982
+ toolInteraction: options.toolInteraction
44850
44983
  });
44851
44984
  if (decision.behavior === "deny") {
44852
44985
  return {
@@ -45878,10 +46011,11 @@ function createPiRunner(options = {}) {
45878
46011
  const allowedTools = normalizeAllowedTools(options.allowedTools);
45879
46012
  const toolRefDefinitions = options.toolRefs && options.toolRefs.length > 0 ? buildToolDefinitionsFromRefs(options.toolRefs) : [];
45880
46013
  const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
45881
- const bypassApproval = Boolean(options.yolo) || isRoot;
46014
+ const bypassApproval = Boolean(options.yolo) || isRoot && !options.toolInteraction;
45882
46015
  const approvalGate = {
45883
46016
  cwd,
45884
- fallbackSignal: options.abortController?.signal
46017
+ fallbackSignal: options.abortController?.signal,
46018
+ toolInteraction: options.toolInteraction
45885
46019
  };
45886
46020
  const askUserQuestionEnabled = allowedTools === void 0 || allowedTools.includes(ASK_USER_QUESTION_TOOL_NAME);
45887
46021
  const previousModel = sessionManager.buildSessionContext?.().model;
@@ -46007,8 +46141,8 @@ function createPiRunner(options = {}) {
46007
46141
  break;
46008
46142
  }
46009
46143
  if (!promptSettled && !aborted3 && eventQueue.length === 0) {
46010
- await new Promise((resolve5) => {
46011
- wakeConsumer = resolve5;
46144
+ await new Promise((resolve6) => {
46145
+ wakeConsumer = resolve6;
46012
46146
  });
46013
46147
  }
46014
46148
  }
@@ -46168,7 +46302,8 @@ function createRunner(options) {
46168
46302
  yolo: options.yolo,
46169
46303
  env,
46170
46304
  abortController,
46171
- askUserQuestionTimeoutMs: options.askUserQuestionTimeoutMs
46305
+ askUserQuestionTimeoutMs: options.askUserQuestionTimeoutMs,
46306
+ toolInteraction: options.toolInteraction
46172
46307
  };
46173
46308
  const input2 = options.input ? parseAgentTurnInputV1(options.input) : createLegacyAgentTurnInput(options.userInput ?? "");
46174
46309
  const rawStream = dispatchRunner(options.runner, base, cwd, options, input2);
@@ -46232,7 +46367,8 @@ function dispatchRunner(runner, base, cwd, options, input2) {
46232
46367
  abortController: base.abortController,
46233
46368
  systemPrompt: base.systemPrompt,
46234
46369
  resume: base.resume,
46235
- yolo: base.yolo
46370
+ yolo: base.yolo,
46371
+ toolInteraction: base.toolInteraction
46236
46372
  }).run(compiled.text);
46237
46373
  case "pi": {
46238
46374
  return createPiRunner({
@@ -46260,7 +46396,8 @@ function dispatchRunner(runner, base, cwd, options, input2) {
46260
46396
  abortController: base.abortController,
46261
46397
  systemPrompt: base.systemPrompt,
46262
46398
  resume: base.resume,
46263
- yolo: base.yolo
46399
+ yolo: base.yolo,
46400
+ toolInteraction: base.toolInteraction
46264
46401
  }).run(compiled.text);
46265
46402
  case "copilot": {
46266
46403
  const reasoningEfforts = ["low", "medium", "high", "xhigh"];
@@ -46274,6 +46411,7 @@ function dispatchRunner(runner, base, cwd, options, input2) {
46274
46411
  resume: base.resume,
46275
46412
  yolo: base.yolo,
46276
46413
  cwd,
46414
+ toolInteraction: base.toolInteraction,
46277
46415
  env: base.env,
46278
46416
  abortController: base.abortController,
46279
46417
  ...options.effort && reasoningEfforts.includes(options.effort) ? {
@@ -46356,21 +46494,403 @@ import { execFile } from "node:child_process";
46356
46494
  import { promisify } from "node:util";
46357
46495
  var execFileAsync = promisify(execFile);
46358
46496
 
46497
+ // ../../packages/server-acp/dist/tool-display.js
46498
+ import { isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
46499
+ var recordOf = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
46500
+ function toolKind(name) {
46501
+ const key = name.toLowerCase().replace(/[\s_-]/g, "");
46502
+ if (/^(read|readfile|list|ls|find|glob)/.test(key))
46503
+ return "read";
46504
+ if (/^(grep|search|websearch|ripgrep)/.test(key))
46505
+ return "search";
46506
+ if (/^(write|edit|multiedit|applypatch|replace|notebookedit)/.test(key))
46507
+ return "edit";
46508
+ if (/^(delete|remove|unlink)/.test(key))
46509
+ return "delete";
46510
+ if (/^(move|rename)/.test(key))
46511
+ return "move";
46512
+ if (/^(bash|shell|execute|exec|terminal|runcommand)/.test(key))
46513
+ return "execute";
46514
+ if (/^(fetch|webfetch|browse)/.test(key))
46515
+ return "fetch";
46516
+ if (/^(todo|plan|updateplan|task|agent)/.test(key))
46517
+ return "think";
46518
+ return "other";
46519
+ }
46520
+ function toolDisplay(name, input2, cwd) {
46521
+ const value = recordOf(input2) ?? {};
46522
+ const kind = toolKind(name);
46523
+ const path3 = [value.file_path, value.path, value.filePath].find((candidate) => typeof candidate === "string" && candidate.length > 0);
46524
+ const location = path3 && (isAbsolute2(path3) ? path3 : cwd ? resolve3(cwd, path3) : void 0);
46525
+ const detail = path3 ?? [
46526
+ value.command,
46527
+ value.query,
46528
+ value.pattern,
46529
+ value.url,
46530
+ value.description
46531
+ ].find((candidate) => typeof candidate === "string" && candidate.length > 0);
46532
+ const title = detail ? `${name}: ${detail.slice(0, 160)}` : name;
46533
+ const oldText = value.old_string ?? value.oldText;
46534
+ const newText = value.new_string ?? value.newText ?? value.content;
46535
+ const content = kind === "edit" && path3 && typeof newText === "string" ? [
46536
+ {
46537
+ type: "diff",
46538
+ path: location ?? path3,
46539
+ ...typeof oldText === "string" ? { oldText } : {},
46540
+ newText
46541
+ }
46542
+ ] : void 0;
46543
+ return {
46544
+ title,
46545
+ kind,
46546
+ ...location ? { locations: [{ path: location }] } : {},
46547
+ ...content ? { content } : {}
46548
+ };
46549
+ }
46550
+ function contentBlock(value) {
46551
+ const item = recordOf(value);
46552
+ if (!item)
46553
+ return void 0;
46554
+ if (item.type === "text" && typeof item.text === "string")
46555
+ return item;
46556
+ if ((item.type === "image" || item.type === "audio") && typeof item.data === "string" && typeof item.mimeType === "string")
46557
+ return item;
46558
+ if (item.type === "resource_link" && typeof item.uri === "string" && typeof item.name === "string") {
46559
+ return item;
46560
+ }
46561
+ if (item.type === "resource" && recordOf(item.resource))
46562
+ return item;
46563
+ return void 0;
46564
+ }
46565
+ function toolContents(output2) {
46566
+ const value = recordOf(output2);
46567
+ const candidates = Array.isArray(output2) ? output2 : Array.isArray(value?.content) ? value.content : [];
46568
+ const contents = candidates.flatMap((candidate) => {
46569
+ const item = recordOf(candidate);
46570
+ if (!item)
46571
+ return [];
46572
+ if (item.type === "diff" && typeof item.path === "string" && typeof item.newText === "string") {
46573
+ return [item];
46574
+ }
46575
+ if (item.type === "terminal" && typeof item.terminalId === "string") {
46576
+ return [item];
46577
+ }
46578
+ if (item.type === "content" && contentBlock(item.content))
46579
+ return [item];
46580
+ const block = contentBlock(item);
46581
+ return block ? [{ type: "content", content: block }] : [];
46582
+ });
46583
+ if (contents.length > 0)
46584
+ return contents;
46585
+ const text = typeof output2 === "string" ? output2 : JSON.stringify(output2 ?? null);
46586
+ return [{ type: "content", content: { type: "text", text } }];
46587
+ }
46588
+ function planEntries(input2) {
46589
+ const value = recordOf(input2);
46590
+ const entries = value?.todos ?? value?.plan;
46591
+ if (!Array.isArray(entries))
46592
+ return void 0;
46593
+ return entries.flatMap((entry) => {
46594
+ const item = recordOf(entry);
46595
+ if (!item)
46596
+ return [];
46597
+ const description = item.content ?? item.step ?? item.subject;
46598
+ if (typeof description !== "string")
46599
+ return [];
46600
+ const status = item.status === "completed" ? "completed" : item.status === "in_progress" ? "in_progress" : "pending";
46601
+ return [
46602
+ {
46603
+ content: status === "in_progress" && typeof item.activeForm === "string" ? item.activeForm : description,
46604
+ status,
46605
+ priority: item.priority === "high" || item.priority === "low" ? item.priority : "medium"
46606
+ }
46607
+ ];
46608
+ });
46609
+ }
46610
+
46611
+ // ../../packages/server-acp/dist/interaction-bridge.js
46612
+ var BUNNY_INTERACTION_RESPOND_METHOD = "_bunny-agent/interaction/respond";
46613
+ var INTERACTIVE_RUNNERS = [
46614
+ "claude",
46615
+ "pi",
46616
+ "gemini",
46617
+ "opencode",
46618
+ "copilot"
46619
+ ];
46620
+ function createAcpToolInteraction(input2) {
46621
+ return async (request) => {
46622
+ if (request.signal?.aborted)
46623
+ return { behavior: "deny", message: "The run was cancelled." };
46624
+ const isQuestion = /^(ask_user_question|AskUserQuestion)$/.test(request.toolName);
46625
+ const questions = isQuestion ? parseQuestions(request.input) : void 0;
46626
+ if (isQuestion && !input2.supportsForm) {
46627
+ return {
46628
+ behavior: "deny",
46629
+ message: "The connected ACP client does not support structured questions."
46630
+ };
46631
+ }
46632
+ const form = questions ? buildQuestionForm(questions) : void 0;
46633
+ const options = request.permission?.options ?? [
46634
+ { optionId: "allow-once", name: "Allow once", kind: "allow_once" },
46635
+ { optionId: "reject-once", name: "Reject", kind: "reject_once" }
46636
+ ];
46637
+ const interaction = input2.registry.create(input2.sessionId, (reply) => {
46638
+ if (!["accept", "decline", "cancel"].includes(reply.action))
46639
+ throw new Error("Invalid interaction action.");
46640
+ if (form && reply.action === "accept")
46641
+ form.answers(reply.content ?? {});
46642
+ if (!form && reply.action !== "cancel" && !options.some((option) => option.optionId === reply.optionId)) {
46643
+ throw new Error("A permission reply must select a reported permission option.");
46644
+ }
46645
+ });
46646
+ const controller = new AbortController();
46647
+ const cancel = () => {
46648
+ void input2.registry.respond(input2.sessionId, interaction.id, { action: "cancel" }).catch(() => void 0);
46649
+ controller.abort();
46650
+ };
46651
+ request.signal?.addEventListener("abort", cancel, { once: true });
46652
+ if (request.signal?.aborted)
46653
+ cancel();
46654
+ const timer = request.timeoutMs === void 0 ? void 0 : setTimeout(cancel, Math.max(0, request.timeoutMs));
46655
+ timer?.unref?.();
46656
+ const meta5 = {
46657
+ "bunny-agent": {
46658
+ interactionId: interaction.id,
46659
+ toolCallId: request.toolCallId
46660
+ }
46661
+ };
46662
+ const remoteReply = form ? input2.client.request("elicitation/create", {
46663
+ sessionId: input2.sessionId,
46664
+ toolCallId: request.toolCallId,
46665
+ mode: "form",
46666
+ message: "The agent needs your answer to continue.",
46667
+ requestedSchema: form.schema,
46668
+ _meta: { ...meta5, "bunny-agent/questions": questions }
46669
+ }, { cancellationSignal: controller.signal }).then((reply) => ({
46670
+ action: reply.action,
46671
+ ...reply.action === "accept" && reply.content ? { content: reply.content } : {}
46672
+ })) : permissionRequest(input2, request, options, meta5, controller.signal);
46673
+ void remoteReply.then((reply) => input2.registry.respond(input2.sessionId, interaction.id, reply)).catch(() => input2.registry.respond(input2.sessionId, interaction.id, { action: "cancel" }).catch(() => void 0));
46674
+ try {
46675
+ const reply = await interaction.promise;
46676
+ if (form) {
46677
+ return reply.action === "accept" ? {
46678
+ behavior: "allow",
46679
+ updatedInput: {
46680
+ questions,
46681
+ answers: form.answers(reply.content ?? {})
46682
+ }
46683
+ } : {
46684
+ behavior: "deny",
46685
+ message: reply.action === "decline" ? "The user declined to answer." : "The question was cancelled or expired."
46686
+ };
46687
+ }
46688
+ const selected = options.find((option) => option.optionId === reply.optionId);
46689
+ return selected && ["allow_once", "allow_always"].includes(selected.kind) && reply.action === "accept" ? {
46690
+ behavior: "allow",
46691
+ updatedInput: recordOf(request.input),
46692
+ optionId: selected.optionId
46693
+ } : {
46694
+ behavior: "deny",
46695
+ message: "The user did not authorize this tool.",
46696
+ ...selected ? { optionId: selected.optionId } : {}
46697
+ };
46698
+ } finally {
46699
+ input2.registry.acknowledge(interaction.id);
46700
+ if (timer)
46701
+ clearTimeout(timer);
46702
+ request.signal?.removeEventListener("abort", cancel);
46703
+ controller.abort();
46704
+ }
46705
+ };
46706
+ }
46707
+ async function permissionRequest(input2, request, options, meta5, signal) {
46708
+ const reply = await input2.client.request("session/request_permission", {
46709
+ sessionId: input2.sessionId,
46710
+ toolCall: request.permission?.toolCall ?? {
46711
+ toolCallId: request.toolCallId,
46712
+ ...toolDisplay(request.toolName, request.input, input2.cwd),
46713
+ status: "pending",
46714
+ rawInput: request.input
46715
+ },
46716
+ options,
46717
+ _meta: { ...request.permission?._meta, ...meta5 }
46718
+ }, { cancellationSignal: signal });
46719
+ if (reply.outcome.outcome !== "selected")
46720
+ return { action: "cancel" };
46721
+ const optionId = reply.outcome.optionId;
46722
+ return {
46723
+ // ACP's selected outcome confirms a choice, including a rejection. Tool
46724
+ // disposition is derived from the selected option's kind by the gate.
46725
+ action: "accept",
46726
+ optionId
46727
+ };
46728
+ }
46729
+ function parseQuestions(input2) {
46730
+ const raw = recordOf(input2)?.questions;
46731
+ if (!Array.isArray(raw) || raw.length === 0 || raw.length > 8)
46732
+ throw new Error("A survey must contain 1 to 8 questions.");
46733
+ return raw.map((item) => {
46734
+ const question = recordOf(item);
46735
+ if (typeof question?.question !== "string" || !question.question.trim())
46736
+ throw new Error("A survey question must have text.");
46737
+ const options = Array.isArray(question.options) ? question.options.map((option) => {
46738
+ const value = recordOf(option);
46739
+ if (typeof value?.label !== "string" || !value.label)
46740
+ throw new Error("Question options must have labels.");
46741
+ return {
46742
+ label: value.label,
46743
+ ...typeof value.description === "string" ? { description: value.description } : {}
46744
+ };
46745
+ }) : void 0;
46746
+ return {
46747
+ question: question.question,
46748
+ ...typeof question.header === "string" ? { header: question.header } : {},
46749
+ multiSelect: question.multiSelect === true,
46750
+ ...question.allowFreeform === false ? { allowFreeform: false } : {},
46751
+ ...options ? { options } : {}
46752
+ };
46753
+ });
46754
+ }
46755
+ function buildQuestionForm(questions) {
46756
+ const properties = {};
46757
+ questions.forEach((question, index) => {
46758
+ const key = `question_${index}`;
46759
+ const title = question.header ? `${question.header}: ${question.question}` : question.question;
46760
+ const choices = question.options?.map((option) => option.label);
46761
+ const selectable = choices && question.allowFreeform !== false ? [...choices, "Other"] : choices;
46762
+ properties[key] = choices?.length ? question.multiSelect ? {
46763
+ type: "array",
46764
+ title,
46765
+ items: { type: "string", enum: selectable },
46766
+ minItems: 1
46767
+ } : { type: "string", title, enum: selectable } : { type: "string", title, minLength: 1 };
46768
+ if (choices?.length && question.allowFreeform !== false)
46769
+ properties[`${key}_other`] = {
46770
+ type: "string",
46771
+ title: `Other answer: ${question.question}`
46772
+ };
46773
+ });
46774
+ return {
46775
+ schema: {
46776
+ type: "object",
46777
+ properties,
46778
+ required: questions.map((_, index) => `question_${index}`)
46779
+ },
46780
+ answers: (content) => Object.fromEntries(questions.map((question, index) => {
46781
+ const key = `question_${index}`;
46782
+ const supplied = content[key];
46783
+ const choices = question.options?.map((option) => option.label);
46784
+ const values = question.multiSelect && choices?.length ? Array.isArray(supplied) && supplied.every((item) => typeof item === "string") ? supplied : [] : typeof supplied === "string" && supplied.trim() ? [supplied] : [];
46785
+ if (values.length === 0 || choices?.length && values.some((item) => !choices.includes(item) && !(item === "Other" && question.allowFreeform !== false))) {
46786
+ throw new Error(`Invalid answer for ${question.question}`);
46787
+ }
46788
+ const answer = values.map((value) => {
46789
+ if (value !== "Other" || !choices?.length)
46790
+ return value;
46791
+ const other = content[`${key}_other`];
46792
+ if (typeof other !== "string" || !other.trim())
46793
+ throw new Error(`A custom answer is required for ${question.question}`);
46794
+ return other;
46795
+ }).join(", ");
46796
+ return [question.question, answer];
46797
+ }))
46798
+ };
46799
+ }
46800
+
46801
+ // ../../packages/server-acp/dist/interaction-registry.js
46802
+ import { randomUUID as randomUUID2 } from "node:crypto";
46803
+ var AcpInteractionRegistry = class {
46804
+ pending = /* @__PURE__ */ new Map();
46805
+ create(sessionId, validate2) {
46806
+ const id = randomUUID2();
46807
+ let resolve6;
46808
+ let acknowledge;
46809
+ const promise3 = new Promise((done) => {
46810
+ resolve6 = done;
46811
+ });
46812
+ const accepted = new Promise((done) => {
46813
+ acknowledge = done;
46814
+ });
46815
+ this.pending.set(id, {
46816
+ sessionId,
46817
+ promise: promise3,
46818
+ resolve: resolve6,
46819
+ accepted,
46820
+ acknowledge,
46821
+ validate: validate2
46822
+ });
46823
+ return { id, promise: promise3 };
46824
+ }
46825
+ async respond(sessionId, id, reply) {
46826
+ const pending = this.pending.get(id);
46827
+ if (!pending || pending.sessionId !== sessionId)
46828
+ throw new Error("Interaction is unknown or expired.");
46829
+ pending.validate?.(reply);
46830
+ if (pending.reply && canonical(pending.reply) !== canonical(reply)) {
46831
+ throw new Error("Interaction was already resolved with a different decision.");
46832
+ }
46833
+ if (!pending.reply) {
46834
+ pending.reply = reply;
46835
+ pending.resolve(reply);
46836
+ }
46837
+ await pending.accepted;
46838
+ return { ok: true };
46839
+ }
46840
+ acknowledge(id) {
46841
+ this.pending.get(id)?.acknowledge();
46842
+ if (this.pending.size > 1e3) {
46843
+ for (const [key, pending] of this.pending) {
46844
+ if (pending.reply && key !== id) {
46845
+ this.pending.delete(key);
46846
+ break;
46847
+ }
46848
+ }
46849
+ }
46850
+ }
46851
+ };
46852
+ function canonical(value) {
46853
+ if (Array.isArray(value))
46854
+ return `[${value.map(canonical).join(",")}]`;
46855
+ if (value && typeof value === "object") {
46856
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
46857
+ }
46858
+ return JSON.stringify(value) ?? "undefined";
46859
+ }
46860
+
46861
+ // ../../packages/server-acp/dist/lifecycle.js
46862
+ var BUNNY_ACP_EVENT_METHOD = "_bunny-agent/session_event";
46863
+
46359
46864
  // ../../packages/server-acp/dist/session-update-serializer.js
46360
- var asText = (value) => typeof value === "string" ? value : JSON.stringify(value ?? null);
46361
46865
  var AcpSessionUpdateSerializer = class {
46866
+ options;
46362
46867
  startedTools = /* @__PURE__ */ new Set();
46868
+ terminalTools = /* @__PURE__ */ new Set();
46363
46869
  toolTitles = /* @__PURE__ */ new Map();
46870
+ toolInputs = /* @__PURE__ */ new Map();
46871
+ completeToolInputs = /* @__PURE__ */ new Map();
46872
+ taskPlan = /* @__PURE__ */ new Map();
46364
46873
  stopReasonValue = "end_turn";
46365
46874
  errorTextValue;
46366
46875
  sessionIdValue;
46367
46876
  inputTokensValue = 0;
46368
46877
  outputTokensValue = 0;
46369
- /** Stop reason for the prompt turn; valid once `serialize()` completes. */
46878
+ usageReported = false;
46879
+ extraUsage = {};
46880
+ nativePassthrough = false;
46881
+ stepNumber = 0;
46882
+ activeStepId;
46883
+ messageId;
46884
+ turnId;
46885
+ now;
46886
+ constructor(options = {}) {
46887
+ this.options = options;
46888
+ this.turnId = options.turnId ?? "turn";
46889
+ this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
46890
+ }
46370
46891
  get stopReason() {
46371
- return this.stopReasonValue;
46892
+ return this.options.signal?.aborted ? "cancelled" : this.stopReasonValue;
46372
46893
  }
46373
- /** Error text emitted by the runner, if the turn failed. */
46374
46894
  get errorText() {
46375
46895
  return this.errorTextValue;
46376
46896
  }
@@ -46378,161 +46898,461 @@ var AcpSessionUpdateSerializer = class {
46378
46898
  return this.sessionIdValue;
46379
46899
  }
46380
46900
  get usage() {
46381
- const totalTokens = this.inputTokensValue + this.outputTokensValue;
46382
- if (totalTokens === 0)
46901
+ if (!this.usageReported)
46383
46902
  return void 0;
46384
46903
  return {
46904
+ ...this.extraUsage,
46385
46905
  inputTokens: this.inputTokensValue,
46386
46906
  outputTokens: this.outputTokensValue,
46387
- totalTokens
46907
+ totalTokens: this.extraUsage.totalTokens ?? this.inputTokensValue + this.outputTokensValue
46388
46908
  };
46389
46909
  }
46910
+ /** Compatibility API for consumers interested only in standard ACP updates. */
46390
46911
  async *serialize(chunks) {
46391
- for await (const chunk of chunks) {
46392
- yield* this.convert(chunk);
46912
+ for await (const event of this.serializeEvents(chunks)) {
46913
+ if (event.type === "update")
46914
+ yield event.update;
46915
+ }
46916
+ }
46917
+ async *serializeEvents(chunks) {
46918
+ yield this.lifecycle("turn_started");
46919
+ try {
46920
+ for await (const chunk of chunks)
46921
+ yield* this.convert(chunk);
46922
+ } catch (error109) {
46923
+ this.errorTextValue = error109 instanceof Error ? error109.message : String(error109);
46924
+ this.stopReasonValue = "refusal";
46925
+ throw error109;
46926
+ } finally {
46927
+ for (const id of this.startedTools) {
46928
+ if (this.terminalTools.has(id))
46929
+ continue;
46930
+ this.terminalTools.add(id);
46931
+ const message = this.stopReason === "cancelled" ? "The tool was interrupted when the turn was cancelled." : "The turn ended before this tool returned.";
46932
+ yield this.update({
46933
+ sessionUpdate: "tool_call_update",
46934
+ toolCallId: id,
46935
+ status: "failed",
46936
+ rawOutput: message,
46937
+ content: [
46938
+ { type: "content", content: { type: "text", text: message } }
46939
+ ],
46940
+ _meta: { "bunny-agent/interrupted": true }
46941
+ });
46942
+ }
46943
+ if (this.activeStepId) {
46944
+ yield this.lifecycle("step_finished", { stepId: this.activeStepId });
46945
+ this.activeStepId = void 0;
46946
+ }
46947
+ yield this.lifecycle("turn_finished", {
46948
+ stopReason: this.stopReason,
46949
+ ...this.usage ? {
46950
+ usage: Object.fromEntries(Object.entries(this.usage).filter(([, value]) => typeof value === "number"))
46951
+ } : {}
46952
+ });
46393
46953
  }
46394
46954
  }
46955
+ lifecycle(type, fields = {}) {
46956
+ return {
46957
+ type: "lifecycle",
46958
+ event: {
46959
+ version: 1,
46960
+ type,
46961
+ turnId: this.turnId,
46962
+ at: this.now(),
46963
+ ...fields
46964
+ }
46965
+ };
46966
+ }
46967
+ update(update) {
46968
+ return { type: "update", update };
46969
+ }
46970
+ metadata(chunk) {
46971
+ const value = chunk;
46972
+ const original = recordOf(value._meta);
46973
+ const providerMetadata = recordOf(value.providerMetadata);
46974
+ const parentToolCallId = value.parentToolCallId ?? value.parent_tool_use_id;
46975
+ if (!original && !providerMetadata && typeof parentToolCallId !== "string")
46976
+ return void 0;
46977
+ return {
46978
+ ...original,
46979
+ ...providerMetadata ? { "bunny-agent/providerMetadata": providerMetadata } : {},
46980
+ ...typeof parentToolCallId === "string" ? { "bunny-agent/parentToolCallId": parentToolCallId } : {}
46981
+ };
46982
+ }
46395
46983
  *convert(chunk) {
46984
+ const value = chunk;
46985
+ const meta5 = this.metadata(chunk);
46986
+ if (chunk.type === "data-acp-session-update") {
46987
+ const original = recordOf(value.data);
46988
+ if (typeof original?.sessionUpdate === "string") {
46989
+ this.nativePassthrough = true;
46990
+ if ((original.sessionUpdate === "tool_call" || original.sessionUpdate === "tool_call_update") && typeof original.toolCallId === "string") {
46991
+ this.startedTools.add(original.toolCallId);
46992
+ if (original.status === "completed" || original.status === "failed")
46993
+ this.terminalTools.add(original.toolCallId);
46994
+ }
46995
+ yield this.update(original);
46996
+ }
46997
+ return;
46998
+ }
46999
+ if (this.nativePassthrough && ![
47000
+ "finish",
47001
+ "message-metadata",
47002
+ "error",
47003
+ "step-finish",
47004
+ "finish-step"
47005
+ ].includes(chunk.type))
47006
+ return;
46396
47007
  switch (chunk.type) {
46397
- case "text-delta": {
46398
- const { id, delta } = chunk;
46399
- yield {
46400
- sessionUpdate: "agent_message_chunk",
46401
- messageId: id,
46402
- content: { type: "text", text: delta }
46403
- };
47008
+ case "text-start":
47009
+ if (typeof value.id === "string")
47010
+ this.messageId = value.id;
47011
+ return;
47012
+ case "text-delta":
47013
+ case "reasoning":
47014
+ case "reasoning-delta": {
47015
+ const text = chunk.type === "reasoning" ? value.text : value.delta;
47016
+ if (typeof text !== "string")
47017
+ return;
47018
+ if (chunk.type === "text-delta" && typeof value.id === "string")
47019
+ this.messageId = value.id;
47020
+ yield this.update({
47021
+ sessionUpdate: chunk.type === "text-delta" ? "agent_message_chunk" : "agent_thought_chunk",
47022
+ ...typeof value.id === "string" ? { messageId: value.id } : {},
47023
+ content: { type: "text", text },
47024
+ ...meta5 ? { _meta: meta5 } : {}
47025
+ });
46404
47026
  return;
46405
47027
  }
46406
- case "reasoning": {
46407
- const { text } = chunk;
46408
- yield {
46409
- sessionUpdate: "agent_thought_chunk",
46410
- content: { type: "text", text }
46411
- };
47028
+ case "text-end": {
47029
+ if (!meta5)
47030
+ return;
47031
+ yield this.update({
47032
+ sessionUpdate: "agent_message_chunk",
47033
+ messageId: value.id,
47034
+ content: { type: "text", text: "" },
47035
+ _meta: meta5
47036
+ });
46412
47037
  return;
46413
47038
  }
46414
47039
  case "tool-input-start": {
46415
- const { toolCallId, toolName } = chunk;
46416
- yield* this.openToolCall(toolCallId, toolName);
47040
+ if (typeof value.toolCallId !== "string")
47041
+ return;
47042
+ yield* this.openToolCall(value.toolCallId, typeof value.toolName === "string" ? value.toolName : void 0, meta5);
47043
+ return;
47044
+ }
47045
+ case "tool-input-delta": {
47046
+ if (typeof value.toolCallId !== "string" || typeof value.inputTextDelta !== "string")
47047
+ return;
47048
+ const inputText = (this.toolInputs.get(value.toolCallId) ?? "") + value.inputTextDelta;
47049
+ this.toolInputs.set(value.toolCallId, inputText);
47050
+ yield* this.openToolCall(value.toolCallId, this.toolTitles.get(value.toolCallId));
47051
+ yield this.update({
47052
+ sessionUpdate: "tool_call_update",
47053
+ toolCallId: value.toolCallId,
47054
+ _meta: { ...meta5, "bunny-agent/inputText": inputText }
47055
+ });
46417
47056
  return;
46418
47057
  }
46419
47058
  case "tool-input-available": {
46420
- const { toolCallId, toolName, input: input2 } = chunk;
46421
- yield* this.openToolCall(toolCallId, toolName);
46422
- yield {
47059
+ if (typeof value.toolCallId !== "string")
47060
+ return;
47061
+ const name = typeof value.toolName === "string" ? value.toolName : this.toolTitles.get(value.toolCallId) ?? "tool";
47062
+ this.completeToolInputs.set(value.toolCallId, value.input);
47063
+ yield* this.openToolCall(value.toolCallId, name, meta5);
47064
+ yield this.update({
46423
47065
  sessionUpdate: "tool_call_update",
46424
- toolCallId,
47066
+ toolCallId: value.toolCallId,
47067
+ ...toolDisplay(name, value.input, this.options.cwd),
46425
47068
  status: "in_progress",
46426
- rawInput: input2
46427
- };
47069
+ rawInput: value.input,
47070
+ ...meta5 ? { _meta: meta5 } : {}
47071
+ });
47072
+ const entries = planEntries(value.input);
47073
+ if (entries && /todo|plan/i.test(name))
47074
+ yield this.update({ sessionUpdate: "plan", entries });
46428
47075
  return;
46429
47076
  }
46430
- case "tool-output-available": {
46431
- const { toolCallId, output: output2, isError } = chunk;
46432
- yield* this.openToolCall(toolCallId, this.toolTitles.get(toolCallId));
46433
- yield {
47077
+ case "tool-output-available":
47078
+ case "tool-output-error":
47079
+ case "tool-input-error":
47080
+ case "tool-output-denied": {
47081
+ if (typeof value.toolCallId !== "string")
47082
+ return;
47083
+ yield* this.openToolCall(value.toolCallId, this.toolTitles.get(value.toolCallId));
47084
+ const failed = chunk.type !== "tool-output-available" || value.isError === true;
47085
+ const output2 = chunk.type === "tool-output-denied" ? "Tool execution was denied." : chunk.type === "tool-output-available" ? value.output : value.errorText;
47086
+ this.terminalTools.add(value.toolCallId);
47087
+ yield this.update({
46434
47088
  sessionUpdate: "tool_call_update",
46435
- toolCallId,
46436
- status: isError ? "failed" : "completed",
47089
+ toolCallId: value.toolCallId,
47090
+ status: failed ? "failed" : "completed",
46437
47091
  rawOutput: output2,
46438
- content: [
46439
- {
46440
- type: "content",
46441
- content: { type: "text", text: asText(output2) }
47092
+ content: toolContents(output2),
47093
+ ...meta5 ? { _meta: meta5 } : {}
47094
+ });
47095
+ if (!failed) {
47096
+ const entries = this.updateTaskPlan(value.toolCallId, output2);
47097
+ if (entries)
47098
+ yield this.update({ sessionUpdate: "plan", entries });
47099
+ }
47100
+ return;
47101
+ }
47102
+ case "file":
47103
+ case "source-url":
47104
+ case "source-document": {
47105
+ const uri = value.url ?? value.uri;
47106
+ const name = value.filename ?? value.title ?? value.sourceId ?? "Resource";
47107
+ const mimeType = value.mediaType ?? value.mimeType;
47108
+ let content;
47109
+ if (typeof uri === "string") {
47110
+ const inline = /^data:([^;,]+);base64,(.*)$/s.exec(uri);
47111
+ content = inline ? inline[1]?.startsWith("image/") ? { type: "image", mimeType: inline[1], data: inline[2] ?? "" } : inline[1]?.startsWith("audio/") ? { type: "audio", mimeType: inline[1], data: inline[2] ?? "" } : {
47112
+ type: "resource",
47113
+ resource: {
47114
+ uri: String(name),
47115
+ mimeType: inline[1],
47116
+ blob: inline[2] ?? ""
46442
47117
  }
46443
- ]
46444
- };
47118
+ } : {
47119
+ type: "resource_link",
47120
+ uri,
47121
+ name: String(name),
47122
+ ...typeof mimeType === "string" ? { mimeType } : {}
47123
+ };
47124
+ } else if (chunk.type === "source-document") {
47125
+ content = {
47126
+ type: "resource_link",
47127
+ uri: `bunny-source:${encodeURIComponent(String(value.sourceId ?? name))}`,
47128
+ name: String(name),
47129
+ ...typeof mimeType === "string" ? { mimeType } : {}
47130
+ };
47131
+ }
47132
+ if (content)
47133
+ yield this.update({
47134
+ sessionUpdate: "agent_message_chunk",
47135
+ content,
47136
+ ...typeof value.id === "string" ? { messageId: value.id } : this.messageId ? { messageId: this.messageId } : {},
47137
+ _meta: {
47138
+ ...meta5,
47139
+ ...value.sourceId ? { "bunny-agent/sourceId": value.sourceId } : {}
47140
+ }
47141
+ });
47142
+ if (chunk.type === "file" && typeof uri === "string" && !uri.startsWith("data:")) {
47143
+ yield this.lifecycle("resource_update", {
47144
+ resource: {
47145
+ id: String(value.id ?? uri),
47146
+ uri,
47147
+ name: String(name),
47148
+ ...typeof mimeType === "string" ? { mimeType } : {}
47149
+ }
47150
+ });
47151
+ }
46445
47152
  return;
46446
47153
  }
46447
- case "tool-output-error": {
46448
- const { toolCallId, errorText } = chunk;
46449
- yield* this.openToolCall(toolCallId, this.toolTitles.get(toolCallId));
46450
- yield {
46451
- sessionUpdate: "tool_call_update",
46452
- toolCallId,
46453
- status: "failed",
46454
- rawOutput: errorText,
46455
- content: [
46456
- { type: "content", content: { type: "text", text: errorText } }
46457
- ]
46458
- };
47154
+ case "start-step":
47155
+ case "step-start": {
47156
+ if (this.activeStepId)
47157
+ yield this.lifecycle("step_finished", { stepId: this.activeStepId });
47158
+ this.activeStepId = typeof value.stepId === "string" ? value.stepId : typeof value.id === "string" ? value.id : `${this.turnId}:step:${++this.stepNumber}`;
47159
+ yield this.lifecycle("step_started", { stepId: this.activeStepId });
46459
47160
  return;
46460
47161
  }
46461
- case "error": {
46462
- const { errorText } = chunk;
46463
- this.errorTextValue = errorText;
46464
- this.stopReasonValue = "refusal";
47162
+ case "finish-step":
47163
+ case "step-finish": {
47164
+ const usage = recordOf(value.usage);
47165
+ if (usage) {
47166
+ this.usageReported = true;
47167
+ this.inputTokensValue += typeof usage.inputTokens === "number" ? usage.inputTokens : 0;
47168
+ this.outputTokensValue += typeof usage.outputTokens === "number" ? usage.outputTokens : 0;
47169
+ }
47170
+ if (this.activeStepId)
47171
+ yield this.lifecycle("step_finished", { stepId: this.activeStepId });
47172
+ this.activeStepId = void 0;
46465
47173
  return;
46466
47174
  }
47175
+ case "error":
47176
+ this.errorTextValue = typeof value.errorText === "string" ? value.errorText : "Runner failed.";
47177
+ this.stopReasonValue = "refusal";
47178
+ return;
46467
47179
  case "finish": {
46468
- const { finishReason: finishReason2, messageMetadata } = chunk;
46469
- this.captureMetadata(messageMetadata);
47180
+ this.captureMetadata(value.messageMetadata);
46470
47181
  if (this.errorTextValue === void 0) {
46471
- this.stopReasonValue = mapFinishReason2(finishReason2);
47182
+ const metadata = recordOf(value.messageMetadata);
47183
+ this.stopReasonValue = isStopReason(metadata?.acpStopReason) ? metadata.acpStopReason : mapFinishReason2(value.finishReason);
46472
47184
  }
46473
47185
  return;
46474
47186
  }
46475
- case "step-finish": {
46476
- const usage = chunk.usage;
46477
- this.inputTokensValue += usage?.inputTokens ?? 0;
46478
- this.outputTokensValue += usage?.outputTokens ?? 0;
46479
- return;
46480
- }
46481
- case "message-metadata": {
46482
- this.captureMetadata(chunk.messageMetadata);
47187
+ case "message-metadata":
47188
+ this.captureMetadata(value.messageMetadata);
47189
+ yield this.update({
47190
+ sessionUpdate: "agent_message_chunk",
47191
+ content: { type: "text", text: "" },
47192
+ _meta: { "bunny-agent/messageMetadata": value.messageMetadata }
47193
+ });
46483
47194
  return;
47195
+ default: {
47196
+ const data = recordOf(value.data);
47197
+ if (chunk.type === "data-artifact" && data) {
47198
+ const uri = data.uri ?? data.url ?? data.path;
47199
+ if (typeof uri === "string") {
47200
+ yield this.lifecycle("resource_update", {
47201
+ resource: {
47202
+ id: String(data.id ?? data.artifactId ?? uri),
47203
+ uri,
47204
+ name: String(data.name ?? data.title ?? data.artifactId ?? "Artifact"),
47205
+ ...typeof data.mimeType === "string" ? { mimeType: data.mimeType } : {},
47206
+ _meta: data
47207
+ }
47208
+ });
47209
+ }
47210
+ } else if ((chunk.type === "data-job" || chunk.type === "data-agent-job") && data && typeof data.id === "string" && typeof data.status === "string") {
47211
+ yield this.lifecycle("job_update", {
47212
+ job: {
47213
+ id: data.id,
47214
+ title: typeof data.title === "string" ? data.title : "Background task",
47215
+ status: data.status,
47216
+ ...typeof data.message === "string" ? { message: data.message } : {}
47217
+ }
47218
+ });
47219
+ }
47220
+ const block = contentBlock(value.content);
47221
+ if (block)
47222
+ yield this.update({
47223
+ sessionUpdate: "agent_message_chunk",
47224
+ content: block,
47225
+ ...meta5 ? { _meta: meta5 } : {}
47226
+ });
47227
+ else if (chunk.type.startsWith("data-") || chunk.type === "compaction") {
47228
+ yield this.update({
47229
+ sessionUpdate: "agent_message_chunk",
47230
+ content: { type: "text", text: "" },
47231
+ _meta: { "bunny-agent/runnerData": value }
47232
+ });
47233
+ }
46484
47234
  }
46485
- // text-start/text-end have no ACP equivalent (agent_message_chunk carries
46486
- // messageId), and tool-input-delta has no incremental-input counterpart.
46487
- default:
46488
- return;
46489
47235
  }
46490
47236
  }
46491
47237
  captureMetadata(metadata) {
46492
- if (!metadata || typeof metadata !== "object")
47238
+ const value = recordOf(metadata);
47239
+ if (!value)
46493
47240
  return;
46494
- const value = metadata;
46495
47241
  const sessionId = value.sessionId ?? value.session_id ?? value.sessionFile;
46496
- if (typeof sessionId === "string" && sessionId.length > 0) {
47242
+ if (typeof sessionId === "string" && sessionId.length > 0)
46497
47243
  this.sessionIdValue = sessionId;
46498
- }
46499
- const usage = value.usage;
46500
- if (!usage || typeof usage !== "object")
47244
+ const usage = recordOf(value.usage);
47245
+ if (!usage)
46501
47246
  return;
46502
- const usageValue = usage;
46503
- if (typeof usageValue.inputTokens === "number") {
46504
- this.inputTokensValue = Math.max(this.inputTokensValue, usageValue.inputTokens);
46505
- }
46506
- if (typeof usageValue.outputTokens === "number") {
46507
- this.outputTokensValue = Math.max(this.outputTokensValue, usageValue.outputTokens);
46508
- }
46509
- }
46510
- *openToolCall(toolCallId, toolName) {
46511
- if (this.startedTools.has(toolCallId))
47247
+ const input2 = usage.inputTokens ?? usage.input_tokens;
47248
+ const output2 = usage.outputTokens ?? usage.output_tokens;
47249
+ if (typeof input2 === "number") {
47250
+ this.inputTokensValue = Math.max(this.inputTokensValue, input2);
47251
+ this.usageReported = true;
47252
+ }
47253
+ if (typeof output2 === "number") {
47254
+ this.outputTokensValue = Math.max(this.outputTokensValue, output2);
47255
+ this.usageReported = true;
47256
+ }
47257
+ for (const [key, alias] of [
47258
+ ["cachedReadTokens", "cache_read_input_tokens"],
47259
+ ["cachedWriteTokens", "cache_creation_input_tokens"],
47260
+ ["thoughtTokens", "reasoning_tokens"],
47261
+ ["totalTokens", "total_tokens"]
47262
+ ]) {
47263
+ const count = usage[key] ?? usage[alias];
47264
+ if (typeof count === "number")
47265
+ this.extraUsage[key] = count;
47266
+ }
47267
+ }
47268
+ *openToolCall(id, name, meta5) {
47269
+ if (name)
47270
+ this.toolTitles.set(id, name);
47271
+ if (this.startedTools.has(id))
46512
47272
  return;
46513
- this.startedTools.add(toolCallId);
46514
- const title = toolName ?? "tool";
46515
- this.toolTitles.set(toolCallId, title);
46516
- yield {
47273
+ this.startedTools.add(id);
47274
+ const title = name ?? "tool";
47275
+ this.toolTitles.set(id, title);
47276
+ yield this.update({
46517
47277
  sessionUpdate: "tool_call",
46518
- toolCallId,
47278
+ toolCallId: id,
46519
47279
  title,
46520
- status: "pending"
47280
+ kind: toolKind(title),
47281
+ status: "pending",
47282
+ _meta: { ...meta5, "bunny-agent/toolName": title }
47283
+ });
47284
+ }
47285
+ updateTaskPlan(id, output2) {
47286
+ const name = this.toolTitles.get(id)?.toLowerCase().replace(/[\s_-]/g, "");
47287
+ if (!["taskcreate", "taskupdate", "tasklist"].includes(name ?? ""))
47288
+ return void 0;
47289
+ const input2 = recordOf(this.completeToolInputs.get(id)) ?? {};
47290
+ let result = recordOf(output2);
47291
+ if (!result && typeof output2 === "string") {
47292
+ try {
47293
+ result = recordOf(JSON.parse(output2));
47294
+ } catch {
47295
+ const created = /^Task #(\S+) created successfully: (.+)$/m.exec(output2);
47296
+ if (created)
47297
+ result = { task: { id: created[1], subject: created[2] } };
47298
+ }
47299
+ }
47300
+ const makeEntry = (task) => {
47301
+ const content = task.subject ?? task.content;
47302
+ if (typeof content !== "string")
47303
+ return void 0;
47304
+ return {
47305
+ content,
47306
+ priority: "medium",
47307
+ status: task.status === "completed" ? "completed" : task.status === "in_progress" ? "in_progress" : "pending"
47308
+ };
46521
47309
  };
47310
+ if (name === "tasklist" && Array.isArray(result?.tasks)) {
47311
+ this.taskPlan.clear();
47312
+ for (const task of result.tasks) {
47313
+ const value = recordOf(task);
47314
+ const entry = value && makeEntry(value);
47315
+ if (value && typeof value.id === "string" && entry)
47316
+ this.taskPlan.set(value.id, entry);
47317
+ }
47318
+ } else if (name === "taskcreate") {
47319
+ const task = recordOf(result?.task);
47320
+ const entry = makeEntry({ ...input2, ...task });
47321
+ if (typeof task?.id !== "string" || !entry)
47322
+ return void 0;
47323
+ this.taskPlan.set(task.id, entry);
47324
+ } else if (name === "taskupdate" && typeof input2.taskId === "string") {
47325
+ if (input2.status === "deleted")
47326
+ this.taskPlan.delete(input2.taskId);
47327
+ else {
47328
+ const previous = this.taskPlan.get(input2.taskId);
47329
+ const entry = makeEntry({ ...previous, ...input2 });
47330
+ if (!entry)
47331
+ return void 0;
47332
+ this.taskPlan.set(input2.taskId, entry);
47333
+ }
47334
+ } else
47335
+ return void 0;
47336
+ return [...this.taskPlan.values()];
46522
47337
  }
46523
47338
  };
46524
- function mapFinishReason2(finishReason2) {
46525
- switch (finishReason2) {
46526
- case "length":
46527
- return "max_tokens";
46528
- case "error":
46529
- return "refusal";
46530
- case "abort":
46531
- case "cancelled":
46532
- return "cancelled";
46533
- default:
46534
- return "end_turn";
46535
- }
47339
+ function isStopReason(value) {
47340
+ return typeof value === "string" && [
47341
+ "end_turn",
47342
+ "max_tokens",
47343
+ "max_turn_requests",
47344
+ "refusal",
47345
+ "cancelled"
47346
+ ].includes(value);
47347
+ }
47348
+ function mapFinishReason2(reason) {
47349
+ if (reason === "length")
47350
+ return "max_tokens";
47351
+ if (reason === "error")
47352
+ return "refusal";
47353
+ if (reason === "abort" || reason === "cancelled")
47354
+ return "cancelled";
47355
+ return "end_turn";
46536
47356
  }
46537
47357
 
46538
47358
  // ../../packages/server-acp/dist/acp-agent.js
@@ -46584,17 +47404,44 @@ var mergeEnv = (...sources) => {
46584
47404
  var promptToText = (prompt) => prompt.map((block) => block.type === "text" ? block.text : "").filter((text) => text.length > 0).join("\n");
46585
47405
  function createBunnyAgentAcpApp(options = {}) {
46586
47406
  const sessions = /* @__PURE__ */ new Map();
46587
- return agent({ name: "bunny-agent" }).onRequest(methods.agent.initialize, () => ({
46588
- protocolVersion: PROTOCOL_VERSION,
46589
- agentCapabilities: { loadSession: false, promptCapabilities: {} },
46590
- agentInfo: { name: "bunny-agent", version: "0.1.0" }
46591
- })).onRequest(methods.agent.session.new, ({ params }) => {
46592
- const sessionId = randomUUID();
47407
+ const interactions = options.interactionRegistry ?? new AcpInteractionRegistry();
47408
+ let capabilities = {};
47409
+ return agent({ name: "bunny-agent" }).onRequest(methods.agent.initialize, ({ params }) => {
47410
+ capabilities = params.clientCapabilities ?? {};
47411
+ return {
47412
+ protocolVersion: PROTOCOL_VERSION,
47413
+ agentCapabilities: {
47414
+ loadSession: false,
47415
+ promptCapabilities: {},
47416
+ _meta: {
47417
+ [BUNNY_META_KEY]: {
47418
+ eventStream: 1,
47419
+ permissionRequests: 1,
47420
+ elicitation: 1,
47421
+ interactionRespond: 1,
47422
+ interactiveRunners: INTERACTIVE_RUNNERS
47423
+ }
47424
+ }
47425
+ },
47426
+ agentInfo: { name: "bunny-agent", version: "0.1.0" }
47427
+ };
47428
+ }).onRequest(methods.agent.session.new, ({ params }) => {
47429
+ const sessionId = randomUUID3();
46593
47430
  sessions.set(sessionId, {
46594
47431
  cwd: params.cwd ?? options.defaultCwd ?? process.cwd(),
46595
47432
  ...readBunnyMeta(params._meta)
46596
47433
  });
46597
- return { sessionId };
47434
+ const runner = readBunnyMeta(params._meta).runner ?? options.defaultRunner ?? "claude";
47435
+ return {
47436
+ sessionId,
47437
+ _meta: {
47438
+ [BUNNY_META_KEY]: {
47439
+ runner,
47440
+ permissionRequests: INTERACTIVE_RUNNERS.includes(runner) ? 1 : 0,
47441
+ elicitation: ["claude", "pi", "copilot"].includes(runner) ? 1 : 0
47442
+ }
47443
+ }
47444
+ };
46598
47445
  }).onRequest(methods.agent.session.prompt, async ({ params, client: client2, signal }) => {
46599
47446
  const session = sessions.get(params.sessionId);
46600
47447
  if (!session) {
@@ -46608,12 +47455,20 @@ function createBunnyAgentAcpApp(options = {}) {
46608
47455
  abort.abort();
46609
47456
  else
46610
47457
  signal.addEventListener("abort", onTransportAbort, { once: true });
46611
- const serializer = new AcpSessionUpdateSerializer();
47458
+ const turnId = randomUUID3();
47459
+ const serializer = new AcpSessionUpdateSerializer({
47460
+ turnId,
47461
+ cwd: session.cwd,
47462
+ signal: abort.signal
47463
+ });
47464
+ const runner = turn.runner ?? session.runner ?? options.defaultRunner ?? "claude";
47465
+ const extension = recordOf(recordOf(capabilities._meta)?.[BUNNY_META_KEY]);
47466
+ const eventStream = extension?.eventStream === 1;
46612
47467
  try {
46613
47468
  let chunks;
46614
47469
  try {
46615
47470
  chunks = parseRunnerStream(createRunner({
46616
- runner: turn.runner ?? session.runner ?? options.defaultRunner ?? "claude",
47471
+ runner,
46617
47472
  model: turn.model ?? session.model ?? options.defaultModel ?? DEFAULT_MODEL,
46618
47473
  ...turn.input ? { input: turn.input } : { userInput: promptToText(params.prompt) },
46619
47474
  systemPrompt: turn.systemPrompt ?? session.systemPrompt,
@@ -46629,6 +47484,15 @@ function createBunnyAgentAcpApp(options = {}) {
46629
47484
  mcpConfig: turn.mcpConfig ?? session.mcpConfig,
46630
47485
  effort: turn.effort ?? session.effort,
46631
47486
  askUserQuestionTimeoutMs: turn.askUserQuestionTimeoutMs ?? session.askUserQuestionTimeoutMs,
47487
+ ...INTERACTIVE_RUNNERS.includes(runner) ? {
47488
+ toolInteraction: createAcpToolInteraction({
47489
+ sessionId: params.sessionId,
47490
+ client: client2,
47491
+ registry: interactions,
47492
+ supportsForm: capabilities.elicitation?.form != null,
47493
+ cwd: session.cwd
47494
+ })
47495
+ } : {},
46632
47496
  abortController: abort,
46633
47497
  // Caller owns session/resume; don't touch cwd/.bunny-agent.
46634
47498
  autoInject: false
@@ -46636,11 +47500,18 @@ function createBunnyAgentAcpApp(options = {}) {
46636
47500
  } catch (error109) {
46637
47501
  throw RequestError.invalidParams(void 0, error109 instanceof Error ? error109.message : String(error109));
46638
47502
  }
46639
- for await (const update of serializer.serialize(chunks)) {
46640
- await client2.notify(methods.client.session.update, {
46641
- sessionId: params.sessionId,
46642
- update
46643
- });
47503
+ for await (const event of serializer.serializeEvents(chunks)) {
47504
+ if (event.type === "update") {
47505
+ await client2.notify(methods.client.session.update, {
47506
+ sessionId: params.sessionId,
47507
+ update: event.update
47508
+ });
47509
+ } else if (eventStream) {
47510
+ await client2.notify(BUNNY_ACP_EVENT_METHOD, {
47511
+ sessionId: params.sessionId,
47512
+ event: event.event
47513
+ });
47514
+ }
46644
47515
  }
46645
47516
  } finally {
46646
47517
  signal.removeEventListener("abort", onTransportAbort);
@@ -46648,13 +47519,27 @@ function createBunnyAgentAcpApp(options = {}) {
46648
47519
  }
46649
47520
  const runnerMeta = {
46650
47521
  ...serializer.sessionId ? { sessionId: serializer.sessionId } : {},
46651
- ...serializer.errorText ? { errorText: serializer.errorText } : {}
47522
+ ...serializer.errorText && !abort.signal.aborted ? { errorText: serializer.errorText } : {}
46652
47523
  };
46653
47524
  return {
46654
47525
  stopReason: serializer.stopReason,
46655
47526
  ...serializer.usage ? { usage: serializer.usage } : {},
46656
47527
  ...Object.keys(runnerMeta).length > 0 ? { _meta: { [BUNNY_META_KEY]: runnerMeta } } : {}
46657
47528
  };
47529
+ }).onRequest(BUNNY_INTERACTION_RESPOND_METHOD, { parse: (value) => recordOf(value) ?? {} }, async ({ params }) => {
47530
+ const value = recordOf(params);
47531
+ if (typeof value?.sessionId !== "string" || typeof value.requestId !== "string" || !["accept", "decline", "cancel"].includes(String(value.action))) {
47532
+ throw RequestError.invalidParams(void 0, "Invalid interaction response.");
47533
+ }
47534
+ try {
47535
+ return await interactions.respond(value.sessionId, value.requestId, {
47536
+ action: value.action,
47537
+ ...recordOf(value.content) ? { content: value.content } : {},
47538
+ ...typeof value.optionId === "string" ? { optionId: value.optionId } : {}
47539
+ });
47540
+ } catch (error109) {
47541
+ throw RequestError.invalidParams(void 0, error109 instanceof Error ? error109.message : String(error109));
47542
+ }
46658
47543
  }).onNotification(methods.agent.session.cancel, ({ params }) => {
46659
47544
  sessions.get(params.sessionId)?.abort?.abort();
46660
47545
  });
@@ -46704,17 +47589,17 @@ import {
46704
47589
  statSync as statSync3,
46705
47590
  writeFileSync as writeFileSync10
46706
47591
  } from "node:fs";
46707
- import { basename as basename2, dirname as dirname5, join as join15, resolve as resolve3 } from "node:path";
47592
+ import { basename as basename2, dirname as dirname5, join as join15, resolve as resolve4 } from "node:path";
46708
47593
  import { fileURLToPath as fileURLToPath2 } from "node:url";
46709
47594
  function getPackageRoot() {
46710
47595
  const thisDir = dirname5(fileURLToPath2(import.meta.url));
46711
- return resolve3(thisDir, "..");
47596
+ return resolve4(thisDir, "..");
46712
47597
  }
46713
47598
  function getShippedDockerfile() {
46714
47599
  const packageRoot = getPackageRoot();
46715
47600
  const candidates = [
46716
47601
  join15(packageRoot, "Dockerfile"),
46717
- resolve3(
47602
+ resolve4(
46718
47603
  packageRoot,
46719
47604
  "..",
46720
47605
  "..",
@@ -46722,7 +47607,7 @@ function getShippedDockerfile() {
46722
47607
  "bunny-agent-claude",
46723
47608
  "Dockerfile"
46724
47609
  ),
46725
- resolve3(process.cwd(), "docker", "bunny-agent-claude", "Dockerfile")
47610
+ resolve4(process.cwd(), "docker", "bunny-agent-claude", "Dockerfile")
46726
47611
  ];
46727
47612
  for (const p of candidates) {
46728
47613
  if (existsSync11(p)) return p;
@@ -46745,7 +47630,7 @@ function ensureDocker() {
46745
47630
  }
46746
47631
  }
46747
47632
  function resolveTemplatePath(template) {
46748
- const abs = resolve3(process.cwd(), template);
47633
+ const abs = resolve4(process.cwd(), template);
46749
47634
  if (!existsSync11(abs)) {
46750
47635
  console.error(`\u274C Template directory not found: ${abs}`);
46751
47636
  process.exit(1);
@@ -46897,9 +47782,9 @@ async function runAgent(options) {
46897
47782
  }
46898
47783
 
46899
47784
  // src/cli.ts
46900
- config3({ path: resolve4(process.cwd(), ".env"), quiet: true });
46901
- config3({ path: resolve4(process.cwd(), "../.env"), quiet: true });
46902
- config3({ path: resolve4(process.cwd(), "../../.env"), quiet: true });
47785
+ config3({ path: resolve5(process.cwd(), ".env"), quiet: true });
47786
+ config3({ path: resolve5(process.cwd(), "../.env"), quiet: true });
47787
+ config3({ path: resolve5(process.cwd(), "../../.env"), quiet: true });
46903
47788
  function getSubcommand() {
46904
47789
  for (let i = 2; i < process.argv.length; i++) {
46905
47790
  const a = process.argv[i];