import { type AgentAttachment, type AgentMessageSender, type AgentRequestBase, type AgentResponder as SharedAgentResponder, type AgentResponse } from "@mono-agent/agent-contracts"; import { TelegramMessageStream, type AgentMessageStream, type TelegramMessageStreamLogger } from "./message-stream.js"; import { type TelegramTranscriber, type TelegramTranscriptionConfig } from "./transcription.js"; import type { TelegramChatId, TelegramMessage, TelegramUpdate } from "./types.js"; export type TelegramAttachmentKind = "document" | "photo" | "audio" | "video" | "video_note" | "voice"; export interface TelegramAttachmentBase { kind: TelegramAttachmentKind; fileId: string; fileUniqueId: string; fileSize?: number; } export interface TelegramDocumentAttachment extends TelegramAttachmentBase { kind: "document"; fileName?: string; mimeType?: string; } export interface TelegramPhotoAttachmentSize { fileId: string; fileUniqueId: string; width: number; height: number; fileSize?: number; } export interface TelegramPhotoAttachment extends TelegramAttachmentBase { kind: "photo"; width: number; height: number; sizes: readonly TelegramPhotoAttachmentSize[]; } export interface TelegramAudioAttachment extends TelegramAttachmentBase { kind: "audio"; duration: number; fileName?: string; mimeType?: string; } export interface TelegramVideoAttachment extends TelegramAttachmentBase { kind: "video"; duration: number; width: number; height: number; fileName?: string; mimeType?: string; } export interface TelegramVoiceAttachment extends TelegramAttachmentBase { kind: "voice"; duration: number; mimeType?: string; } export interface TelegramVideoNoteAttachment extends TelegramAttachmentBase { kind: "video_note"; duration: number; /** Diameter (width == height) of the square round video, in pixels. */ length: number; } export type TelegramAttachment = TelegramDocumentAttachment | TelegramPhotoAttachment | TelegramAudioAttachment | TelegramVideoAttachment | TelegramVideoNoteAttachment | TelegramVoiceAttachment; export interface TelegramAgentMessageInput { text: string; attachments: readonly TelegramAttachment[]; } export interface AgentRequest extends AgentRequestBase { conversationId: string; chatId: TelegramChatId; messageId: number; updateId: number; userId?: number; username?: string; text: string; /** * Downloaded attachment bytes, ready for a vision/document-aware runtime, in * the transport-agnostic {@link AgentAttachment} shape (base64 data + mime + * name). * * BREAKING (intentional, unified contract): on earlier versions this field * held Telegram-specific `TelegramAttachment[]` metadata. It is now * `AgentAttachment[]` (matching Slack and the OpenAI-compatible channel). The * original Telegram file metadata (fileId, sizes, kind, …) is preserved under * `metadata.telegram.attachments` — custom responders that filtered by * `fileId`/`sizes`/`kind` should read it from there. */ attachments?: readonly AgentAttachment[]; /** Model-visible speaker identity; `sender.id` stays host-only. */ sender?: AgentMessageSender; abortSignal: AbortSignal; metadata: { telegram: TelegramRequestMetadata; [key: string]: unknown; }; } export interface TelegramRequestMetadata { updateId: number; /** Per-chat model selected through Telegram runtime controls. */ model?: string; /** Per-chat effort selected through Telegram runtime controls. */ effort?: string; chat: { id: TelegramChatId; type?: string; title?: string; username?: string; }; message: { id: number; date?: number; }; attachments?: readonly TelegramAttachment[]; from?: { id: number; isBot?: boolean; username?: string; firstName?: string; lastName?: string; languageCode?: string; }; } export type { AgentResponse }; export type AgentResponder = SharedAgentResponder; export interface TelegramAdapterMessages { welcomeText?: string; helpText?: string; busyText?: string; unauthorizedText?: string; cancelledText?: string; newSessionText?: string; newSessionErrorText?: string; errorText?: TelegramAdapterErrorText; unsupportedText?: string; } export type TelegramAdapterErrorText = string | ((input: TelegramAdapterErrorTextInput) => string | Promise); export interface TelegramAdapterErrorTextInput { readonly error: unknown; readonly request: AgentRequest; } export interface TelegramAdapterStreamOptions { initialStatusText?: string; editDebounceMs?: number; maxMessageChars?: number; maxSendRetries?: number; retryCapMs?: number; retryBaseDelayMs?: number; showHints?: boolean; formatMarkdown?: boolean; /** * Deliver only the final answer with a "typing…" indicator while working, * instead of streaming interim edits. When tools produced a progress message, * the final answer is posted separately and that progress message is removed. * Defaults to true for the Telegram bot. */ finalOnly?: boolean; } export interface TelegramAdapterLogger extends TelegramMessageStreamLogger { info?(message: string, metadata?: Record): void; } export declare const DEFAULT_ERROR_TEXT = "The agent failed while processing your message."; export declare const DEFAULT_MESSAGES: Required; /** * Build the responder-facing {@link AgentRequest} from a Telegram update. The * grammY message handler passes `ctx.update` and `ctx.message`, which are * structurally compatible with the wire types this reads. * * `resolvedAttachments` are the downloaded {@link AgentAttachment} bytes (when * available) that populate `request.attachments`; the original Telegram file * metadata is always preserved under `metadata.telegram.attachments`. */ export declare function buildAgentRequest(update: TelegramUpdate, message: TelegramMessage, input: TelegramAgentMessageInput, abortSignal: AbortSignal, resolvedAttachments?: readonly AgentAttachment[], /** Effective per-message budget for this chat, so the surface can state it. */ maxMessageChars?: number): AgentRequest; export declare function normalizeTelegramMessageInput(message: TelegramMessage): TelegramAgentMessageInput | undefined; /** * Merge a Telegram media-group (album) into a single input: Telegram delivers an * album of N photos/videos as N separate messages sharing one `media_group_id`, * with the caption on only one of them. We concatenate every message's * attachments and take the single caption (first non-empty), so the agent sees * all photos as one request instead of N single-attachment turns. */ export declare function mergeTelegramMessageInputs(messages: readonly TelegramMessage[]): TelegramAgentMessageInput | undefined; /** Telegram-compatible aliases retained for existing adapter consumers. */ export declare const DEFAULT_ATTACHMENT_MAX_BYTES: number; export declare const DEFAULT_ATTACHMENT_MIME_ALLOWLIST: readonly string[]; /** * Minimal seam over the Telegram Bot API needed to fetch attachment bytes: * resolve a `file_id` to a `file_path` (getFile) then download it from the file * URL. Both calls honor the request `abortSignal`. */ export interface TelegramFileDownloader { /** Resolve a `file_id` to a downloadable `file_path` (Bot API `getFile`). */ resolveFilePath(fileId: string, signal: AbortSignal): Promise; /** * Download the file at `file_path` (GET on the file URL). `maxBytes`, when * provided, lets the downloader abort an oversized transfer mid-stream instead * of buffering the whole body first; the caller still re-checks the cap as a * backstop, so a custom downloader that ignores `maxBytes` stays bounded. */ download(filePath: string, signal: AbortSignal, maxBytes?: number): Promise; } export interface DownloadTelegramAttachmentsOptions { /** Skip files larger than this many decoded bytes. Default ~20 MB. */ readonly maxBytes?: number; /** Only download files whose MIME type is allowed. Defaults to images + common docs/text. */ readonly mimeAllowlist?: readonly string[]; /** * Per-file download timeout (ms) for the default downloader, composed with the * run abort signal. Defaults to 30000. Only consulted by the built-in * downloader; custom downloaders manage their own timeouts. */ readonly downloadTimeoutMs?: number; /** * Auto-transcription config for inbound audio (voice / audio / video_note). When * set (and no {@link transcriber} seam is supplied), a default OpenAI-compatible * transcriber is built from it once and used to fill each audio attachment's * `text` with the transcript. Omit to leave audio as an on-disk file only. */ readonly transcription?: TelegramTranscriptionConfig; /** * Test/override seam for the transcriber (mirrors the downloader seam). When * present it wins over {@link transcription}; when absent the config builds the * default transcriber. With neither, audio is never transcribed. */ readonly transcriber?: TelegramTranscriber; readonly logger?: TelegramAdapterLogger; } /** * Download the bytes for each inbound {@link TelegramAttachment} and map them to * the transport-agnostic {@link AgentAttachment} shape. Enforces a byte cap and a * MIME allowlist, ties every request to `abortSignal`, and skips (never throws on) * an attachment whose download fails so the run still proceeds. Photos and audio * without a declared MIME type fall back to sensible defaults. */ export declare function downloadTelegramAttachments(attachments: readonly TelegramAttachment[], downloader: TelegramFileDownloader, abortSignal: AbortSignal, options?: DownloadTelegramAttachmentsOptions): Promise; /** The text inlined when transcription fails, pointing at the saved audio file. */ export declare const TELEGRAM_TRANSCRIPTION_UNAVAILABLE_NOTE = "[automatic transcription unavailable \u2014 audio saved at the path above]"; /** * Deliver a terminal/system message (cancelled, error, …) in place. Such copy is * fixed text we author, not model output, so it is delivered as plain text * (`format: false`) — no MarkdownV2 escaping — while still reusing the stream's * resilient edit-or-recreate delivery. */ export declare function finishSafely(stream: TelegramMessageStream, text: string, logger: TelegramAdapterLogger | undefined): Promise; export declare function resolveErrorText(input: { readonly configured: TelegramAdapterErrorText; readonly error: unknown; readonly request: AgentRequest; readonly logger: TelegramAdapterLogger | undefined; }): Promise; //# sourceMappingURL=adapter.d.ts.map