Add Telegram file upload directive

This commit is contained in:
Codex
2026-06-08 01:44:48 +00:00
parent 739b6cd870
commit ac8d5c2803
3 changed files with 108 additions and 9 deletions

View File

@@ -26,11 +26,12 @@ const (
resumeThreadPageSize = 8
assistantStreamEditInterval = 1200 * time.Millisecond
assistantStreamInitialRunes = 24
telegramFileDirectiveStart = "<!-- telegram-file "
telegramPhotoDirectiveStart = "<!-- telegram-photo "
telegramThreadRenameDirectiveStart = "<!-- codex-thread-rename "
telegramThreadCWDDirectiveStart = "<!-- codex-thread-cwd "
telegramDirectiveEnd = " -->"
telegramPhotoCaptionLimit = 1024
telegramCaptionLimit = 1024
pictureMediaGroupLimit = 10
)
@@ -54,11 +55,17 @@ type Bot struct {
type assistantMessageSegment struct {
Text string
File *assistantFileDirective
Photo *assistantPhotoDirective
ThreadRename *assistantThreadRenameDirective
ThreadCWD *assistantThreadCWDDirective
}
type assistantFileDirective struct {
Path string `json:"path"`
Caption string `json:"caption,omitempty"`
}
type assistantPhotoDirective struct {
Path string `json:"path"`
Caption string `json:"caption,omitempty"`
@@ -2580,6 +2587,11 @@ func splitAssistantMessageSegments(text string) []assistantMessageSegment {
for _, line := range strings.SplitAfter(text, "\n") {
body := strings.TrimSuffix(line, "\n")
body = strings.TrimSuffix(body, "\r")
if directive, ok := parseAssistantFileDirectiveLine(body); ok {
flushVisible()
segments = append(segments, assistantMessageSegment{File: &directive})
continue
}
if directive, ok := parseAssistantPhotoDirectiveLine(body); ok {
flushVisible()
segments = append(segments, assistantMessageSegment{Photo: &directive})
@@ -2631,6 +2643,22 @@ func parseAssistantThreadCWDDirectiveLine(line string) (assistantThreadCWDDirect
return directive, true
}
func parseAssistantFileDirectiveLine(line string) (assistantFileDirective, bool) {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, telegramFileDirectiveStart) || !strings.HasSuffix(trimmed, telegramDirectiveEnd) {
return assistantFileDirective{}, false
}
raw := strings.TrimSuffix(strings.TrimPrefix(trimmed, telegramFileDirectiveStart), telegramDirectiveEnd)
raw = strings.TrimSpace(raw)
var directive assistantFileDirective
if err := json.Unmarshal([]byte(raw), &directive); err != nil {
return assistantFileDirective{}, false
}
directive.Path = strings.TrimSpace(directive.Path)
directive.Caption = strings.TrimSpace(directive.Caption)
return directive, true
}
func parseAssistantPhotoDirectiveLine(line string) (assistantPhotoDirective, bool) {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, telegramPhotoDirectiveStart) || !strings.HasSuffix(trimmed, telegramDirectiveEnd) {
@@ -2654,6 +2682,14 @@ func (b *Bot) sendAssistantText(ctx context.Context, threadID string, chatID int
return err
}
}
if segment.File != nil {
if err := b.sendAssistantFile(ctx, chatID, *segment.File); err != nil {
b.logger.Printf("send assistant file: %v", err)
if sendErr := b.sendLong(ctx, chatID, "Could not send file: "+err.Error()); sendErr != nil {
return sendErr
}
}
}
if segment.Photo != nil {
if err := b.sendAssistantPhoto(ctx, chatID, *segment.Photo); err != nil {
b.logger.Printf("send assistant photo: %v", err)
@@ -2721,6 +2757,25 @@ func (b *Bot) applyAssistantThreadCWD(ctx context.Context, threadID string, dire
return b.store.SetSessionWorkspace(ctx, thread.TelegramUserID, workspace.ID)
}
func (b *Bot) sendAssistantFile(ctx context.Context, chatID int64, directive assistantFileDirective) error {
path := strings.TrimSpace(directive.Path)
if path == "" {
return errors.New("file directive is missing a path")
}
if !filepath.IsAbs(path) {
return fmt.Errorf("file path must be absolute: %s", path)
}
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read %s: %v", filepath.Base(path), err)
}
caption := truncateTelegramCaption(directive.Caption)
if _, err := b.tg.SendDocumentBytes(ctx, chatID, path, data, caption); err != nil {
return fmt.Errorf("send %s: %v", filepath.Base(path), err)
}
return nil
}
func (b *Bot) sendAssistantPhoto(ctx context.Context, chatID int64, directive assistantPhotoDirective) error {
path := strings.TrimSpace(directive.Path)
if path == "" {
@@ -2736,22 +2791,22 @@ func (b *Bot) sendAssistantPhoto(ctx context.Context, chatID int64, directive as
if err != nil {
return fmt.Errorf("read %s: %v", filepath.Base(path), err)
}
caption := truncateTelegramPhotoCaption(directive.Caption)
caption := truncateTelegramCaption(directive.Caption)
if _, err := b.tg.SendPhotoBytes(ctx, chatID, path, data, caption); err != nil {
return fmt.Errorf("send %s: %v", filepath.Base(path), err)
}
return nil
}
func truncateTelegramPhotoCaption(caption string) string {
func truncateTelegramCaption(caption string) string {
runes := []rune(caption)
if len(runes) <= telegramPhotoCaptionLimit {
if len(runes) <= telegramCaptionLimit {
return caption
}
if telegramPhotoCaptionLimit <= 3 {
return string(runes[:telegramPhotoCaptionLimit])
if telegramCaptionLimit <= 3 {
return string(runes[:telegramCaptionLimit])
}
return string(runes[:telegramPhotoCaptionLimit-3]) + "..."
return string(runes[:telegramCaptionLimit-3]) + "..."
}
func assistantStreamPreview(text string) string {
@@ -2768,7 +2823,9 @@ func assistantStreamPreview(text string) string {
}
func isAssistantDirectiveStart(line string) bool {
return strings.HasPrefix(line, telegramPhotoDirectiveStart) ||
return strings.HasPrefix(line, telegramFileDirectiveStart) ||
strings.HasPrefix(line, strings.TrimSpace(telegramFileDirectiveStart)) ||
strings.HasPrefix(line, telegramPhotoDirectiveStart) ||
strings.HasPrefix(line, strings.TrimSpace(telegramPhotoDirectiveStart)) ||
strings.HasPrefix(line, telegramThreadRenameDirectiveStart) ||
strings.HasPrefix(line, strings.TrimSpace(telegramThreadRenameDirectiveStart)) ||