11import { Bot , Context } from "grammy" ;
2+ import type { FilePartInput , TextPartInput } from "@opencode-ai/sdk/v2" ;
23import { opencodeClient } from "../../opencode/client.js" ;
34import { clearSession , getCurrentSession , setCurrentSession } from "../../session/manager.js" ;
45import { ingestSessionInfoForCache } from "../../session/cache-manager.js" ;
@@ -77,14 +78,19 @@ export interface ProcessPromptDeps {
7778
7879/**
7980 * Processes a user prompt: ensures project/session, subscribes to events, and sends
80- * the prompt to OpenCode. Used by both text and voice message handlers.
81+ * the prompt to OpenCode. Used by text, voice, and photo message handlers.
8182 *
83+ * @param ctx - Grammy context
84+ * @param text - Text content of the prompt
85+ * @param deps - Dependencies (bot and event subscription)
86+ * @param fileParts - Optional file parts (for photo/document attachments)
8287 * @returns true if the prompt was dispatched, false if it was blocked/failed early.
8388 */
8489export async function processUserPrompt (
8590 ctx : Context ,
8691 text : string ,
8792 deps : ProcessPromptDeps ,
93+ fileParts : FilePartInput [ ] = [ ] ,
8894) : Promise < boolean > {
8995 const { bot, ensureEventSubscription } = deps ;
9096
@@ -193,17 +199,36 @@ export async function processUserPrompt(
193199 const currentAgent = getStoredAgent ( ) ;
194200 const storedModel = getStoredModel ( ) ;
195201
202+ // Build parts array with text and files
203+ const parts : Array < TextPartInput | FilePartInput > = [ ] ;
204+
205+ // Add text part if present
206+ if ( text . trim ( ) . length > 0 ) {
207+ parts . push ( { type : "text" , text } ) ;
208+ }
209+
210+ // Add file parts
211+ parts . push ( ...fileParts ) ;
212+
213+ // If no text and files exist, use a placeholder
214+ if ( parts . length === 0 || ( parts . length > 0 && parts . every ( ( p ) => p . type === "file" ) ) ) {
215+ if ( fileParts . length > 0 ) {
216+ // Files without text - add a minimal system prompt
217+ parts . unshift ( { type : "text" , text : "See attached file" } ) ;
218+ }
219+ }
220+
196221 const promptOptions : {
197222 sessionID : string ;
198223 directory : string ;
199- parts : Array < { type : "text" ; text : string } > ;
224+ parts : Array < TextPartInput | FilePartInput > ;
200225 model ?: { providerID : string ; modelID : string } ;
201226 agent ?: string ;
202227 variant ?: string ;
203228 } = {
204229 sessionID : currentSession . id ,
205230 directory : currentSession . directory ,
206- parts : [ { type : "text" , text } ] ,
231+ parts,
207232 agent : currentAgent ,
208233 } ;
209234
@@ -228,9 +253,12 @@ export async function processUserPrompt(
228253 modelId : storedModel . modelID || "default" ,
229254 variant : storedModel . variant || "default" ,
230255 promptLength : text . length ,
256+ fileCount : fileParts . length ,
231257 } ;
232258
233- logger . info ( `[Bot] Calling session.prompt (fire-and-forget) with agent=${ currentAgent } ...` ) ;
259+ logger . info (
260+ `[Bot] Calling session.prompt (fire-and-forget) with agent=${ currentAgent } , fileCount=${ fileParts . length } ...` ,
261+ ) ;
234262
235263 // CRITICAL: DO NOT wait for session.prompt to complete.
236264 // If we wait, the handler will not finish and grammY will not call getUpdates,
0 commit comments