From 17cadcc046f671dc818a8d81382743b0e6bbd2b6 Mon Sep 17 00:00:00 2001 From: Nico Bailon Date: Sat, 20 Dec 2025 11:02:22 -0800 Subject: [PATCH] fix: send Slack image attachments inline to LLM --- packages/mom/src/agent.ts | 51 +++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/mom/src/agent.ts b/packages/mom/src/agent.ts index 50261e10..9959085b 100644 --- a/packages/mom/src/agent.ts +++ b/packages/mom/src/agent.ts @@ -1,4 +1,4 @@ -import { Agent, type AgentEvent, ProviderTransport } from "@mariozechner/pi-agent-core"; +import { Agent, type AgentEvent, type Attachment, ProviderTransport } from "@mariozechner/pi-agent-core"; import { getModel } from "@mariozechner/pi-ai"; import { AgentSession, @@ -7,7 +7,7 @@ import { messageTransformer, type Skill, } from "@mariozechner/pi-coding-agent"; -import { existsSync, readFileSync } from "fs"; +import { existsSync, readFileSync, statSync } from "fs"; import { mkdir, writeFile } from "fs/promises"; import { join } from "path"; import { MomSessionManager, MomSettingsManager } from "./context.js"; @@ -64,6 +64,18 @@ function getAnthropicApiKey(): string { return key; } +const IMAGE_MIME_TYPES: Record = { + jpg: "image/jpeg", + jpeg: "image/jpeg", + png: "image/png", + gif: "image/gif", + webp: "image/webp", +}; + +function getImageMimeType(filename: string): string | undefined { + return IMAGE_MIME_TYPES[filename.toLowerCase().split(".").pop() || ""]; +} + function getMemory(channelDir: string): string { const parts: string[] = []; @@ -716,10 +728,34 @@ function createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDi const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`; let userMessage = `[${timestamp}] [${ctx.message.userName || "unknown"}]: ${ctx.message.text}`; - // Add attachment paths if any (convert to absolute paths in execution environment) - if (ctx.message.attachments && ctx.message.attachments.length > 0) { - const attachmentPaths = ctx.message.attachments.map((a) => `${workspacePath}/${a.local}`).join("\n"); - userMessage += `\n\n\n${attachmentPaths}\n`; + const imageAttachments: Attachment[] = []; + const nonImagePaths: string[] = []; + + for (const a of ctx.message.attachments || []) { + const fullPath = `${workspacePath}/${a.local}`; + const mimeType = getImageMimeType(a.local); + + if (mimeType && existsSync(fullPath)) { + try { + const stats = statSync(fullPath); + imageAttachments.push({ + id: a.local, + type: "image", + fileName: a.local.split("/").pop() || a.local, + mimeType, + size: stats.size, + content: readFileSync(fullPath).toString("base64"), + }); + } catch { + nonImagePaths.push(fullPath); + } + } else { + nonImagePaths.push(fullPath); + } + } + + if (nonImagePaths.length > 0) { + userMessage += `\n\n\n${nonImagePaths.join("\n")}\n`; } // Debug: write context to last_prompt.jsonl @@ -727,10 +763,11 @@ function createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDi systemPrompt, messages: session.messages, newUserMessage: userMessage, + imageAttachmentCount: imageAttachments.length, }; await writeFile(join(channelDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2)); - await session.prompt(userMessage); + await session.prompt(userMessage, imageAttachments.length > 0 ? { attachments: imageAttachments } : undefined); // Wait for queued messages await queueChain;