diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 7529eb00..776640a6 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -842,6 +842,23 @@ export const MODELS = { contextWindow: 200000, maxTokens: 100000, } satisfies Model<"openai-responses">, + "gpt-5.2-pro": { + id: "gpt-5.2-pro", + name: "GPT-5.2 Pro", + api: "openai-responses", + provider: "openai", + baseUrl: "https://api.openai.com/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 21, + output: 168, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 400000, + maxTokens: 128000, + } satisfies Model<"openai-responses">, "gpt-4-turbo": { id: "gpt-4-turbo", name: "GPT-4 Turbo", @@ -893,6 +910,23 @@ export const MODELS = { contextWindow: 200000, maxTokens: 100000, } satisfies Model<"openai-responses">, + "gpt-5.2-chat-latest": { + id: "gpt-5.2-chat-latest", + name: "GPT-5.2 Chat", + api: "openai-responses", + provider: "openai", + baseUrl: "https://api.openai.com/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 1.75, + output: 14, + cacheRead: 0.175, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 16384, + } satisfies Model<"openai-responses">, "gpt-5.1": { id: "gpt-5.1", name: "GPT-5.1", @@ -1182,6 +1216,23 @@ export const MODELS = { contextWindow: 400000, maxTokens: 272000, } satisfies Model<"openai-responses">, + "gpt-5.2": { + id: "gpt-5.2", + name: "GPT-5.2", + api: "openai-responses", + provider: "openai", + baseUrl: "https://api.openai.com/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 1.75, + output: 14, + cacheRead: 0.175, + cacheWrite: 0, + }, + contextWindow: 400000, + maxTokens: 128000, + } satisfies Model<"openai-responses">, "gpt-5.1-chat-latest": { id: "gpt-5.1-chat-latest", name: "GPT-5.1 Chat", @@ -2093,6 +2144,23 @@ export const MODELS = { contextWindow: 128000, maxTokens: 128000, } satisfies Model<"openai-completions">, + "mistral-small-2506": { + id: "mistral-small-2506", + name: "Mistral Small 3.2", + api: "openai-completions", + provider: "mistral", + baseUrl: "https://api.mistral.ai/v1", + reasoning: false, + input: ["text", "image"], + cost: { + input: 0.1, + output: 0.3, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 16384, + } satisfies Model<"openai-completions">, "ministral-3b-latest": { id: "ministral-3b-latest", name: "Ministral 3B", @@ -2401,6 +2469,57 @@ export const MODELS = { } satisfies Model<"openai-completions">, }, openrouter: { + "openai/gpt-5.2-chat": { + id: "openai/gpt-5.2-chat", + name: "OpenAI: GPT-5.2 Chat", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text", "image"], + cost: { + input: 1.75, + output: 14, + cacheRead: 0.175, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 16384, + } satisfies Model<"openai-completions">, + "openai/gpt-5.2-pro": { + id: "openai/gpt-5.2-pro", + name: "OpenAI: GPT-5.2 Pro", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 21, + output: 168, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 400000, + maxTokens: 128000, + } satisfies Model<"openai-completions">, + "openai/gpt-5.2": { + id: "openai/gpt-5.2", + name: "OpenAI: GPT-5.2", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 1.75, + output: 14, + cacheRead: 0.175, + cacheWrite: 0, + }, + contextWindow: 400000, + maxTokens: 128000, + } satisfies Model<"openai-completions">, "mistralai/devstral-2512:free": { id: "mistralai/devstral-2512:free", name: "Mistral: Devstral 2 2512 (free)", @@ -2475,7 +2594,7 @@ export const MODELS = { api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", - reasoning: true, + reasoning: false, input: ["text"], cost: { input: 0, @@ -2648,9 +2767,9 @@ export const MODELS = { reasoning: true, input: ["text"], cost: { - input: 0.26, - output: 0.39, - cacheRead: 0.19999999999999998, + input: 0.25, + output: 0.38, + cacheRead: 0.19, cacheWrite: 0, }, contextWindow: 163840, @@ -3192,13 +3311,13 @@ export const MODELS = { reasoning: true, input: ["text"], cost: { - input: 0.43, - output: 1.75, - cacheRead: 0.0799999993, + input: 0.44, + output: 1.76, + cacheRead: 0, cacheWrite: 0, }, - contextWindow: 202752, - maxTokens: 4096, + contextWindow: 204800, + maxTokens: 131072, } satisfies Model<"openai-completions">, "anthropic/claude-sonnet-4.5": { id: "anthropic/claude-sonnet-4.5", @@ -5512,23 +5631,6 @@ export const MODELS = { contextWindow: 200000, maxTokens: 8192, } satisfies Model<"openai-completions">, - "mistralai/ministral-3b": { - id: "mistralai/ministral-3b", - name: "Mistral: Ministral 3B", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: false, - input: ["text"], - cost: { - input: 0.04, - output: 0.04, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 131072, - maxTokens: 4096, - } satisfies Model<"openai-completions">, "mistralai/ministral-8b": { id: "mistralai/ministral-8b", name: "Mistral: Ministral 8B", @@ -5546,6 +5648,23 @@ export const MODELS = { contextWindow: 131072, maxTokens: 4096, } satisfies Model<"openai-completions">, + "mistralai/ministral-3b": { + id: "mistralai/ministral-3b", + name: "Mistral: Ministral 3B", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text"], + cost: { + input: 0.04, + output: 0.04, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 131072, + maxTokens: 4096, + } satisfies Model<"openai-completions">, "nvidia/llama-3.1-nemotron-70b-instruct": { id: "nvidia/llama-3.1-nemotron-70b-instruct", name: "NVIDIA: Llama 3.1 Nemotron 70B Instruct", @@ -5716,22 +5835,22 @@ export const MODELS = { contextWindow: 128000, maxTokens: 16384, } satisfies Model<"openai-completions">, - "meta-llama/llama-3.1-70b-instruct": { - id: "meta-llama/llama-3.1-70b-instruct", - name: "Meta: Llama 3.1 70B Instruct", + "meta-llama/llama-3.1-8b-instruct": { + id: "meta-llama/llama-3.1-8b-instruct", + name: "Meta: Llama 3.1 8B Instruct", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", reasoning: false, input: ["text"], cost: { - input: 0.39999999999999997, - output: 0.39999999999999997, + input: 0.02, + output: 0.03, cacheRead: 0, cacheWrite: 0, }, contextWindow: 131072, - maxTokens: 4096, + maxTokens: 16384, } satisfies Model<"openai-completions">, "meta-llama/llama-3.1-405b-instruct": { id: "meta-llama/llama-3.1-405b-instruct", @@ -5750,22 +5869,22 @@ export const MODELS = { contextWindow: 130815, maxTokens: 4096, } satisfies Model<"openai-completions">, - "meta-llama/llama-3.1-8b-instruct": { - id: "meta-llama/llama-3.1-8b-instruct", - name: "Meta: Llama 3.1 8B Instruct", + "meta-llama/llama-3.1-70b-instruct": { + id: "meta-llama/llama-3.1-70b-instruct", + name: "Meta: Llama 3.1 70B Instruct", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", reasoning: false, input: ["text"], cost: { - input: 0.02, - output: 0.03, + input: 0.39999999999999997, + output: 0.39999999999999997, cacheRead: 0, cacheWrite: 0, }, contextWindow: 131072, - maxTokens: 16384, + maxTokens: 4096, } satisfies Model<"openai-completions">, "mistralai/mistral-nemo": { id: "mistralai/mistral-nemo", @@ -5903,6 +6022,23 @@ export const MODELS = { contextWindow: 128000, maxTokens: 4096, } satisfies Model<"openai-completions">, + "openai/gpt-4o-2024-05-13": { + id: "openai/gpt-4o-2024-05-13", + name: "OpenAI: GPT-4o (2024-05-13)", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text", "image"], + cost: { + input: 5, + output: 15, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 4096, + } satisfies Model<"openai-completions">, "openai/gpt-4o": { id: "openai/gpt-4o", name: "OpenAI: GPT-4o", @@ -5937,22 +6073,22 @@ export const MODELS = { contextWindow: 128000, maxTokens: 64000, } satisfies Model<"openai-completions">, - "openai/gpt-4o-2024-05-13": { - id: "openai/gpt-4o-2024-05-13", - name: "OpenAI: GPT-4o (2024-05-13)", + "meta-llama/llama-3-70b-instruct": { + id: "meta-llama/llama-3-70b-instruct", + name: "Meta: Llama 3 70B Instruct", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", reasoning: false, - input: ["text", "image"], + input: ["text"], cost: { - input: 5, - output: 15, + input: 0.3, + output: 0.39999999999999997, cacheRead: 0, cacheWrite: 0, }, - contextWindow: 128000, - maxTokens: 4096, + contextWindow: 8192, + maxTokens: 16384, } satisfies Model<"openai-completions">, "meta-llama/llama-3-8b-instruct": { id: "meta-llama/llama-3-8b-instruct", @@ -5971,23 +6107,6 @@ export const MODELS = { contextWindow: 8192, maxTokens: 16384, } satisfies Model<"openai-completions">, - "meta-llama/llama-3-70b-instruct": { - id: "meta-llama/llama-3-70b-instruct", - name: "Meta: Llama 3 70B Instruct", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: false, - input: ["text"], - cost: { - input: 0.3, - output: 0.39999999999999997, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 8192, - maxTokens: 16384, - } satisfies Model<"openai-completions">, "mistralai/mixtral-8x22b-instruct": { id: "mistralai/mixtral-8x22b-instruct", name: "Mistral: Mixtral 8x22B Instruct", @@ -6073,23 +6192,6 @@ export const MODELS = { contextWindow: 128000, maxTokens: 4096, } satisfies Model<"openai-completions">, - "openai/gpt-4-turbo-preview": { - id: "openai/gpt-4-turbo-preview", - name: "OpenAI: GPT-4 Turbo Preview", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: false, - input: ["text"], - cost: { - input: 10, - output: 30, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 128000, - maxTokens: 4096, - } satisfies Model<"openai-completions">, "openai/gpt-3.5-turbo-0613": { id: "openai/gpt-3.5-turbo-0613", name: "OpenAI: GPT-3.5 Turbo (older v0613)", @@ -6107,6 +6209,23 @@ export const MODELS = { contextWindow: 4095, maxTokens: 4096, } satisfies Model<"openai-completions">, + "openai/gpt-4-turbo-preview": { + id: "openai/gpt-4-turbo-preview", + name: "OpenAI: GPT-4 Turbo Preview", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text"], + cost: { + input: 10, + output: 30, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 4096, + } satisfies Model<"openai-completions">, "mistralai/mistral-tiny": { id: "mistralai/mistral-tiny", name: "Mistral Tiny", @@ -6175,23 +6294,6 @@ export const MODELS = { contextWindow: 16385, maxTokens: 4096, } satisfies Model<"openai-completions">, - "openai/gpt-3.5-turbo": { - id: "openai/gpt-3.5-turbo", - name: "OpenAI: GPT-3.5 Turbo", - api: "openai-completions", - provider: "openrouter", - baseUrl: "https://openrouter.ai/api/v1", - reasoning: false, - input: ["text"], - cost: { - input: 0.5, - output: 1.5, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 16385, - maxTokens: 4096, - } satisfies Model<"openai-completions">, "openai/gpt-4-0314": { id: "openai/gpt-4-0314", name: "OpenAI: GPT-4 (older v0314)", @@ -6226,6 +6328,23 @@ export const MODELS = { contextWindow: 8191, maxTokens: 4096, } satisfies Model<"openai-completions">, + "openai/gpt-3.5-turbo": { + id: "openai/gpt-3.5-turbo", + name: "OpenAI: GPT-3.5 Turbo", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: false, + input: ["text"], + cost: { + input: 0.5, + output: 1.5, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 16385, + maxTokens: 4096, + } satisfies Model<"openai-completions">, "openrouter/auto": { id: "openrouter/auto", name: "OpenRouter: Auto Router", diff --git a/packages/mom/src/agent.ts b/packages/mom/src/agent.ts index b0af9387..24cc4f8c 100644 --- a/packages/mom/src/agent.ts +++ b/packages/mom/src/agent.ts @@ -576,9 +576,9 @@ function createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDi // Format: "[username]: message" so LLM knows who's talking let userMessage = `[${ctx.message.userName || "unknown"}]: ${ctx.message.text}`; - // Add attachment paths if any + // 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) => a.local).join("\n"); + const attachmentPaths = ctx.message.attachments.map((a) => `${workspacePath}/${a.local}`).join("\n"); userMessage += `\n\nAttachments:\n${attachmentPaths}`; } diff --git a/packages/mom/src/main.ts b/packages/mom/src/main.ts index d56ce0a8..af0dd3f9 100644 --- a/packages/mom/src/main.ts +++ b/packages/mom/src/main.ts @@ -100,7 +100,7 @@ function createSlackContext(event: SlackEvent, slack: SlackBot, state: ChannelSt userName: user?.userName, channel: event.channel, ts: event.ts, - attachments: [], + attachments: (event.attachments || []).map((a) => ({ local: a.local })), }, channelName: slack.getChannel(event.channel)?.name, store: state.store, @@ -243,10 +243,14 @@ const handler: MomHandler = { log.logStartup(workingDir, sandbox.type === "host" ? "host" : `docker:${sandbox.container}`); +// Shared store for attachment downloads (also used per-channel in getState) +const sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! }); + const bot = new SlackBotClass(handler, { appToken: MOM_SLACK_APP_TOKEN, botToken: MOM_SLACK_BOT_TOKEN, workingDir, + store: sharedStore, }); bot.start(); diff --git a/packages/mom/src/slack.ts b/packages/mom/src/slack.ts index c36747cf..5e68eaa2 100644 --- a/packages/mom/src/slack.ts +++ b/packages/mom/src/slack.ts @@ -3,6 +3,7 @@ import { WebClient } from "@slack/web-api"; import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs"; import { basename, join } from "path"; import * as log from "./log.js"; +import type { Attachment, ChannelStore } from "./store.js"; // ============================================================================ // Types @@ -14,7 +15,9 @@ export interface SlackEvent { ts: string; user: string; text: string; - files?: Array<{ name: string; url_private_download?: string; url_private?: string }>; + files?: Array<{ name?: string; url_private_download?: string; url_private?: string }>; + /** Processed attachments with local paths (populated after logUserMessage) */ + attachments?: Attachment[]; } export interface SlackUser { @@ -118,6 +121,7 @@ export class SlackBot { private webClient: WebClient; private handler: MomHandler; private workingDir: string; + private store: ChannelStore; private botUserId: string | null = null; private startupTs: string | null = null; // Messages older than this are just logged, not processed @@ -125,9 +129,13 @@ export class SlackBot { private channels = new Map(); private queues = new Map(); - constructor(handler: MomHandler, config: { appToken: string; botToken: string; workingDir: string }) { + constructor( + handler: MomHandler, + config: { appToken: string; botToken: string; workingDir: string; store: ChannelStore }, + ) { this.handler = handler; this.workingDir = config.workingDir; + this.store = config.store; this.socketClient = new SocketModeClient({ appToken: config.appToken }); this.webClient = new WebClient(config.botToken); } @@ -258,7 +266,8 @@ export class SlackBot { }; // SYNC: Log to log.jsonl (ALWAYS, even for old messages) - this.logUserMessage(slackEvent); + // Also downloads attachments in background and stores local paths + slackEvent.attachments = this.logUserMessage(slackEvent); // Only trigger processing for messages AFTER startup (not replayed old messages) if (this.startupTs && e.ts < this.startupTs) { @@ -336,7 +345,8 @@ export class SlackBot { }; // SYNC: Log to log.jsonl (ALL messages - channel chatter and DMs) - this.logUserMessage(slackEvent); + // Also downloads attachments in background and stores local paths + slackEvent.attachments = this.logUserMessage(slackEvent); // Only trigger processing for messages AFTER startup (not replayed old messages) if (this.startupTs && e.ts < this.startupTs) { @@ -371,9 +381,12 @@ export class SlackBot { /** * Log a user message to log.jsonl (SYNC) + * Downloads attachments in background via store */ - private logUserMessage(event: SlackEvent): void { + private logUserMessage(event: SlackEvent): Attachment[] { const user = this.users.get(event.user); + // Process attachments - queues downloads in background + const attachments = event.files ? this.store.processAttachments(event.channel, event.files, event.ts) : []; this.logToFile(event.channel, { date: new Date(parseFloat(event.ts) * 1000).toISOString(), ts: event.ts, @@ -381,9 +394,10 @@ export class SlackBot { userName: user?.userName, displayName: user?.displayName, text: event.text, - attachments: event.files?.map((f) => f.name) || [], + attachments, isBot: false, }); + return attachments; } // ========================================================================== @@ -464,6 +478,8 @@ export class SlackBot { const user = this.users.get(msg.user!); // Strip @mentions from text (same as live messages) const text = (msg.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim(); + // Process attachments - queues downloads in background + const attachments = msg.files ? this.store.processAttachments(channelId, msg.files, msg.ts!) : []; this.logToFile(channelId, { date: new Date(parseFloat(msg.ts!) * 1000).toISOString(), @@ -472,7 +488,7 @@ export class SlackBot { userName: isMomMessage ? undefined : user?.userName, displayName: isMomMessage ? undefined : user?.displayName, text, - attachments: msg.files?.map((f) => f.name) || [], + attachments, isBot: isMomMessage, }); }