@@ -32,8 +32,9 @@ type TelegramCommand struct {
3232
3333 // sessions caches conversation-ID → session-ID mappings so we don't
3434 // query the API on every message.
35- mu sync.Mutex `kong:"-"`
36- sessions map [string ]string `kong:"-"`
35+ mu sync.Mutex `kong:"-"`
36+ sessions map [string ]string `kong:"-"`
37+ pendingOpts map [string ][]httpclient.ChatOpt `kong:"-"`
3738}
3839
3940///////////////////////////////////////////////////////////////////////////////
@@ -69,6 +70,20 @@ func (h *telegramHooks) OnSessionReset() {
6970 // Nothing extra needed — OnSessionChanged already updated the cache.
7071}
7172
73+ func (h * telegramHooks ) ResetMeta () * schema.SessionMeta {
74+ return & schema.SessionMeta {
75+ GeneratorMeta : schema.GeneratorMeta {
76+ SystemPrompt : h .cmd .SystemPrompt ,
77+ Thinking : h .cmd .Thinking ,
78+ ThinkingBudget : h .cmd .ThinkingBudget ,
79+ },
80+ Name : h .uctx .UserName (),
81+ Labels : map [string ]string {
82+ telegramChatLabel : h .conversationID ,
83+ },
84+ }
85+ }
86+
7287///////////////////////////////////////////////////////////////////////////////
7388// COMMANDS
7489
@@ -100,6 +115,7 @@ func (cmd *TelegramCommand) Run(ctx *Globals) (err error) {
100115 defer bot .Close ()
101116
102117 cmd .sessions = make (map [string ]string )
118+ cmd .pendingOpts = make (map [string ][]httpclient.ChatOpt )
103119
104120 ctx .logger .Print (parent , "Telegram bot started" )
105121
@@ -123,20 +139,43 @@ func (cmd *TelegramCommand) Run(ctx *Globals) (err error) {
123139 if err := cmd .handleChat (parent , evt , client , sessionID ); err != nil {
124140 evt .Context .SendText (parent , fmt .Sprintf ("Error: %v" , err ))
125141 }
142+ case ui .EventAttachment :
143+ ctx .logger .Print (parent , fmt .Sprintf ("Attachment received: %d file(s), caption=%q, conv=%s" ,
144+ len (evt .Attachments ), evt .Text , evt .Context .ConversationID ()))
145+ for i , att := range evt .Attachments {
146+ ctx .logger .Print (parent , fmt .Sprintf (" att[%d]: name=%q type=%q" , i , att .Filename , att .Type ))
147+ }
148+ sessionID , err := cmd .resolveSession (parent , ctx , client , evt .Context )
149+ if err != nil {
150+ evt .Context .SendText (parent , fmt .Sprintf ("Error: %v" , err ))
151+ continue
152+ }
153+ // Queue attachments as pending file opts.
154+ convID := evt .Context .ConversationID ()
155+ for _ , att := range evt .Attachments {
156+ name := att .Filename
157+ if name == "" {
158+ name = "attachment"
159+ }
160+ cmd .mu .Lock ()
161+ cmd .pendingOpts [convID ] = append (cmd .pendingOpts [convID ], httpclient .WithChatFile (name , att .Data ))
162+ cmd .mu .Unlock ()
163+ }
164+ // If the attachment has caption text, send it as a chat message immediately.
165+ if evt .Text != "" {
166+ if err := cmd .handleChat (parent , evt , client , sessionID ); err != nil {
167+ evt .Context .SendText (parent , fmt .Sprintf ("Error: %v" , err ))
168+ }
169+ } else {
170+ evt .Context .SendText (parent , fmt .Sprintf ("Attached %d file(s). Send a message to use them." , len (evt .Attachments )))
171+ }
126172 case ui .EventCommand :
127173 sessionID , err := cmd .resolveSession (parent , ctx , client , evt .Context )
128174 if err != nil {
129175 evt .Context .SendText (parent , fmt .Sprintf ("Error: %v" , err ))
130176 continue
131177 }
132- cmdHandler := uicmd .New (client , & telegramHooks {
133- cmd : cmd ,
134- globals : ctx ,
135- client : client ,
136- conversationID : evt .Context .ConversationID (),
137- uctx : evt .Context ,
138- })
139- if err := cmdHandler .Handle (parent , evt , & sessionID ); err != nil {
178+ if err := cmd .handleTelegramCommand (parent , evt , client , ctx , & sessionID ); err != nil {
140179 evt .Context .SendText (parent , fmt .Sprintf ("Error: %v" , err ))
141180 }
142181 }
@@ -198,16 +237,51 @@ func (cmd *TelegramCommand) resolveSession(ctx context.Context, globals *Globals
198237 return session .ID , nil
199238}
200239
240+ // handleTelegramCommand processes slash commands for Telegram. It handles
241+ // /url locally and delegates everything else to the shared command handler.
242+ func (cmd * TelegramCommand ) handleTelegramCommand (ctx context.Context , evt ui.Event , client * httpclient.Client , globals * Globals , sessionID * string ) error {
243+ switch evt .Command {
244+ case "url" :
245+ if len (evt .Args ) == 0 {
246+ return evt .Context .SendText (ctx , "Usage: /url <url>" )
247+ }
248+ u := evt .Args [0 ]
249+ convID := evt .Context .ConversationID ()
250+ cmd .mu .Lock ()
251+ cmd .pendingOpts [convID ] = append (cmd .pendingOpts [convID ], httpclient .WithChatURL (u ))
252+ cmd .mu .Unlock ()
253+ return evt .Context .SendText (ctx , fmt .Sprintf ("Attached URL: %s\n Send a message to use it." , u ))
254+ default :
255+ cmdHandler := uicmd .New (client , & telegramHooks {
256+ cmd : cmd ,
257+ globals : globals ,
258+ client : client ,
259+ conversationID : evt .Context .ConversationID (),
260+ uctx : evt .Context ,
261+ })
262+ return cmdHandler .Handle (ctx , evt , sessionID )
263+ }
264+ }
265+
201266func (cmd * TelegramCommand ) handleChat (ctx context.Context , evt ui.Event , client * httpclient.Client , sessionID string ) error {
202267 evt .Context .SetTyping (ctx , true )
203268 evt .Context .StreamStart (ctx )
204269
205- opts := []httpclient.ChatOpt {
206- httpclient .WithChatStream (func (role , text string ) {
207- evt .Context .StreamChunk (ctx , role , text )
208- }),
270+ // Consume any pending attachments for this conversation.
271+ convID := evt .Context .ConversationID ()
272+ cmd .mu .Lock ()
273+ pending := cmd .pendingOpts [convID ]
274+ delete (cmd .pendingOpts , convID )
275+ cmd .mu .Unlock ()
276+
277+ if len (pending ) > 0 {
278+ fmt .Printf ("[telegram] handleChat: consuming %d pending opts for conv=%s\n " , len (pending ), convID )
209279 }
210280
281+ opts := append (pending , httpclient .WithChatStream (func (role , text string ) {
282+ evt .Context .StreamChunk (ctx , role , text )
283+ }))
284+
211285 req := schema.ChatRequest {
212286 Session : sessionID ,
213287 Text : evt .Text ,
0 commit comments