@iletai/nzb 1.6.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- const TELEGRAM_MAX_LENGTH = 4096;
1
+ export const TELEGRAM_MAX_LENGTH = 4096;
2
2
  // Reserve space for tag closure and pagination prefix
3
3
  const CHUNK_TARGET = TELEGRAM_MAX_LENGTH - 40;
4
4
  // Telegram HTML tags we track for proper tag closure across chunks
@@ -27,6 +27,11 @@ export function isChatNotFoundError(err) {
27
27
  const msg = err instanceof Error ? err.message : String(err);
28
28
  return /chat not found/i.test(msg);
29
29
  }
30
+ /** Detect Telegram "message is too long" errors. */
31
+ export function isMessageTooLongError(err) {
32
+ const msg = err instanceof Error ? err.message : String(err);
33
+ return /message is too long/i.test(msg);
34
+ }
30
35
  /**
31
36
  * Track open HTML tags in a segment for proper closure/reopening across chunks.
32
37
  * Returns the stack of currently open tag names (outermost first).
@@ -1,7 +1,7 @@
1
1
  import { InlineKeyboard } from "grammy";
2
2
  import { config } from "../../config.js";
3
3
  import { getQueueSize, sendToOrchestrator } from "../../copilot/orchestrator.js";
4
- import { chunkMessage, escapeHtml, formatToolSummaryExpandable, isHtmlParseError, isMessageNotModifiedError, toTelegramHTML, } from "../formatter.js";
4
+ import { chunkMessage, escapeHtml, formatToolSummaryExpandable, isHtmlParseError, isMessageNotModifiedError, isMessageTooLongError, TELEGRAM_MAX_LENGTH, toTelegramHTML, } from "../formatter.js";
5
5
  import { logDebug, logError, logInfo } from "../log-channel.js";
6
6
  import { editSafe } from "../safe-api.js";
7
7
  import { createSmartSuggestionsWithContext } from "./suggestions.js";
@@ -78,13 +78,18 @@ export function registerMessageHandler(bot, getBot) {
78
78
  const enqueueEdit = (text) => {
79
79
  if (finalized || text === lastEditedText)
80
80
  return;
81
+ // Clamp streaming previews to Telegram's limit — these are ephemeral
82
+ let safeText = text;
83
+ if (safeText.length > TELEGRAM_MAX_LENGTH) {
84
+ safeText = safeText.slice(0, TELEGRAM_MAX_LENGTH - 4) + " ⋯";
85
+ }
81
86
  editChain = editChain
82
87
  .then(async () => {
83
- if (finalized || text === lastEditedText)
88
+ if (finalized || safeText === lastEditedText)
84
89
  return;
85
90
  if (!placeholderMsgId) {
86
91
  // Don't create a placeholder for tiny initial chunks — wait for meaningful content
87
- if (text.length < MIN_INITIAL_CHARS && !text.startsWith("🔧") && !text.startsWith("✅"))
92
+ if (safeText.length < MIN_INITIAL_CHARS && !safeText.startsWith("🔧") && !safeText.startsWith("✅"))
88
93
  return;
89
94
  // Let the typing indicator show for at least a short period
90
95
  const elapsed = Date.now() - handlerStartTime;
@@ -94,7 +99,7 @@ export function registerMessageHandler(bot, getBot) {
94
99
  if (finalized)
95
100
  return;
96
101
  try {
97
- const msg = await ctx.reply(text, { reply_parameters: replyParams });
102
+ const msg = await ctx.reply(safeText, { reply_parameters: replyParams });
98
103
  placeholderMsgId = msg.message_id;
99
104
  // Stop typing once placeholder is visible — edits serve as the indicator now
100
105
  stopTyping();
@@ -105,16 +110,16 @@ export function registerMessageHandler(bot, getBot) {
105
110
  }
106
111
  else {
107
112
  try {
108
- await editSafe(getBot().api, chatId, placeholderMsgId, text);
113
+ await editSafe(getBot().api, chatId, placeholderMsgId, safeText);
109
114
  }
110
115
  catch (err) {
111
- // Silently ignore "message is not modified" happens when text hasn't actually changed
112
- if (isMessageNotModifiedError(err))
116
+ // Silently ignore "message is not modified" or "too long" during streaming
117
+ if (isMessageNotModifiedError(err) || isMessageTooLongError(err))
113
118
  return;
114
119
  return;
115
120
  }
116
121
  }
117
- lastEditedText = text;
122
+ lastEditedText = safeText;
118
123
  })
119
124
  .catch(() => { });
120
125
  };
@@ -338,6 +343,15 @@ export function registerMessageHandler(bot, getBot) {
338
343
  const isLast = index === totalChunks - 1;
339
344
  // Pagination header for multi-chunk messages
340
345
  const pageTag = totalChunks > 1 ? `📄 ${index + 1}/${totalChunks}\n` : "";
346
+ // Trim chunk if pageTag pushes it over the limit
347
+ let safeChunk = chunk;
348
+ if (pageTag.length + safeChunk.length > TELEGRAM_MAX_LENGTH) {
349
+ safeChunk = safeChunk.slice(0, TELEGRAM_MAX_LENGTH - pageTag.length - 4) + " ⋯";
350
+ }
351
+ let safeFallback = fallback;
352
+ if (pageTag.length + safeFallback.length > TELEGRAM_MAX_LENGTH) {
353
+ safeFallback = safeFallback.slice(0, TELEGRAM_MAX_LENGTH - pageTag.length - 4) + " ⋯";
354
+ }
341
355
  const opts = {
342
356
  parse_mode: "HTML",
343
357
  ...(isFirst ? { reply_parameters: replyParams } : {}),
@@ -348,8 +362,8 @@ export function registerMessageHandler(bot, getBot) {
348
362
  ...(isLast && smartKb ? { reply_markup: smartKb } : {}),
349
363
  };
350
364
  const sent = await ctx
351
- .reply(pageTag + chunk, opts)
352
- .catch(() => ctx.reply(pageTag + fallback, fallbackOpts));
365
+ .reply(pageTag + safeChunk, opts)
366
+ .catch(() => ctx.reply(pageTag + safeFallback, fallbackOpts));
353
367
  if (index === 0 && sent)
354
368
  firstSentMsgId = sent.message_id;
355
369
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iletai/nzb",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "NZB — a personal AI assistant for developers, built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "nzb": "dist/cli.js"