fix: stream native slash tool replies

This commit is contained in:
Peter Steinberger
2026-01-13 00:53:30 +00:00
parent c03a745f61
commit 6a48688c09
5 changed files with 211 additions and 54 deletions

View File

@@ -1871,6 +1871,49 @@ describe("createTelegramBot", () => {
);
});
it("streams tool summaries for native slash commands", async () => {
onSpy.mockReset();
sendMessageSpy.mockReset();
commandSpy.mockReset();
const replySpy = replyModule.__replySpy as unknown as ReturnType<
typeof vi.fn
>;
replySpy.mockReset();
replySpy.mockImplementation(async (_ctx, opts) => {
await opts?.onToolResult?.({ text: "tool update" });
return { text: "final reply" };
});
loadConfig.mockReturnValue({
commands: { native: true },
telegram: {
dmPolicy: "open",
allowFrom: ["*"],
},
});
createTelegramBot({ token: "tok" });
const verboseHandler = commandSpy.mock.calls.find(
(call) => call[0] === "verbose",
)?.[1] as ((ctx: Record<string, unknown>) => Promise<void>) | undefined;
if (!verboseHandler) throw new Error("verbose command handler missing");
await verboseHandler({
message: {
chat: { id: 12345, type: "private" },
from: { id: 12345, username: "testuser" },
text: "/verbose on",
date: 1736380800,
message_id: 42,
},
match: "on",
});
expect(sendMessageSpy).toHaveBeenCalledTimes(2);
expect(sendMessageSpy.mock.calls[0]?.[1]).toContain("tool update");
expect(sendMessageSpy.mock.calls[1]?.[1]).toContain("final reply");
});
it("dedupes duplicate message updates by update_id", async () => {
onSpy.mockReset();
const replySpy = replyModule.__replySpy as unknown as ReturnType<

View File

@@ -31,7 +31,6 @@ import {
matchesMentionPatterns,
} from "../auto-reply/reply/mentions.js";
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
import { getReplyFromConfig } from "../auto-reply/reply.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import {
isNativeCommandsExplicitlyDisabled,
@@ -1128,25 +1127,41 @@ export function createTelegramBot(opts: TelegramBotOptions) {
IsForum: isForum,
};
const replyResult = await getReplyFromConfig(
ctxPayload,
{ skillFilter },
const disableBlockStreaming =
typeof telegramCfg.blockStreaming === "boolean"
? !telegramCfg.blockStreaming
: undefined;
await dispatchReplyWithBufferedBlockDispatcher({
ctx: ctxPayload,
cfg,
);
const replies = replyResult
? Array.isArray(replyResult)
? replyResult
: [replyResult]
: [];
await deliverReplies({
replies,
chatId: String(chatId),
token: opts.token,
runtime,
bot,
replyToMode,
textLimit,
messageThreadId,
dispatcherOptions: {
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId)
.responsePrefix,
deliver: async (payload) => {
await deliverReplies({
replies: [payload],
chatId: String(chatId),
token: opts.token,
runtime,
bot,
replyToMode,
textLimit,
messageThreadId,
});
},
onError: (err, info) => {
runtime.error?.(
danger(
`telegram slash ${info.kind} reply failed: ${String(err)}`,
),
);
},
},
replyOptions: {
skillFilter,
disableBlockStreaming,
},
});
});
}