teapot-coding-agent 0.37.67 → 0.37.69

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.
@@ -1850,7 +1850,26 @@ export class Agent {
1850
1850
  durationMs: Date.now() - t0,
1851
1851
  result: capped,
1852
1852
  });
1853
- this.messages.push({ role: "tool", tool_call_id: call.id, content: capped });
1853
+ /**
1854
+ * A tool may hand the model an IMAGE (read_file on a picture).
1855
+ *
1856
+ * It rides on the tool message as `content_parts`, which `sanitize()` already knows how to send
1857
+ * to a vision model. The text stays in `content` so the log, the timeline and a non-vision model
1858
+ * all still see something meaningful.
1859
+ */
1860
+ this.messages.push({
1861
+ role: "tool",
1862
+ tool_call_id: call.id,
1863
+ content: capped,
1864
+ ...(result.image
1865
+ ? {
1866
+ content_parts: [
1867
+ { type: "text", text: capped },
1868
+ { type: "image_url", image_url: { url: result.image.url } },
1869
+ ],
1870
+ }
1871
+ : {}),
1872
+ });
1854
1873
  this.consecutiveToolErrors = result.ok ? 0 : this.consecutiveToolErrors + 1;
1855
1874
  if (this.consecutiveToolErrors >= this.opts.maxConsecutiveToolErrors) {
1856
1875
  await this.log.append("system_note", this.currentSession, this.currentBranch, {
@@ -32,6 +32,13 @@ export async function looksBinary(p) {
32
32
  await fh.close();
33
33
  }
34
34
  }
35
+ /** the `looksBinary()` kinds that are pictures a vision model can actually receive */
36
+ const IMAGE_MIME = {
37
+ "PNG image": "image/png",
38
+ "JPEG image": "image/jpeg",
39
+ "GIF image": "image/gif",
40
+ "WEBP image": "image/webp",
41
+ };
35
42
  function sniff(head) {
36
43
  const hex = (n) => head.subarray(0, n).toString("hex");
37
44
  if (hex(8).startsWith("89504e47"))
@@ -40,6 +47,9 @@ function sniff(head) {
40
47
  return "JPEG image";
41
48
  if (head.subarray(0, 4).toString("latin1") === "GIF8")
42
49
  return "GIF image";
50
+ // RIFF....WEBP — the four bytes after "RIFF" are the size, so "WEBP" sits at offset 8
51
+ if (head.subarray(0, 4).toString("latin1") === "RIFF" && head.subarray(8, 12).toString("latin1") === "WEBP")
52
+ return "WEBP image";
43
53
  if (head.subarray(0, 4).toString("latin1") === "PK\x03\x04")
44
54
  return "zip archive";
45
55
  if (hex(4) === "25504446")
@@ -1045,6 +1055,27 @@ export const TOOLS = [
1045
1055
  try {
1046
1056
  const probe = await looksBinary(p);
1047
1057
  if (probe.binary) {
1058
+ /**
1059
+ * An IMAGE is handed to the model as an image; anything else is still refused.
1060
+ *
1061
+ * Refusing images was never a feature — it was the fix for returning raw bytes as mangled
1062
+ * text (`d907af3`). But the operator's point is the asymmetry: a user can attach a picture
1063
+ * and the agent cannot, so the agent is asked to work on files it is not allowed to see.
1064
+ * MEASURED: `read_file` had never been able to return an image at all; the multimodal commit
1065
+ * (`257333a`) was the USER path (composer → `/prompt`), not a tool.
1066
+ */
1067
+ const mime = IMAGE_MIME[probe.kind];
1068
+ if (mime) {
1069
+ const buf = await fs.readFile(p);
1070
+ const name = path.basename(p);
1071
+ return {
1072
+ ok: true,
1073
+ result: `${str(args.path)} is a ${probe.kind} (${probe.bytes} bytes) — the image itself is ` +
1074
+ `attached to this message, so you are looking at it. Describe or reason about it ` +
1075
+ `directly; there is no need to decode bytes.`,
1076
+ image: { url: `data:${mime};base64,${buf.toString("base64")}`, name },
1077
+ };
1078
+ }
1048
1079
  return {
1049
1080
  ok: false,
1050
1081
  result: `${str(args.path)} is a ${probe.kind} (${probe.bytes} bytes), not text — read_file returns ` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.37.67",
3
+ "version": "0.37.69",
4
4
  "description": "A lightweight, always-on multi-agent harness for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",