### Quickstart: Stream Text with grammY Source: https://github.com/grammyjs/stream/blob/main/README.md This snippet demonstrates how to set up a grammY bot with the stream plugin to send a generator yielding slow text. Ensure you have installed `grammy`, `@grammyjs/stream`, and `@grammyjs/auto-retry`. The `autoRetry` middleware is applied globally, and the `stream` plugin is then used. ```typescript import { Bot, type Context } from "grammy"; import { autoRetry } from "@grammyjs/auto-retry"; import { stream, type StreamFlavor } from "@grammyjs/stream"; type MyContext = StreamFlavor; const bot = new Bot(""); bot.api.config.use(autoRetry()); bot.use(stream()); async function* slowText() { // emulate slow text generation yield "This i"; await new Promise((r) => setTimeout(r, 1000)); yield "s some sl"; await new Promise((r) => setTimeout(r, 1000)); yield "owly gene"; await new Promise((r) => setTimeout(r, 1000)); yield "rated text"; } bot.command("stream", async (ctx) => { await ctx.replyWithStream(slowText()); }); bot.command("start", (ctx) => ctx.reply("Hi! Send /stream")); bot.use((ctx) => ctx.reply("What a nice update.")); bot.start(); ``` -------------------------------- ### Install plugin middleware `stream(options?)` Source: https://context7.com/grammyjs/stream/llms.txt The `stream()` function returns a grammY middleware that registers `ctx.replyWithStream` on the context and `ctx.api.streamMessage` on the API object. It must be installed on the bot before handlers that use streaming. The optional `options` object accepts a `defaultDraftIdOffset` function that computes a unique integer offset per update to prevent draft ID collisions across concurrent handlers. ```APIDOC ## `stream(options?)` — Install plugin middleware The `stream()` function returns a grammY middleware that registers `ctx.replyWithStream` on the context and `ctx.api.streamMessage` on the API object. It must be installed on the bot before handlers that use streaming. The optional `options` object accepts a `defaultDraftIdOffset` function that computes a unique integer offset per update to prevent draft ID collisions across concurrent handlers. ```ts import { Bot, type Context } from "grammy"; import { autoRetry } from "@grammyjs/auto-retry"; import { stream, type StreamFlavor } from "@grammyjs/stream"; // Extend context type with StreamFlavor type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); // Install auto-retry BEFORE stream to handle Telegram rate limits gracefully bot.api.config.use(autoRetry()); // Install stream middleware with default options bot.use(stream()); // Custom draft ID offset (useful when calling replyWithStream multiple times per update) bot.use(stream({ defaultDraftIdOffset: (ctx) => ctx.update.update_id * 1000, })); bot.start(); ``` ``` -------------------------------- ### Install Stream Plugin Middleware Source: https://context7.com/grammyjs/stream/llms.txt Install the stream middleware before handlers that use streaming. Ensure auto-retry is installed BEFORE stream to handle Telegram rate limits gracefully. ```typescript import { Bot, type Context } from "grammy"; import { autoRetry } from "@grammyjs/auto-retry"; import { stream, type StreamFlavor } from "@grammyjs/stream"; // Extend context type with StreamFlavor type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); // Install auto-retry BEFORE stream to handle Telegram rate limits gracefully bot.api.config.use(autoRetry()); // Install stream middleware with default options bot.use(stream()); // Custom draft ID offset (useful when calling replyWithStream multiple times per update) bot.use(stream({ defaultDraftIdOffset: (ctx) => ctx.update.update_id * 1000, })); bot.start(); ``` -------------------------------- ### Stream LLM Output with AI SDK and grammY Source: https://github.com/grammyjs/stream/blob/main/README.md This example shows how to integrate the grammY stream plugin with the AI SDK to stream responses from a language model. It uses `streamText` from the AI SDK and then pipes the resulting stream to `ctx.replyWithStream` for animated message delivery in Telegram. ```typescript import { streamText } from "ai"; import { google } from "@ai-sdk/google"; bot.command("credits", async (ctx) => { // Send prompt to LLM: const { textStream } = streamText({ model: google("gemini-2.5-flash"), prompt: "How cool are grammY bots?", }); // Automatically stream response with grammY: await ctx.replyWithStream(textStream); }); ``` -------------------------------- ### Integrate Stream Plugin with AI SDKs (OpenAI GPT) Source: https://context7.com/grammyjs/stream/llms.txt Integrate the `@grammyjs/stream` plugin with the Vercel AI SDK to stream responses from OpenAI GPT models. Ensure `@grammyjs/auto-retry` is registered before using the stream plugin. The `streamText` function from the AI SDK returns an async iterable that can be passed directly to `ctx.replyWithStream`. ```typescript import { Bot, type Context } from "grammy"; import { autoRetry } from "@grammyjs/auto-retry"; import { stream, type StreamFlavor } from "@grammyjs/stream"; // Vercel AI SDK import { streamText } from "ai"; import { openai } from "@ai-sdk/openai"; type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); bot.api.config.use(autoRetry()); bot.use(stream()); // OpenAI GPT bot.command("gpt", async (ctx) => { const { textStream } = streamText({ model: openai("gpt-4o"), messages: [{ role: "user", content: ctx.match ?? "Hello!" }], }); await ctx.replyWithStream(textStream); }); bot.start(); ``` -------------------------------- ### Integrate Stream Plugin with AI SDKs (Google Gemini) Source: https://context7.com/grammyjs/stream/llms.txt Integrate the `@grammyjs/stream` plugin with the Vercel AI SDK to stream responses from Google Gemini. Ensure `@grammyjs/auto-retry` is registered before using the stream plugin. The `streamText` function from the AI SDK returns an async iterable that can be passed directly to `ctx.replyWithStream`. ```typescript import { Bot, type Context } from "grammy"; import { autoRetry } from "@grammyjs/auto-retry"; import { stream, type StreamFlavor } from "@grammyjs/stream"; // Vercel AI SDK import { streamText } from "ai"; import { google } from "@ai-sdk/google"; type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); bot.api.config.use(autoRetry()); bot.use(stream()); // Google Gemini bot.command("gemini", async (ctx) => { const { textStream } = streamText({ model: google("gemini-2.5-flash"), prompt: ctx.match ?? "Explain quantum entanglement briefly.", }); const msgs = await ctx.replyWithStream(textStream); console.log(`Response split into ${msgs.length} message(s).`); }); bot.start(); ``` -------------------------------- ### streamApi Source: https://context7.com/grammyjs/stream/llms.txt Provides a low-level streaming interface for custom API instances. It returns a `StreamContextExtension["api"]` object with `.streamMessage` built directly on top of a `RawApi` instance. ```APIDOC ## `streamApi(rawApi)` — Low-level streaming for custom API instances Returns a `StreamContextExtension["api"]` object (with `.streamMessage`) built directly on top of a `RawApi` instance. This is the underlying engine used by the middleware. Use this when you have a custom `Api` object or are working outside a standard grammY bot context. ### Parameters - **rawApi** (RawApi) - The raw API instance to build the streaming interface upon. ``` -------------------------------- ### Integrate Stream Plugin with AI SDKs (Anthropic Claude) Source: https://context7.com/grammyjs/stream/llms.txt Integrate the `@grammyjs/stream` plugin with the Vercel AI SDK to stream responses from Anthropic Claude models. Ensure `@grammyjs/auto-retry` is registered before using the stream plugin. The `streamText` function from the AI SDK returns an async iterable that can be passed directly to `ctx.replyWithStream`. ```typescript import { Bot, type Context } from "grammy"; import { autoRetry } from "@grammyjs/auto-retry"; import { stream, type StreamFlavor } from "@grammyjs/stream"; // Vercel AI SDK import { streamText } from "ai"; import { anthropic } from "@ai-sdk/anthropic"; type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); bot.api.config.use(autoRetry()); bot.use(stream()); // Anthropic Claude bot.command("claude", async (ctx) => { const { textStream } = streamText({ model: anthropic("claude-opus-4-5"), prompt: ctx.match ?? "Write a haiku about Telegram bots.", }); await ctx.replyWithStream(textStream); }); bot.start(); ``` -------------------------------- ### Low-level streaming with streamApi Source: https://context7.com/grammyjs/stream/llms.txt Use `streamApi` to create a streaming API instance directly from a `RawApi` object. This is useful for custom API instances or when working outside a standard grammY bot context. The returned `streamMessage` function allows direct streaming to a known chat. ```typescript import { Api, Bot } from "grammy"; import { streamApi, type MessageDraftPiece } from "@grammyjs/stream"; // Construct a raw API instance independently const api = new Api(process.env.BOT_TOKEN!); const { streamMessage } = streamApi(api.raw); async function* chunks(): AsyncGenerator { yield "Chunk one. "; await new Promise((r) => setTimeout(r, 500)); yield "Chunk two. "; await new Promise((r) => setTimeout(r, 500)); yield "Chunk three."; } // Stream directly to a known chat const messages = await streamMessage( 987654321, // chat_id 0, // draft_id_offset chunks(), ); console.log(`Sent messages:`, messages.map((m) => m.message_id)); // Output: Sent messages: [42, 43] (if content exceeded 4096 chars, else [42]) ``` -------------------------------- ### `ctx.replyWithStream(stream, otherMessageDraft?, otherMessage?, signal?)` — Stream reply to current chat Source: https://context7.com/grammyjs/stream/llms.txt Streams an iterable of message pieces to the current chat, automatically managing draft updates and final message sends. Returns a `Promise` — one entry per auto-split message. Entity offsets must be relative to the beginning of the **entire** data stream, not individual chunks. ```APIDOC ## `ctx.replyWithStream(stream, otherMessageDraft?, otherMessage?, signal?)` — Stream reply to current chat Streams an iterable of message pieces to the current chat, automatically managing draft updates and final message sends. Returns a `Promise` — one entry per auto-split message. Entity offsets must be relative to the beginning of the **entire** data stream, not individual chunks. ```ts import { Bot, type Context } from "grammy"; import { stream, type StreamFlavor, type MessageDraftPiece } from "@grammyjs/stream"; type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); bot.use(stream()); // --- Example 1: Simple generator simulating slow text output --- async function* slowText(): AsyncGenerator { yield "This i"; await new Promise((r) => setTimeout(r, 1000)); yield "s some sl"; await new Promise((r) => setTimeout(r, 1000)); yield "owly gene"; await new Promise((r) => setTimeout(r, 1000)); yield "rated text"; } bot.command("demo", async (ctx) => { // Streams the generator; Telegram users see the message update live const messages = await ctx.replyWithStream(slowText()); console.log(`Sent ${messages.length} message(s)`); // Output: Sent 1 message(s) }); // --- Example 2: Streaming with entities (bold/italic formatting) --- async function* formattedStream(): AsyncGenerator { yield { text: "Hello ", entities: [{ type: "bold", offset: 0, length: 5 }], }; yield { text: "world!", entities: [{ type: "italic", offset: 6, length: 6 }], // offsets are relative to the full stream start, not this chunk }; } bot.command("formatted", async (ctx) => { await ctx.replyWithStream( formattedStream(), { parse_mode: undefined }, // otherMessageDraft params { disable_notification: true }, // otherMessage params ); }); // --- Example 3: Abort streaming via AbortSignal --- bot.command("abortable", async (ctx) => { const controller = new AbortController(); setTimeout(() => controller.abort(), 3000); // abort after 3s try { await ctx.replyWithStream(slowText(), undefined, undefined, controller.signal); } catch (e) { await ctx.reply("Stream was cancelled."); } }); bot.start(); ``` ``` -------------------------------- ### Configure StreamOptions for Stream Plugin Source: https://context7.com/grammyjs/stream/llms.txt Configure the `stream` plugin with `StreamOptions` to adjust the default draft ID offset. This is useful when multiple sequential `replyWithStream` calls occur within the same update handler to prevent draft ID collisions. The `defaultDraftIdOffset` function receives the context and returns a number used as the draft ID base. ```typescript import { Bot, type Context } from "grammy"; import { stream, type StreamFlavor, type StreamOptions } from "@grammyjs/stream"; type MyContext = StreamFlavor; const options: StreamOptions = { // Default: (ctx) => 256 * ctx.update.update_id // Override to give more headroom (e.g., 1000 message parts per update): defaultDraftIdOffset: (ctx) => 1000 * ctx.update.update_id, }; const bot = new Bot(process.env.BOT_TOKEN!); bot.use(stream(options)); // Now two sequential replyWithStream calls in the same handler won't collide: bot.command("double", async (ctx) => { await ctx.replyWithStream(async function* () { yield "First stream"; }()); await ctx.replyWithStream(async function* () { yield "Second stream"; }()); // Safe because the offset provides 1000 slots before wrapping }); bot.start(); ``` -------------------------------- ### Stream message to any chat using ctx.api.streamMessage Source: https://context7.com/grammyjs/stream/llms.txt Use `ctx.api.streamMessage` to stream message pieces to any private chat. It requires an explicit `chat_id` and `draft_id_offset`. Useful for sending streamed messages to chats other than the one that triggered the update. ```typescript import { Bot, type Context } from "grammy"; import { stream, type StreamFlavor } from "@grammyjs/stream"; import { streamText } from "ai"; import { openai } from "@ai-sdk/openai"; type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); bot.use(stream()); bot.command("ask", async (ctx) => { const targetChatId = 123456789; // any private chat ID async function* aiStream() { const { textStream } = streamText({ model: openai("gpt-4o"), prompt: ctx.match ?? "Tell me something interesting.", }); for await (const chunk of textStream) { yield chunk; } } // draft_id_offset should be unique per update to avoid ID collisions const offset = ctx.update.update_id * 256; const messages = await ctx.api.streamMessage( targetChatId, offset, aiStream(), { parse_mode: "Markdown" }, // sendMessageDraft extra params { disable_web_page_preview: true }, // sendMessage extra params ); console.log(`Streamed into ${messages.length} message(s)`); }); bot.start(); ``` -------------------------------- ### Stream Reply to Current Chat Source: https://context7.com/grammyjs/stream/llms.txt Streams an iterable of message pieces to the current chat, automatically managing draft updates and final message sends. Entity offsets must be relative to the beginning of the entire data stream. ```typescript import { Bot, type Context } from "grammy"; import { stream, type StreamFlavor, type MessageDraftPiece } from "@grammyjs/stream"; type MyContext = StreamFlavor; const bot = new Bot(process.env.BOT_TOKEN!); bot.use(stream()); // --- Example 1: Simple generator simulating slow text output --- async function* slowText(): AsyncGenerator { yield "This i"; await new Promise((r) => setTimeout(r, 1000)); yield "s some sl"; await new Promise((r) => setTimeout(r, 1000)); yield "owly gene"; await new Promise((r) => setTimeout(r, 1000)); yield "rated text"; } bot.command("demo", async (ctx) => { // Streams the generator; Telegram users see the message update live const messages = await ctx.replyWithStream(slowText()); console.log(`Sent ${messages.length} message(s)`); // Output: Sent 1 message(s) }); // --- Example 2: Streaming with entities (bold/italic formatting) --- async function* formattedStream(): AsyncGenerator { yield { text: "Hello ", entities: [{ type: "bold", offset: 0, length: 5 }], }; yield { text: "world!", entities: [{ type: "italic", offset: 6, length: 6 }], // offsets are relative to the full stream start, not this chunk }; } bot.command("formatted", async (ctx) => { await ctx.replyWithStream( formattedStream(), { parse_mode: undefined }, // otherMessageDraft params { disable_notification: true }, // otherMessage params ); }); // --- Example 3: Abort streaming via AbortSignal --- bot.command("abortable", async (ctx) => { const controller = new AbortController(); setTimeout(() => controller.abort(), 3000); // abort after 3s try { await ctx.replyWithStream(slowText(), undefined, undefined, controller.signal); } catch (e) { await ctx.reply("Stream was cancelled."); } }); bot.start(); ``` -------------------------------- ### ctx.api.streamMessage Source: https://context7.com/grammyjs/stream/llms.txt Streams message pieces to any private chat by its chat_id. Requires an explicit chat_id and a draft_id_offset integer. Useful for sending streamed messages to chats other than the one that triggered the update. ```APIDOC ## `ctx.api.streamMessage(chat_id, draft_id_offset, stream, otherMessageDraft?, otherMessage?, signal?)` — Stream to any chat A lower-level method added to `ctx.api` that streams message pieces to any private chat by its `chat_id`. Unlike `ctx.replyWithStream`, it requires an explicit `chat_id` and a `draft_id_offset` integer. Useful for sending streamed messages to chats other than the one that triggered the update. ### Parameters - **chat_id** (number) - The ID of the chat to stream the message to. - **draft_id_offset** (number) - An integer to offset draft IDs, ensuring uniqueness per update. - **stream** (AsyncGenerator) - An asynchronous generator yielding message pieces. - **otherMessageDraft** (object, optional) - Extra parameters for `sendMessageDraft`. - **otherMessage** (object, optional) - Extra parameters for `sendMessage`. - **signal** (AbortSignal, optional) - An AbortSignal to cancel the streaming operation. ``` -------------------------------- ### MessageDraftPiece Type Source: https://context7.com/grammyjs/stream/llms.txt Defines the structure for elements within a stream chunk. Each yielded value can be a plain string or a structured object containing text, optional entities, and an optional draft_id. ```APIDOC ## `MessageDraftPiece` — Type for stream chunk elements Each yielded value in a stream can be either a plain `string` or a structured object containing `text`, optional `entities`, and an optional `draft_id`. When provided, `draft_id` controls which Telegram message a chunk belongs to. `entities` offsets must always be relative to the start of the full content stream (not just the current chunk), and are automatically adjusted per-message before being sent. ### Structure - **text** (string) - The text content of the chunk. - **entities** (Array, optional) - Formatting entities for the text. - **draft_id** (number, optional) - Controls which Telegram message a chunk belongs to. Incrementing this forces a new message. ``` -------------------------------- ### MessageDraftPiece type for stream chunks Source: https://context7.com/grammyjs/stream/llms.txt The `MessageDraftPiece` type defines the structure for stream chunk elements. Each yielded value can be a plain string or a structured object with `text`, optional `entities`, and an optional `draft_id`. `draft_id` controls message boundaries, and `entities` offsets are relative to the full stream content. ```typescript import type { MessageDraftPiece } from "@grammyjs/stream"; import type { MessageEntity } from "grammy/types"; // Plain string (simplest form) const simple: MessageDraftPiece = "Hello, Telegram!"; // Structured with formatting entities const withEntities: MessageDraftPiece = { text: "Bold text here.", entities: [{ type: "bold", offset: 0, length: 9 }] satisfies MessageEntity[], }; // With explicit draft_id to force a new message boundary const newMessage: MessageDraftPiece = { draft_id: 1, // incremented from 0 forces a new message text: "This starts a brand new Telegram message.", entities: [], }; // Full usage in an async generator async function* structuredStream(): AsyncGenerator { yield { text: "**Report**\n", entities: [{ type: "bold", offset: 0, length: 8 }] }; yield { text: "Section one content. " }; // same message (draft_id auto-managed) yield { draft_id: 1, text: "Section two starts here." }; // new message } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.