Truncate voice-transcript echo so Telegram doesn't reject long messages
The bot re-emits the transcript back to the owner as a preview
(`🎤 …`). Telegram's sendMessage caps at 4096 chars, so a long audio
message crashed the handler with a 400. Trim the echo to 200 chars
with an ellipsis; the agent still receives the full transcript via
result.input.text.
This commit is contained in:
@@ -262,6 +262,22 @@ describe("registerBridge", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("echoes a truncated transcript but sends the full text to the agent", async () => {
|
||||
const long = "a".repeat(5000);
|
||||
const transcriber = { transcribe: async () => long };
|
||||
const h = harness({ debounceMs: 5, transcriber });
|
||||
const voiceUpdate = textUpdate("");
|
||||
delete (voiceUpdate.message as { text?: string }).text;
|
||||
(voiceUpdate.message as { voice?: unknown }).voice = { file_id: "v1", file_unique_id: "u", duration: 3, mime_type: "audio/ogg" };
|
||||
await h.bot.handleUpdate(voiceUpdate);
|
||||
const echoed = h.texts().find((t) => t.startsWith("🎤 "));
|
||||
expect(echoed).toBeDefined();
|
||||
// Emoji + space + 199 chars + ellipsis = 202 code points.
|
||||
expect(Array.from(echoed as string)).toHaveLength(202);
|
||||
expect((echoed as string).endsWith("…")).toBe(true);
|
||||
await vi.waitFor(() => expect(h.host.sent).toEqual([{ text: long, images: undefined }]));
|
||||
});
|
||||
|
||||
it("drops buffered messages when /new starts a fresh session", async () => {
|
||||
const h = harness({ debounceMs: 100 });
|
||||
await h.bot.handleUpdate(textUpdate("hold this thought"));
|
||||
|
||||
@@ -16,6 +16,9 @@ import type { Outbound } from "./outbound.js";
|
||||
/** How long we wait after the last message before delivering the merged batch to the agent. */
|
||||
const DEFAULT_DEBOUNCE_MS = 600;
|
||||
|
||||
/** Longest transcript we echo back to the owner before truncating with an ellipsis. Telegram caps sendMessage at 4096 chars. */
|
||||
const MAX_TRANSCRIPT_ECHO_CHARS = 200;
|
||||
|
||||
export type HostLike = Pick<AgentHost, "busy" | "send" | "abort" | "newSession" | "compact" | "status" | "subscribe">;
|
||||
export type SchedulerLike = Pick<Scheduler, "list" | "delete">;
|
||||
|
||||
@@ -211,7 +214,7 @@ export function registerBridge(deps: BridgeDeps): void {
|
||||
botUserId: bot.botInfo?.id,
|
||||
});
|
||||
for (const note of result.notes) await ctx.reply(note);
|
||||
if (result.transcript) await ctx.reply(`🎤 ${result.transcript}`);
|
||||
if (result.transcript) await ctx.reply(`🎤 ${truncateForEcho(result.transcript)}`);
|
||||
if (!result.input) {
|
||||
if (result.notes.length === 0) await ctx.reply("I can handle text, photos, voice notes, and files.");
|
||||
return;
|
||||
@@ -282,3 +285,9 @@ export async function downloadFile(
|
||||
function errorText(err: unknown): string {
|
||||
return err instanceof Error ? err.message : String(err);
|
||||
}
|
||||
|
||||
export function truncateForEcho(text: string): string {
|
||||
const chars = Array.from(text);
|
||||
if (chars.length <= MAX_TRANSCRIPT_ECHO_CHARS) return text;
|
||||
return `${chars.slice(0, MAX_TRANSCRIPT_ECHO_CHARS - 1).join("")}…`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user