@bunny-agent/daemon 0.9.28 → 0.9.29-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -116838,7 +116838,7 @@ var require_core3 = __commonJS({
116838
116838
  return match2 && match2.index === 0;
116839
116839
  }
116840
116840
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
116841
- function join38(regexps, separator = "|") {
116841
+ function join39(regexps, separator = "|") {
116842
116842
  let numCaptures = 0;
116843
116843
  return regexps.map((regex2) => {
116844
116844
  numCaptures += 1;
@@ -117142,7 +117142,7 @@ var require_core3 = __commonJS({
117142
117142
  this.exec = () => null;
117143
117143
  }
117144
117144
  const terminators = this.regexes.map((el) => el[1]);
117145
- this.matcherRe = langRe(join38(terminators), true);
117145
+ this.matcherRe = langRe(join39(terminators), true);
117146
117146
  this.lastIndex = 0;
117147
117147
  }
117148
117148
  /** @param {string} s */
@@ -168060,14 +168060,14 @@ var require_graceful_fs = __commonJS({
168060
168060
  return close;
168061
168061
  })(fs16.close);
168062
168062
  fs16.closeSync = (function(fs$closeSync) {
168063
- function closeSync2(fd) {
168063
+ function closeSync3(fd) {
168064
168064
  fs$closeSync.apply(fs16, arguments);
168065
168065
  resetQueue();
168066
168066
  }
168067
- Object.defineProperty(closeSync2, previousSymbol, {
168067
+ Object.defineProperty(closeSync3, previousSymbol, {
168068
168068
  value: fs$closeSync
168069
168069
  });
168070
- return closeSync2;
168070
+ return closeSync3;
168071
168071
  })(fs16.closeSync);
168072
168072
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
168073
168073
  process.on("exit", function() {
@@ -206434,10 +206434,15 @@ async function fsStat(state, q2) {
206434
206434
  const root = resolveVolumeRoot(state, q2.volume);
206435
206435
  const target = resolveUnderRoot(root, q2.path);
206436
206436
  const stat6 = await fs2.stat(target);
206437
+ const created_at = msToIsoOrNull(stat6.birthtimeMs) ?? msToIsoOrNull(stat6.ctimeMs);
206437
206438
  return ok({
206438
206439
  path: target,
206439
206440
  is_dir: stat6.isDirectory(),
206440
- size: stat6.isFile() ? stat6.size : 0
206441
+ size: stat6.isFile() ? stat6.size : 0,
206442
+ created_at,
206443
+ modified_at: msToIsoOrNull(
206444
+ stat6.mtimeMs
206445
+ )
206441
206446
  });
206442
206447
  }
206443
206448
  async function fsExists(state, q2) {
@@ -208379,7 +208384,7 @@ function createOpenCodeRunner(options2 = {}) {
208379
208384
 
208380
208385
  // ../../packages/runner-pi/dist/pi-runner.js
208381
208386
  import { appendFileSync as appendFileSync5, existsSync as existsSync28, unlinkSync as unlinkSync6 } from "node:fs";
208382
- import { join as join35 } from "node:path";
208387
+ import { join as join36 } from "node:path";
208383
208388
 
208384
208389
  // ../../node_modules/.pnpm/@mariozechner+pi-ai@0.64.0_ws@8.19.0_zod@4.3.6/node_modules/@mariozechner/pi-ai/dist/index.js
208385
208390
  var dist_exports = {};
@@ -263365,25 +263370,146 @@ var generateImageSchema = {
263365
263370
  },
263366
263371
  quality: {
263367
263372
  type: "string",
263368
- enum: ["standard", "hd"],
263369
- description: "Image quality (OpenAI only). Defaults to standard."
263373
+ enum: ["low", "medium", "high", "auto"],
263374
+ description: "Image quality. Defaults to auto."
263370
263375
  }
263371
263376
  },
263372
263377
  required: ["prompt"],
263373
263378
  additionalProperties: false
263374
263379
  };
263375
- async function resolveB64(item) {
263380
+ async function resolveB64(item, apiKey) {
263376
263381
  if (item.b64_json)
263377
263382
  return item.b64_json;
263378
- if (item.url) {
263379
- const res = await fetch(item.url);
263383
+ if (item.b64Json)
263384
+ return item.b64Json;
263385
+ if (item.image_base64)
263386
+ return item.image_base64;
263387
+ if (item.imageBase64)
263388
+ return item.imageBase64;
263389
+ if (item.base64)
263390
+ return item.base64;
263391
+ if (typeof item.image === "string")
263392
+ return item.image;
263393
+ if (item.image?.b64_json)
263394
+ return item.image.b64_json;
263395
+ if (item.image?.base64)
263396
+ return item.image.base64;
263397
+ const url = item.url ?? item.image_url ?? item.imageUrl ?? item.image?.url;
263398
+ if (url) {
263399
+ const headers = {};
263400
+ if (apiKey) {
263401
+ headers.Authorization = `Bearer ${apiKey}`;
263402
+ }
263403
+ const res = await fetch(url, { headers });
263380
263404
  if (res.ok)
263381
263405
  return Buffer.from(await res.arrayBuffer()).toString("base64");
263382
263406
  }
263383
263407
  return void 0;
263384
263408
  }
263385
- async function saveImageItem(item, filePath) {
263386
- const b64 = await resolveB64(item);
263409
+ function pickImageItem(response) {
263410
+ const tryFromObject = (value2) => {
263411
+ if (!value2 || typeof value2 !== "object")
263412
+ return void 0;
263413
+ const obj = value2;
263414
+ return {
263415
+ b64_json: obj.b64_json ?? obj.b64Json,
263416
+ b64Json: obj.b64Json,
263417
+ url: obj.url ?? obj.imageUrl,
263418
+ image_base64: obj.image_base64 ?? obj.imageBase64,
263419
+ imageBase64: obj.imageBase64,
263420
+ image_url: obj.image_url ?? obj.imageUrl,
263421
+ imageUrl: obj.imageUrl,
263422
+ base64: obj.base64,
263423
+ image: obj.image
263424
+ };
263425
+ };
263426
+ const asItem = (value2) => {
263427
+ if (value2 == null)
263428
+ return void 0;
263429
+ if (typeof value2 === "string") {
263430
+ return { base64: value2 };
263431
+ }
263432
+ if (typeof value2 === "object") {
263433
+ const normalized = tryFromObject(value2);
263434
+ if (normalized)
263435
+ return normalized;
263436
+ }
263437
+ return void 0;
263438
+ };
263439
+ const fromDataArray = Array.isArray(response.data) ? asItem(response.data[0]) : void 0;
263440
+ if (fromDataArray)
263441
+ return fromDataArray;
263442
+ const fromDataValue = asItem(response.data);
263443
+ if (fromDataValue)
263444
+ return fromDataValue;
263445
+ const responseRecord = response;
263446
+ const imagesValue = responseRecord.images;
263447
+ const outputValue = responseRecord.output;
263448
+ const fromImagesArray = Array.isArray(imagesValue) ? asItem(imagesValue[0]) : void 0;
263449
+ if (fromImagesArray)
263450
+ return fromImagesArray;
263451
+ const fromImagesValue = asItem(imagesValue);
263452
+ if (fromImagesValue)
263453
+ return fromImagesValue;
263454
+ const fromOutputArray = Array.isArray(outputValue) ? asItem(outputValue[0]) : void 0;
263455
+ if (fromOutputArray)
263456
+ return fromOutputArray;
263457
+ const fromOutputValue = asItem(outputValue);
263458
+ if (fromOutputValue)
263459
+ return fromOutputValue;
263460
+ const fromTopLevel = asItem(response);
263461
+ if (fromTopLevel)
263462
+ return fromTopLevel;
263463
+ const queue = [response];
263464
+ while (queue.length > 0) {
263465
+ const current = queue.shift();
263466
+ if (current == null)
263467
+ continue;
263468
+ if (typeof current === "string") {
263469
+ if (/^[A-Za-z0-9+/=]{32,}$/.test(current))
263470
+ return { base64: current };
263471
+ continue;
263472
+ }
263473
+ if (typeof current !== "object")
263474
+ continue;
263475
+ const normalized = tryFromObject(current);
263476
+ if (normalized) {
263477
+ const hasUsefulField = Boolean(normalized.b64_json ?? normalized.b64Json ?? normalized.image_base64 ?? normalized.imageBase64 ?? normalized.base64 ?? normalized.url ?? normalized.image_url ?? normalized.imageUrl ?? (typeof normalized.image === "string" ? normalized.image : normalized.image?.b64_json ?? normalized.image?.base64 ?? normalized.image?.url));
263478
+ if (hasUsefulField)
263479
+ return normalized;
263480
+ }
263481
+ if (Array.isArray(current)) {
263482
+ queue.push(...current);
263483
+ continue;
263484
+ }
263485
+ for (const value2 of Object.values(current)) {
263486
+ queue.push(value2);
263487
+ }
263488
+ }
263489
+ return {};
263490
+ }
263491
+ function detectImageMime(filePath) {
263492
+ const ext2 = extname2(filePath).toLowerCase();
263493
+ if (ext2 === ".jpg" || ext2 === ".jpeg")
263494
+ return "image/jpeg";
263495
+ if (ext2 === ".webp")
263496
+ return "image/webp";
263497
+ if (ext2 === ".gif")
263498
+ return "image/gif";
263499
+ return "image/png";
263500
+ }
263501
+ function buildPolicySafeEditPrompt(prompt) {
263502
+ const riskyPattern = /\b(watermark|watermarks|logo|logos|copyright|brand mark|remove branding)\b/i;
263503
+ if (!riskyPattern.test(prompt)) {
263504
+ return { prompt, rewritten: false };
263505
+ }
263506
+ return {
263507
+ prompt: "Clean up distracting overlay text or marks naturally while preserving the original scene, style, and layout. Keep the result seamless and high quality.",
263508
+ rewritten: true
263509
+ };
263510
+ }
263511
+ async function saveImageItem(item, filePath, apiKey) {
263512
+ const b64 = await resolveB64(item, apiKey);
263387
263513
  if (!b64)
263388
263514
  return void 0;
263389
263515
  mkdirSync10(dirname19(filePath), { recursive: true });
@@ -263403,11 +263529,11 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263403
263529
  ],
263404
263530
  // biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
263405
263531
  parameters: generateImageSchema,
263406
- async execute(_toolCallId, params, _signal, _onUpdate) {
263532
+ async execute(_toolCallId, params, signal, _onUpdate) {
263407
263533
  const p = params;
263408
263534
  const prompt = p.prompt;
263409
263535
  const size = p.size ?? "1024x1024";
263410
- const quality = p.quality ?? "standard";
263536
+ const quality = p.quality ?? "auto";
263411
263537
  const rawFilename = p.filename;
263412
263538
  const filename = rawFilename ? extname2(rawFilename) ? rawFilename : `${rawFilename}.png` : `image_${Date.now()}.png`;
263413
263539
  const filePath = join34(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
@@ -263424,24 +263550,28 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263424
263550
  prompt,
263425
263551
  n: 1,
263426
263552
  size,
263427
- quality
263428
- })
263553
+ quality,
263554
+ response_format: "b64_json",
263555
+ output_format: "png"
263556
+ }),
263557
+ signal
263429
263558
  });
263430
263559
  if (!res.ok) {
263431
263560
  throw new Error(`Image generation failed (${res.status}): ${await res.text()}`);
263432
263561
  }
263433
263562
  const json = await res.json();
263434
- const item = json.data?.[0] ?? {};
263435
- const savedPath = await saveImageItem(item, filePath);
263563
+ const item = pickImageItem(json);
263564
+ const savedPath = await saveImageItem(item, filePath, apiKey);
263436
263565
  return {
263437
263566
  content: [
263438
263567
  {
263439
263568
  type: "text",
263440
- text: savedPath ?? "Image generated but could not be saved."
263569
+ text: savedPath ?? `Image generated but could not be saved: no image payload returned; image_model: ${imageModelId}`
263441
263570
  }
263442
263571
  ],
263443
263572
  details: {
263444
263573
  filePath: savedPath,
263574
+ ...json.usage != null ? { usage: { raw: { [imageModelId]: json.usage } } } : {},
263445
263575
  response: json
263446
263576
  }
263447
263577
  };
@@ -263457,13 +263587,504 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
263457
263587
  }
263458
263588
  };
263459
263589
  }
263590
+ var editImageSchema = {
263591
+ type: "object",
263592
+ properties: {
263593
+ image: {
263594
+ type: "string",
263595
+ description: "Path to the source image file to edit (relative to working directory or absolute)."
263596
+ },
263597
+ prompt: {
263598
+ type: "string",
263599
+ description: "Text description of the desired final image. Describe the full result, not just the change."
263600
+ },
263601
+ mask: {
263602
+ type: "string",
263603
+ description: "Optional path to a mask image (PNG with transparent areas indicating where to edit). If omitted, the model decides what to change based on the prompt."
263604
+ },
263605
+ filename: {
263606
+ type: "string",
263607
+ description: "Output filename with extension, e.g. 'edited_cat.png'. Defaults to a timestamp-based name."
263608
+ },
263609
+ size: {
263610
+ type: "string",
263611
+ enum: ["1024x1024", "1024x1536", "1536x1024", "auto"],
263612
+ description: "Output image dimensions. Optional; omit or set auto to let model decide."
263613
+ },
263614
+ quality: {
263615
+ type: "string",
263616
+ enum: ["low", "medium", "high", "auto"],
263617
+ description: "Image quality. Optional; omit or set auto to let model decide."
263618
+ }
263619
+ },
263620
+ required: ["image", "prompt"],
263621
+ additionalProperties: false
263622
+ };
263623
+ function buildMultipartBody(fields, files) {
263624
+ const boundary = `----SandagentBoundary${Date.now()}${Math.random().toString(36).slice(2)}`;
263625
+ const parts = [];
263626
+ for (const { name, value: value2 } of fields) {
263627
+ parts.push(Buffer.from(`--${boundary}\r
263628
+ Content-Disposition: form-data; name="${name}"\r
263629
+ \r
263630
+ ${value2}\r
263631
+ `));
263632
+ }
263633
+ for (const { name, filename, buffer, mime } of files) {
263634
+ parts.push(Buffer.from(`--${boundary}\r
263635
+ Content-Disposition: form-data; name="${name}"; filename="${filename}"\r
263636
+ Content-Type: ${mime}\r
263637
+ \r
263638
+ `));
263639
+ parts.push(buffer);
263640
+ parts.push(Buffer.from("\r\n"));
263641
+ }
263642
+ parts.push(Buffer.from(`--${boundary}--\r
263643
+ `));
263644
+ return {
263645
+ body: Buffer.concat(parts),
263646
+ contentType: `multipart/form-data; boundary=${boundary}`
263647
+ };
263648
+ }
263649
+ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
263650
+ return {
263651
+ name: "edit_image",
263652
+ label: "edit image",
263653
+ description: "Edit an existing image based on a text prompt. Optionally use a mask to control which areas to modify. Saves the result to disk and returns the file path.",
263654
+ promptSnippet: "edit_image(image, prompt, mask?, filename?, size?, quality?) - edit an existing image",
263655
+ promptGuidelines: [
263656
+ "Use edit_image when the user wants to modify, retouch, or transform an existing image.",
263657
+ "The prompt should describe the full desired final image, not just the change.",
263658
+ "Provide the source image path. Use a mask image (PNG with transparent areas) to control where edits happen.",
263659
+ "Without a mask, the model decides what to change based on the prompt."
263660
+ ],
263661
+ // biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
263662
+ parameters: editImageSchema,
263663
+ async execute(_toolCallId, params, signal, _onUpdate) {
263664
+ const { readFileSync: readFileSync24, existsSync: existsSync31 } = await import("node:fs");
263665
+ const { resolve: resolve14, basename: basename12 } = await import("node:path");
263666
+ const p = params;
263667
+ const imagePath = p.image;
263668
+ const prompt = p.prompt;
263669
+ const maskPath = p.mask;
263670
+ const size = p.size;
263671
+ const quality = p.quality;
263672
+ const rawFilename = p.filename;
263673
+ const safePrompt = buildPolicySafeEditPrompt(prompt);
263674
+ const resolvedImage = resolve14(cwd, imagePath);
263675
+ if (!existsSync31(resolvedImage)) {
263676
+ return {
263677
+ content: [
263678
+ {
263679
+ type: "text",
263680
+ text: `Image edit error: source image not found at ${resolvedImage}`
263681
+ }
263682
+ ],
263683
+ details: void 0
263684
+ };
263685
+ }
263686
+ const filename = rawFilename ? extname2(rawFilename) ? rawFilename : `${rawFilename}.png` : `edited_${Date.now()}.png`;
263687
+ const filePath = join34(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
263688
+ try {
263689
+ const imageBuffer = readFileSync24(resolvedImage);
263690
+ const fields = [
263691
+ { name: "model", value: imageModelId },
263692
+ { name: "prompt", value: safePrompt.prompt },
263693
+ { name: "n", value: "1" },
263694
+ { name: "response_format", value: "b64_json" },
263695
+ { name: "output_format", value: "png" }
263696
+ ];
263697
+ if (size && size !== "auto") {
263698
+ fields.push({ name: "size", value: size });
263699
+ }
263700
+ if (quality && quality !== "auto") {
263701
+ fields.push({ name: "quality", value: quality });
263702
+ }
263703
+ const files = [
263704
+ {
263705
+ name: "image",
263706
+ filename: basename12(resolvedImage),
263707
+ buffer: imageBuffer,
263708
+ mime: detectImageMime(resolvedImage)
263709
+ }
263710
+ ];
263711
+ if (maskPath) {
263712
+ const resolvedMask = resolve14(cwd, maskPath);
263713
+ if (existsSync31(resolvedMask)) {
263714
+ files.push({
263715
+ name: "mask",
263716
+ filename: basename12(resolvedMask),
263717
+ buffer: readFileSync24(resolvedMask),
263718
+ mime: detectImageMime(resolvedMask)
263719
+ });
263720
+ }
263721
+ }
263722
+ const { body: multipartBody, contentType } = buildMultipartBody(fields, files);
263723
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/images/edits`;
263724
+ const sendRequest = async (body, type) => {
263725
+ const res = await fetch(url, {
263726
+ method: "POST",
263727
+ headers: {
263728
+ "Content-Type": type,
263729
+ Authorization: `Bearer ${apiKey}`
263730
+ },
263731
+ body,
263732
+ signal
263733
+ });
263734
+ if (!res.ok) {
263735
+ throw new Error(`Image edit failed (${res.status}): ${await res.text()}`);
263736
+ }
263737
+ return await res.json();
263738
+ };
263739
+ let json = await sendRequest(multipartBody, contentType);
263740
+ const item = pickImageItem(json);
263741
+ let savedPath = await saveImageItem(item, filePath, apiKey);
263742
+ const firstResponseHasEmptyDataArray = Array.isArray(json.data) && json.data.length === 0;
263743
+ if (!savedPath && safePrompt.rewritten && firstResponseHasEmptyDataArray) {
263744
+ const retryFields = fields.map((f3) => f3.name === "prompt" ? {
263745
+ name: "prompt",
263746
+ value: "Remove only distracting overlay text artifacts naturally and keep all original content unchanged."
263747
+ } : f3);
263748
+ const retryMultipart = buildMultipartBody(retryFields, files);
263749
+ json = await sendRequest(retryMultipart.body, retryMultipart.contentType);
263750
+ const retryItem = pickImageItem(json);
263751
+ savedPath = await saveImageItem(retryItem, filePath, apiKey);
263752
+ }
263753
+ return {
263754
+ content: [
263755
+ {
263756
+ type: "text",
263757
+ text: savedPath ?? `Image edited but could not be saved: no image payload returned; image_model: ${imageModelId}`
263758
+ }
263759
+ ],
263760
+ details: {
263761
+ filePath: savedPath,
263762
+ ...json.usage != null ? { usage: { raw: { [imageModelId]: json.usage } } } : {},
263763
+ response: json
263764
+ }
263765
+ };
263766
+ } catch (e2) {
263767
+ const msg = e2 instanceof Error ? e2.message : String(e2);
263768
+ return {
263769
+ content: [
263770
+ { type: "text", text: `Image edit error: ${msg}` }
263771
+ ],
263772
+ details: void 0
263773
+ };
263774
+ }
263775
+ }
263776
+ };
263777
+ }
263778
+
263779
+ // ../../packages/runner-pi/dist/session-utils.js
263780
+ import { closeSync as closeSync2, fstatSync, openSync as openSync2, readdirSync as readdirSync12, readSync as readSync2, statSync as statSync13 } from "node:fs";
263781
+ import { join as join35 } from "node:path";
263782
+ var MAX_SESSION_FILE_BYTES = Number(process.env.SANDAGENT_MAX_SESSION_BYTES) || 10 * 1024 * 1024;
263783
+ function resolveSessionPathById(cwd, sessionId) {
263784
+ const tempMgr = SessionManager.create(cwd);
263785
+ const sessionsDir = tempMgr.getSessionDir();
263786
+ try {
263787
+ const suffix = `_${sessionId}.jsonl`;
263788
+ const match2 = readdirSync12(sessionsDir).find((f3) => f3.endsWith(suffix));
263789
+ return match2 ? join35(sessionsDir, match2) : void 0;
263790
+ } catch {
263791
+ return void 0;
263792
+ }
263793
+ }
263794
+ function isSessionFileTooLarge(sessionPath2) {
263795
+ try {
263796
+ return statSync13(sessionPath2).size > MAX_SESSION_FILE_BYTES;
263797
+ } catch {
263798
+ return false;
263799
+ }
263800
+ }
263801
+ function readTailEntries(sessionPath2, tailBytes = 1024 * 1024) {
263802
+ let fd;
263803
+ try {
263804
+ fd = openSync2(sessionPath2, "r");
263805
+ } catch {
263806
+ return [];
263807
+ }
263808
+ try {
263809
+ const fileSize = fstatSync(fd).size;
263810
+ const readStart = Math.max(0, fileSize - tailBytes);
263811
+ const readLen = fileSize - readStart;
263812
+ const buf = Buffer.alloc(readLen);
263813
+ readSync2(fd, buf, 0, readLen, readStart);
263814
+ const tail = buf.toString("utf8");
263815
+ const entries = [];
263816
+ for (const line of tail.split("\n")) {
263817
+ const trimmed = line.trim();
263818
+ if (!trimmed)
263819
+ continue;
263820
+ try {
263821
+ entries.push(JSON.parse(trimmed));
263822
+ } catch {
263823
+ }
263824
+ }
263825
+ return entries;
263826
+ } finally {
263827
+ closeSync2(fd);
263828
+ }
263829
+ }
263830
+ function extractSessionContext(sessionPath2) {
263831
+ const entries = readTailEntries(sessionPath2);
263832
+ if (entries.length === 0)
263833
+ return void 0;
263834
+ for (let i2 = entries.length - 1; i2 >= 0; i2--) {
263835
+ const e2 = entries[i2];
263836
+ if (e2.type === "compaction" && typeof e2.summary === "string") {
263837
+ return e2.summary;
263838
+ }
263839
+ }
263840
+ const recentMessages = [];
263841
+ const MAX_MESSAGES = 6;
263842
+ for (let i2 = entries.length - 1; i2 >= 0 && recentMessages.length < MAX_MESSAGES; i2--) {
263843
+ const e2 = entries[i2];
263844
+ if (e2.type !== "message")
263845
+ continue;
263846
+ const msg = e2.message;
263847
+ if (!msg)
263848
+ continue;
263849
+ if (msg.role !== "user" && msg.role !== "assistant")
263850
+ continue;
263851
+ let text = "";
263852
+ if (typeof msg.content === "string") {
263853
+ text = msg.content;
263854
+ } else if (Array.isArray(msg.content)) {
263855
+ text = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
263856
+ }
263857
+ if (text) {
263858
+ recentMessages.unshift(`[${msg.role}]: ${text}`);
263859
+ }
263860
+ }
263861
+ if (recentMessages.length === 0)
263862
+ return void 0;
263863
+ return "## Previous Session Context (auto-extracted)\n\nThe following is the tail of the previous conversation:\n\n" + recentMessages.join("\n\n");
263864
+ }
263865
+
263866
+ // ../../packages/runner-pi/dist/usage-metadata.js
263867
+ function usageToMessageMetadata(usage) {
263868
+ return {
263869
+ input_tokens: usage.input,
263870
+ output_tokens: usage.output,
263871
+ cache_read_input_tokens: usage.cacheRead,
263872
+ cache_creation_input_tokens: usage.cacheWrite
263873
+ };
263874
+ }
263875
+ function accumulateToolUsage(tally, raw) {
263876
+ for (const [key, row] of Object.entries(raw)) {
263877
+ const existing = tally[key];
263878
+ if (existing) {
263879
+ for (const [field, val] of Object.entries(row)) {
263880
+ if (typeof val === "number")
263881
+ existing[field] = (existing[field] ?? 0) + val;
263882
+ }
263883
+ } else {
263884
+ const nums = {};
263885
+ for (const [field, val] of Object.entries(row)) {
263886
+ if (typeof val === "number")
263887
+ nums[field] = val;
263888
+ }
263889
+ tally[key] = nums;
263890
+ }
263891
+ }
263892
+ }
263893
+ function getUsageFromAgentEndMessages(messages) {
263894
+ for (let i2 = messages.length - 1; i2 >= 0; i2--) {
263895
+ const m2 = messages[i2];
263896
+ if (m2.role === "assistant" && m2.usage != null)
263897
+ return m2.usage;
263898
+ }
263899
+ return void 0;
263900
+ }
263901
+
263902
+ // ../../packages/runner-pi/dist/stream-converter.js
263903
+ function emitStreamError(errorText) {
263904
+ const errorLine = "data: " + JSON.stringify({ type: "error", errorText }) + "\n\n";
263905
+ const finishLine = "data: " + JSON.stringify({ type: "finish", finishReason: "error" }) + "\n\n";
263906
+ return [errorLine, finishLine, "data: [DONE]\n\n"];
263907
+ }
263908
+ function extractToolResultText(result) {
263909
+ if (result !== null && typeof result === "object") {
263910
+ const r2 = result;
263911
+ if (Array.isArray(r2.content) && r2.content.length > 0) {
263912
+ const text = r2.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
263913
+ if (text.length > 0)
263914
+ return text;
263915
+ }
263916
+ }
263917
+ if (typeof result === "string")
263918
+ return result;
263919
+ try {
263920
+ return JSON.stringify(result);
263921
+ } catch {
263922
+ return String(result);
263923
+ }
263924
+ }
263925
+ function sseData(obj) {
263926
+ return "data: " + JSON.stringify(obj) + "\n\n";
263927
+ }
263928
+ var PiAISDKStreamConverter = class {
263929
+ constructor(options2) {
263930
+ this.options = options2;
263931
+ this.messageId = "msg_" + Date.now() + "_" + Math.random().toString(36).slice(2);
263932
+ this.toolUsageTally = {};
263933
+ this.activeTextPartId = null;
263934
+ this.hasStarted = false;
263935
+ this.hasFinished = false;
263936
+ }
263937
+ get finished() {
263938
+ return this.hasFinished;
263939
+ }
263940
+ forceError(errorText) {
263941
+ if (this.hasFinished)
263942
+ return [];
263943
+ return [...this.ensureStart(), ...this.finishError(errorText)];
263944
+ }
263945
+ handleEvent(event, aborted) {
263946
+ if (this.hasFinished)
263947
+ return [];
263948
+ const chunks = [...this.ensureStart()];
263949
+ if (event.type === "message_start") {
263950
+ const msg = event.message;
263951
+ if (msg?.role === "assistant")
263952
+ chunks.push(...this.endTextStreamIfOpen());
263953
+ return chunks;
263954
+ }
263955
+ if (event.type === "message_end")
263956
+ return chunks;
263957
+ if (event.type === "message_update") {
263958
+ const sub = event.assistantMessageEvent;
263959
+ if (sub.type === "text_start")
263960
+ chunks.push(...this.endTextStreamIfOpen(), ...this.openTextStream());
263961
+ else if (sub.type === "text_delta")
263962
+ chunks.push(...this.emitTextDelta(sub.delta));
263963
+ else if (sub.type === "toolcall_start")
263964
+ chunks.push(...this.endTextStreamIfOpen());
263965
+ return chunks;
263966
+ }
263967
+ if (event.type === "tool_execution_start") {
263968
+ chunks.push(...this.endTextStreamIfOpen());
263969
+ chunks.push(sseData({
263970
+ type: "tool-input-start",
263971
+ toolCallId: event.toolCallId,
263972
+ toolName: event.toolName,
263973
+ dynamic: true,
263974
+ providerExecuted: true
263975
+ }), sseData({
263976
+ type: "tool-input-available",
263977
+ toolCallId: event.toolCallId,
263978
+ toolName: event.toolName,
263979
+ input: event.args,
263980
+ dynamic: true,
263981
+ providerExecuted: true
263982
+ }));
263983
+ return chunks;
263984
+ }
263985
+ if (event.type === "tool_execution_end") {
263986
+ const output = this.options.redactText(this.options.normalizeToolOutput(event.result));
263987
+ const raw = event.result?.details?.usage?.raw;
263988
+ if (raw != null)
263989
+ accumulateToolUsage(this.toolUsageTally, raw);
263990
+ chunks.push(sseData({
263991
+ type: "tool-output-available",
263992
+ toolCallId: event.toolCallId,
263993
+ output,
263994
+ isError: event.isError,
263995
+ dynamic: true,
263996
+ providerExecuted: true
263997
+ }));
263998
+ return chunks;
263999
+ }
264000
+ if (event.type === "agent_end") {
264001
+ if (aborted) {
264002
+ chunks.push(...this.finishError("Run aborted by signal."));
264003
+ } else {
264004
+ const errorMsg = this.options.getErrorFromAgentEndMessages(event.messages);
264005
+ if (errorMsg)
264006
+ chunks.push(...this.finishError(errorMsg));
264007
+ else
264008
+ chunks.push(...this.finishSuccess(this.options.getUsageFromAgentEndMessages(event.messages)));
264009
+ }
264010
+ return chunks;
264011
+ }
264012
+ return chunks;
264013
+ }
264014
+ ensureStart() {
264015
+ if (this.hasStarted)
264016
+ return [];
264017
+ this.hasStarted = true;
264018
+ return [
264019
+ sseData({ type: "start", messageId: this.messageId }),
264020
+ sseData({
264021
+ type: "message-metadata",
264022
+ messageMetadata: { sessionId: this.options.sessionId }
264023
+ })
264024
+ ];
264025
+ }
264026
+ newTextPartId() {
264027
+ return "text_" + Date.now() + "_" + Math.random().toString(36).slice(2) + "_" + Math.random().toString(36).slice(2);
264028
+ }
264029
+ openTextStream() {
264030
+ this.activeTextPartId = this.newTextPartId();
264031
+ return [sseData({ type: "text-start", id: this.activeTextPartId })];
264032
+ }
264033
+ emitTextDelta(rawDelta) {
264034
+ const delta = rawDelta ? this.options.redactText(rawDelta) : void 0;
264035
+ if (!delta)
264036
+ return [];
264037
+ const startChunk = this.activeTextPartId == null ? this.openTextStream() : [];
264038
+ return [
264039
+ ...startChunk,
264040
+ sseData({ type: "text-delta", id: this.activeTextPartId, delta })
264041
+ ];
264042
+ }
264043
+ endTextStreamIfOpen() {
264044
+ if (this.activeTextPartId == null)
264045
+ return [];
264046
+ const id = this.activeTextPartId;
264047
+ this.activeTextPartId = null;
264048
+ return [sseData({ type: "text-end", id })];
264049
+ }
264050
+ finishSuccess(usage) {
264051
+ const chunks = [...this.endTextStreamIfOpen()];
264052
+ const raw = {};
264053
+ let chatUsage;
264054
+ if (usage) {
264055
+ const { id } = this.options.model;
264056
+ chatUsage = {
264057
+ type: "chat",
264058
+ ...usageToMessageMetadata(usage)
264059
+ };
264060
+ raw[id] = chatUsage;
264061
+ }
264062
+ for (const [key, tally] of Object.entries(this.toolUsageTally)) {
264063
+ raw[key] = { ...tally };
264064
+ }
264065
+ const finishPayload = {
264066
+ type: "finish",
264067
+ finishReason: "stop"
264068
+ };
264069
+ if (usage) {
264070
+ finishPayload.messageMetadata = { usage: { ...chatUsage, raw } };
264071
+ }
264072
+ chunks.push(sseData(finishPayload), "data: [DONE]\n\n");
264073
+ this.hasFinished = true;
264074
+ return chunks;
264075
+ }
264076
+ finishError(errorText) {
264077
+ this.hasFinished = true;
264078
+ return emitStreamError(errorText);
264079
+ }
264080
+ };
263460
264081
 
263461
264082
  // ../../packages/runner-pi/dist/web-tools.js
263462
264083
  var braveProvider = {
263463
264084
  id: "brave",
263464
264085
  label: "Brave Search",
263465
264086
  envKeys: ["BRAVE_API_KEY"],
263466
- async search({ apiKey, query, count, country, freshness }) {
264087
+ async search({ apiKey, query, count, country, freshness, signal }) {
263467
264088
  const params = new URLSearchParams({
263468
264089
  q: query,
263469
264090
  count: String(Math.min(count, 20))
@@ -263477,7 +264098,8 @@ var braveProvider = {
263477
264098
  Accept: "application/json",
263478
264099
  "Accept-Encoding": "gzip",
263479
264100
  "X-Subscription-Token": apiKey
263480
- }
264101
+ },
264102
+ signal
263481
264103
  });
263482
264104
  if (!res.ok) {
263483
264105
  const body = await res.text().catch(() => "");
@@ -263498,14 +264120,14 @@ ${body}`);
263498
264120
  });
263499
264121
  }
263500
264122
  }
263501
- return results;
264123
+ return { results };
263502
264124
  }
263503
264125
  };
263504
264126
  var tavilyProvider = {
263505
264127
  id: "tavily",
263506
264128
  label: "Tavily",
263507
264129
  envKeys: ["TAVILY_API_KEY"],
263508
- async search({ apiKey, query, count }) {
264130
+ async search({ apiKey, query, count, signal }) {
263509
264131
  const res = await fetch("https://api.tavily.com/search", {
263510
264132
  method: "POST",
263511
264133
  headers: { "Content-Type": "application/json" },
@@ -263514,7 +264136,8 @@ var tavilyProvider = {
263514
264136
  query,
263515
264137
  max_results: Math.min(count, 10),
263516
264138
  include_answer: false
263517
- })
264139
+ }),
264140
+ signal
263518
264141
  });
263519
264142
  if (!res.ok) {
263520
264143
  const body = await res.text().catch(() => "");
@@ -263532,7 +264155,7 @@ ${body}`);
263532
264155
  });
263533
264156
  }
263534
264157
  }
263535
- return results;
264158
+ return { results };
263536
264159
  }
263537
264160
  };
263538
264161
  var AUTO_DETECT_ORDER = [braveProvider, tavilyProvider];
@@ -263567,9 +264190,12 @@ var BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
263567
264190
  function htmlToText(html2) {
263568
264191
  return html2.replace(/<(script|style|noscript)[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|h[1-6]|li|tr)>/gi, "\n").replace(/<(p|div|h[1-6]|li|tr)[^>]*>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
263569
264192
  }
263570
- async function fetchPageContent(url) {
264193
+ async function fetchPageContent(url, externalSignal) {
263571
264194
  const controller = new AbortController();
263572
264195
  const timeout = setTimeout(() => controller.abort(), 15e3);
264196
+ externalSignal?.addEventListener("abort", () => controller.abort(), {
264197
+ once: true
264198
+ });
263573
264199
  try {
263574
264200
  const res = await fetch(url, {
263575
264201
  headers: {
@@ -263666,7 +264292,7 @@ function buildWebSearchTool(env2) {
263666
264292
  ],
263667
264293
  // biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
263668
264294
  parameters: webSearchSchema,
263669
- async execute(_toolCallId, params, _signal, _onUpdate) {
264295
+ async execute(_toolCallId, params, signal, _onUpdate) {
263670
264296
  const p = params;
263671
264297
  const query = p.query;
263672
264298
  const count = p.count ?? 5;
@@ -263676,18 +264302,29 @@ function buildWebSearchTool(env2) {
263676
264302
  let lastError;
263677
264303
  for (const { provider, apiKey } of providers) {
263678
264304
  try {
263679
- const results = await provider.search({
264305
+ const { results } = await provider.search({
263680
264306
  apiKey,
263681
264307
  query,
263682
264308
  count,
263683
264309
  country,
263684
- freshness
264310
+ freshness,
264311
+ signal
263685
264312
  });
264313
+ let fetchedPages = 0;
263686
264314
  if (shouldFetchContent) {
263687
264315
  for (const r2 of results) {
263688
- r2.content = await fetchPageContent(r2.link);
264316
+ r2.content = await fetchPageContent(r2.link, signal);
264317
+ fetchedPages += 1;
263689
264318
  }
263690
264319
  }
264320
+ const usage = {
264321
+ raw: {
264322
+ [provider.id]: {
264323
+ requests: 1,
264324
+ fetchedPages
264325
+ }
264326
+ }
264327
+ };
263691
264328
  return {
263692
264329
  content: [
263693
264330
  {
@@ -263695,7 +264332,9 @@ function buildWebSearchTool(env2) {
263695
264332
  text: formatSearchResults(results, provider.label)
263696
264333
  }
263697
264334
  ],
263698
- details: void 0
264335
+ details: {
264336
+ usage
264337
+ }
263699
264338
  };
263700
264339
  } catch (e2) {
263701
264340
  lastError = e2;
@@ -263731,11 +264370,11 @@ function buildWebFetchTool() {
263731
264370
  ],
263732
264371
  // biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
263733
264372
  parameters: webFetchSchema,
263734
- async execute(_toolCallId, params, _signal, _onUpdate) {
264373
+ async execute(_toolCallId, params, signal, _onUpdate) {
263735
264374
  const p = params;
263736
264375
  const url = p.url;
263737
264376
  try {
263738
- const content = await fetchPageContent(url);
264377
+ const content = await fetchPageContent(url, signal);
263739
264378
  return {
263740
264379
  content: [{ type: "text", text: content }],
263741
264380
  details: void 0
@@ -263878,52 +264517,6 @@ function applyModelOverrides(model, provider, optionsEnv) {
263878
264517
  model.baseUrl = anthropicBaseUrl;
263879
264518
  }
263880
264519
  }
263881
- function emitStreamError(errorText) {
263882
- return [
263883
- `data: ${JSON.stringify({ type: "error", errorText })}
263884
-
263885
- `,
263886
- `data: ${JSON.stringify({ type: "finish", finishReason: "error" })}
263887
-
263888
- `,
263889
- "data: [DONE]\n\n"
263890
- ];
263891
- }
263892
- function extractToolResultText(result) {
263893
- if (result !== null && typeof result === "object") {
263894
- const r2 = result;
263895
- if (Array.isArray(r2.content) && r2.content.length > 0) {
263896
- const text = r2.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
263897
- if (text.length > 0) {
263898
- return text;
263899
- }
263900
- }
263901
- }
263902
- if (typeof result === "string")
263903
- return result;
263904
- try {
263905
- return JSON.stringify(result);
263906
- } catch {
263907
- return String(result);
263908
- }
263909
- }
263910
- function usageToMessageMetadata(usage) {
263911
- return {
263912
- input_tokens: usage.input,
263913
- output_tokens: usage.output,
263914
- cache_read_input_tokens: usage.cacheRead,
263915
- cache_creation_input_tokens: usage.cacheWrite
263916
- };
263917
- }
263918
- function getUsageFromAgentEndMessages(messages) {
263919
- for (let i2 = messages.length - 1; i2 >= 0; i2--) {
263920
- const m2 = messages[i2];
263921
- if (m2.role === "assistant" && m2.usage != null) {
263922
- return m2.usage;
263923
- }
263924
- }
263925
- return void 0;
263926
- }
263927
264520
  function getErrorFromAgentEndMessages(messages) {
263928
264521
  for (let i2 = messages.length - 1; i2 >= 0; i2--) {
263929
264522
  const m2 = messages[i2];
@@ -263939,7 +264532,7 @@ function traceRawMessage(debugCwd, data, reset = false, optionsEnv) {
263939
264532
  if (!enabled)
263940
264533
  return;
263941
264534
  try {
263942
- const file = join35(debugCwd, "pi-message-stream-debug.json");
264535
+ const file = join36(debugCwd, "pi-message-stream-debug.json");
263943
264536
  if (reset && existsSync28(file))
263944
264537
  unlinkSync6(file);
263945
264538
  const type = data !== null && typeof data === "object" ? data.type : void 0;
@@ -264008,9 +264601,22 @@ function createPiRunner(options2 = {}) {
264008
264601
  if (resume.includes("/")) {
264009
264602
  return SessionManager.open(resume);
264010
264603
  }
264011
- const sessions = await SessionManager.list(cwd);
264012
- const found = sessions.find((s2) => s2.id === resume);
264013
- return found ? SessionManager.open(found.path) : SessionManager.create(cwd);
264604
+ const sessionPath2 = resolveSessionPathById(cwd, resume);
264605
+ console.error(`${LOG_PREFIX2} resume: id=${resume} path=${sessionPath2 ?? "(not found)"}`);
264606
+ if (sessionPath2) {
264607
+ if (isSessionFileTooLarge(sessionPath2)) {
264608
+ const context = extractSessionContext(sessionPath2);
264609
+ console.error(`${LOG_PREFIX2} session file too large, starting fresh${context ? " (with context)" : ""}`);
264610
+ const newMgr = SessionManager.create(cwd);
264611
+ if (context) {
264612
+ const firstId = newMgr.getEntries()[0]?.id ?? "";
264613
+ newMgr.appendCompaction(context, firstId, 0);
264614
+ }
264615
+ return newMgr;
264616
+ }
264617
+ return SessionManager.open(sessionPath2);
264618
+ }
264619
+ return SessionManager.create(cwd);
264014
264620
  }
264015
264621
  return SessionManager.create(cwd);
264016
264622
  })();
@@ -264028,7 +264634,7 @@ function createPiRunner(options2 = {}) {
264028
264634
  const customTools = options2.env && Object.keys(options2.env).length > 0 ? buildSecretAwareTools(cwd, options2.env) : [];
264029
264635
  if (imageModelName) {
264030
264636
  const apiKey = await modelRegistry2.authStorage.getApiKey(provider) ?? "";
264031
- customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey));
264637
+ customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey), buildImageEditTool(cwd, imageModelName, model.baseUrl, apiKey));
264032
264638
  }
264033
264639
  const { session } = await createAgentSession({
264034
264640
  cwd,
@@ -264068,165 +264674,34 @@ function createPiRunner(options2 = {}) {
264068
264674
  }
264069
264675
  try {
264070
264676
  traceRawMessage(cwd, null, true, options2.env);
264071
- let promptText = userInput;
264072
- let images;
264073
- try {
264074
- if (userInput.startsWith("[") && userInput.endsWith("]")) {
264075
- const parsed = JSON.parse(userInput);
264076
- if (Array.isArray(parsed)) {
264077
- promptText = parsed.filter((p) => p.type === "text").map((p) => p.text).join("\n");
264078
- const imageParts = parsed.filter((p) => p.type === "image");
264079
- if (imageParts.length > 0) {
264080
- images = imageParts.map((p) => ({
264081
- type: "image",
264082
- data: p.data,
264083
- mimeType: p.mimeType
264084
- }));
264085
- }
264677
+ const promptText = userInput;
264678
+ const promptPromise = session.prompt(promptText);
264679
+ const streamConverter = new PiAISDKStreamConverter({
264680
+ sessionId: session.sessionId,
264681
+ model,
264682
+ redactText: (value2) => {
264683
+ if (options2.env && Object.keys(options2.env).length > 0) {
264684
+ return redactSecrets(value2, options2.env);
264086
264685
  }
264087
- }
264088
- } catch (_e2) {
264089
- }
264090
- const promptPromise = session.prompt(promptText, images ? { images } : void 0);
264091
- const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
264092
- let hasStarted = false;
264093
- let hasFinished = false;
264094
- const imageToolUsage = { input_tokens: 0, output_tokens: 0 };
264095
- const newTextPartId = () => `text_${Date.now()}_${Math.random().toString(36).slice(2)}_${Math.random().toString(36).slice(2)}`;
264096
- let activeTextPartId = null;
264097
- let textStreamOpen = false;
264098
- const endTextStreamIfOpen = function* () {
264099
- if (textStreamOpen && activeTextPartId != null) {
264100
- yield `data: ${JSON.stringify({ type: "text-end", id: activeTextPartId })}
264101
-
264102
- `;
264103
- textStreamOpen = false;
264104
- activeTextPartId = null;
264105
- }
264106
- };
264107
- const beginTextStream = function* () {
264108
- activeTextPartId = newTextPartId();
264109
- yield `data: ${JSON.stringify({ type: "text-start", id: activeTextPartId })}
264110
-
264111
- `;
264112
- textStreamOpen = true;
264113
- };
264114
- const ensureStartEvent = async function* () {
264115
- if (!hasStarted) {
264116
- yield `data: ${JSON.stringify({ type: "start", messageId })}
264117
-
264118
- `;
264119
- yield `data: ${JSON.stringify({
264120
- type: "message-metadata",
264121
- messageMetadata: { sessionId: session.sessionId }
264122
- })}
264123
-
264124
- `;
264125
- hasStarted = true;
264126
- }
264127
- };
264128
- const finishSuccess = async function* (usage) {
264129
- yield* endTextStreamIfOpen();
264130
- const finishPayload = { type: "finish", finishReason: "stop" };
264131
- const hasImageUsage = imageToolUsage.input_tokens > 0 || imageToolUsage.output_tokens > 0;
264132
- if (usage != null || hasImageUsage) {
264133
- const base = usage != null ? usageToMessageMetadata(usage) : {};
264134
- finishPayload.messageMetadata = {
264135
- usage: {
264136
- ...base,
264137
- input_tokens: (base.input_tokens ?? 0) + imageToolUsage.input_tokens,
264138
- output_tokens: (base.output_tokens ?? 0) + imageToolUsage.output_tokens
264139
- }
264140
- };
264141
- }
264142
- yield `data: ${JSON.stringify(finishPayload)}
264143
-
264144
- `;
264145
- yield "data: [DONE]\n\n";
264146
- hasFinished = true;
264147
- };
264148
- const finishError = async function* (errorText) {
264149
- for (const chunk of emitStreamError(errorText)) {
264150
- yield chunk;
264151
- }
264152
- hasFinished = true;
264153
- };
264686
+ return value2;
264687
+ },
264688
+ normalizeToolOutput: extractToolResultText,
264689
+ getUsageFromAgentEndMessages,
264690
+ getErrorFromAgentEndMessages
264691
+ });
264154
264692
  while (!isComplete || eventQueue.length > 0) {
264155
264693
  while (eventQueue.length > 0) {
264156
264694
  const event = eventQueue.shift();
264157
264695
  traceRawMessage(cwd, event, false, options2.env);
264158
- yield* ensureStartEvent();
264159
- if (event.type === "message_start") {
264160
- const msg = event.message;
264161
- if (msg?.role === "assistant") {
264162
- yield* endTextStreamIfOpen();
264163
- }
264164
- } else if (event.type === "message_update") {
264165
- const sub = event.assistantMessageEvent;
264166
- if (sub.type === "text_start") {
264167
- yield* endTextStreamIfOpen();
264168
- yield* beginTextStream();
264169
- } else if (sub.type === "text_delta") {
264170
- let delta = sub.delta;
264171
- if (delta) {
264172
- if (options2.env && Object.keys(options2.env).length > 0) {
264173
- delta = redactSecrets(delta, options2.env);
264174
- }
264175
- if (!textStreamOpen) {
264176
- yield* beginTextStream();
264177
- }
264178
- yield `data: ${JSON.stringify({
264179
- type: "text-delta",
264180
- id: activeTextPartId,
264181
- delta
264182
- })}
264183
-
264184
- `;
264185
- }
264186
- } else if (sub.type === "toolcall_start") {
264187
- yield* endTextStreamIfOpen();
264188
- }
264189
- } else if (event.type === "tool_execution_start") {
264190
- yield* endTextStreamIfOpen();
264191
- yield `data: ${JSON.stringify({ type: "tool-input-start", toolCallId: event.toolCallId, toolName: event.toolName, dynamic: true, providerExecuted: true })}
264192
-
264193
- `;
264194
- yield `data: ${JSON.stringify({ type: "tool-input-available", toolCallId: event.toolCallId, toolName: event.toolName, input: event.args, dynamic: true, providerExecuted: true })}
264195
-
264196
- `;
264197
- } else if (event.type === "tool_execution_end") {
264198
- let output = extractToolResultText(event.result);
264199
- if (options2.env && Object.keys(options2.env).length > 0) {
264200
- output = redactSecrets(output, options2.env);
264201
- }
264202
- if (event.toolName === "generate_image" && event.result !== null && typeof event.result === "object") {
264203
- const details = event.result.details;
264204
- const u = details?.response?.usage;
264205
- if (u) {
264206
- imageToolUsage.input_tokens += u.input_tokens ?? 0;
264207
- imageToolUsage.output_tokens += u.output_tokens ?? 0;
264208
- }
264209
- }
264210
- yield `data: ${JSON.stringify({ type: "tool-output-available", toolCallId: event.toolCallId, output, isError: event.isError, dynamic: true, providerExecuted: true })}
264211
-
264212
- `;
264213
- } else if (event.type === "agent_end") {
264214
- if (aborted) {
264215
- yield* finishError("Run aborted by signal.");
264216
- } else {
264217
- const errorMsg = getErrorFromAgentEndMessages(event.messages);
264218
- if (errorMsg) {
264219
- yield* finishError(errorMsg);
264220
- } else {
264221
- const usage = getUsageFromAgentEndMessages(event.messages);
264222
- yield* finishSuccess(usage);
264223
- }
264224
- }
264696
+ const chunks = streamConverter.handleEvent(event, aborted);
264697
+ for (const chunk of chunks) {
264698
+ yield chunk;
264225
264699
  }
264226
264700
  }
264227
- if (aborted && !hasFinished) {
264228
- yield* ensureStartEvent();
264229
- yield* finishError("Run aborted by signal.");
264701
+ if (aborted && !streamConverter.finished) {
264702
+ for (const chunk of streamConverter.forceError("Run aborted by signal.")) {
264703
+ yield chunk;
264704
+ }
264230
264705
  break;
264231
264706
  }
264232
264707
  if (!isComplete && eventQueue.length === 0) {
@@ -264235,22 +264710,24 @@ function createPiRunner(options2 = {}) {
264235
264710
  });
264236
264711
  }
264237
264712
  }
264238
- if (hasFinished) {
264713
+ if (streamConverter.finished) {
264239
264714
  return;
264240
264715
  }
264241
264716
  try {
264242
264717
  await promptPromise;
264243
264718
  } catch (error) {
264244
- if (!hasFinished) {
264245
- yield* ensureStartEvent();
264719
+ if (!streamConverter.finished) {
264246
264720
  const message = error instanceof Error ? error.message : "Pi agent run failed.";
264247
- yield* finishError(message);
264721
+ for (const chunk of streamConverter.forceError(message)) {
264722
+ yield chunk;
264723
+ }
264248
264724
  }
264249
264725
  return;
264250
264726
  }
264251
- if (!hasFinished && session.agent.state.error) {
264252
- yield* ensureStartEvent();
264253
- yield* finishError(session.agent.state.error);
264727
+ if (!streamConverter.finished && session.agent.state.error) {
264728
+ for (const chunk of streamConverter.forceError(session.agent.state.error)) {
264729
+ yield chunk;
264730
+ }
264254
264731
  }
264255
264732
  } finally {
264256
264733
  if (abortSignal) {
@@ -264270,11 +264747,11 @@ function createPiRunner(options2 = {}) {
264270
264747
 
264271
264748
  // ../../packages/runner-harness/dist/session.js
264272
264749
  import { existsSync as existsSync29, mkdirSync as mkdirSync11, readFileSync as readFileSync23, writeFileSync as writeFileSync14 } from "node:fs";
264273
- import { join as join36 } from "node:path";
264750
+ import { join as join37 } from "node:path";
264274
264751
  var DIR = ".bunny-agent";
264275
264752
  var FILE = "session-id";
264276
264753
  function sessionPath(cwd) {
264277
- return join36(cwd, DIR, FILE);
264754
+ return join37(cwd, DIR, FILE);
264278
264755
  }
264279
264756
  function readSessionId(cwd) {
264280
264757
  try {
@@ -264288,28 +264765,28 @@ function readSessionId(cwd) {
264288
264765
  }
264289
264766
  function writeSessionId(cwd, id) {
264290
264767
  try {
264291
- mkdirSync11(join36(cwd, DIR), { recursive: true });
264768
+ mkdirSync11(join37(cwd, DIR), { recursive: true });
264292
264769
  writeFileSync14(sessionPath(cwd), id, "utf8");
264293
264770
  } catch {
264294
264771
  }
264295
264772
  }
264296
264773
 
264297
264774
  // ../../packages/runner-harness/dist/skills.js
264298
- import { existsSync as existsSync30, readdirSync as readdirSync12, statSync as statSync13 } from "node:fs";
264775
+ import { existsSync as existsSync30, readdirSync as readdirSync13, statSync as statSync14 } from "node:fs";
264299
264776
  import { homedir as homedir14 } from "node:os";
264300
- import { join as join37 } from "node:path";
264777
+ import { join as join38 } from "node:path";
264301
264778
  function discoverSkillPaths(cwd) {
264302
264779
  const paths = [];
264303
264780
  for (const base of [
264304
- join37(cwd, "skills"),
264305
- join37(homedir14(), ".bunny-agent", "skills")
264781
+ join38(cwd, "skills"),
264782
+ join38(homedir14(), ".bunny-agent", "skills")
264306
264783
  ]) {
264307
264784
  if (!existsSync30(base))
264308
264785
  continue;
264309
264786
  try {
264310
- for (const entry of readdirSync12(base)) {
264311
- const full = join37(base, entry);
264312
- if (statSync13(full).isDirectory() && existsSync30(join37(full, "SKILL.md"))) {
264787
+ for (const entry of readdirSync13(base)) {
264788
+ const full = join38(base, entry);
264789
+ if (statSync14(full).isDirectory() && existsSync30(join38(full, "SKILL.md"))) {
264313
264790
  paths.push(full);
264314
264791
  }
264315
264792
  }
@@ -264432,7 +264909,9 @@ async function bunnyAgentRun(req, res, env2) {
264432
264909
  cwd: req.cwd ?? process.env.BUNNY_AGENT_ROOT ?? "/workspace",
264433
264910
  yolo: req.yolo,
264434
264911
  env: env2,
264435
- abortController
264912
+ abortController,
264913
+ // API: caller owns resume/session; do not read/write cwd/.bunny-agent or auto-load CLAUDE.md.
264914
+ autoInject: false
264436
264915
  });
264437
264916
  for await (const chunk of stream2) {
264438
264917
  res.write(chunk);