fix(telegram): skip tool narration messages, only send final answer
When the agent uses tools (web search, etc.), it generates intermediate narration text like "Let me search..." before each tool call. These were being sent as separate Telegram messages, causing message spam. Now we detect tool_use blocks in the message content and skip sending those intermediate messages — only the final answer reaches the user. Applied to both Desktop channel plugin and Gateway Telegram service. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8270762d66
commit
81998e6309
3 changed files with 32 additions and 1 deletions
|
|
@ -1003,8 +1003,17 @@ export class TelegramService implements OnModuleInit, OnModuleDestroy {
|
|||
|
||||
// Stop typing + send formatted text on message_end
|
||||
if (event.type === "message_end") {
|
||||
this.stopTyping(deviceId);
|
||||
const agentMsg = (event as { message?: { content?: Array<{ type: string; text?: string }> } }).message;
|
||||
|
||||
// Skip tool narration: if the message contains tool_use blocks,
|
||||
// it's intermediate text (e.g. "Let me search...") before a tool call.
|
||||
// Keep typing and wait for the final answer.
|
||||
const hasToolUse = agentMsg?.content?.some((c) => c.type === "tool_use") ?? false;
|
||||
if (hasToolUse) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.stopTyping(deviceId);
|
||||
if (agentMsg?.content) {
|
||||
const textContent = agentMsg.content
|
||||
.filter((c) => c.type === "text" && c.text)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,14 @@ export function extractText(message: AgentMessage | undefined): string {
|
|||
.join("");
|
||||
}
|
||||
|
||||
/** Check if an AgentMessage contains tool_use blocks (i.e., is a tool invocation, not a final answer) */
|
||||
export function hasToolUse(message: AgentMessage | undefined): boolean {
|
||||
if (!message || typeof message !== "object" || !("content" in message)) return false;
|
||||
const content = (message as { content?: Array<{ type: string }> }).content;
|
||||
if (!Array.isArray(content)) return false;
|
||||
return content.some((c) => c.type === "tool_use");
|
||||
}
|
||||
|
||||
/** Extract thinking/reasoning content from an AgentMessage */
|
||||
export function extractThinking(message: AgentMessage | undefined): string {
|
||||
if (!message || typeof message !== "object" || !("content" in message)) return "";
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { listChannels } from "./registry.js";
|
|||
import { loadChannelsConfig } from "./config.js";
|
||||
import { MessageAggregator, DEFAULT_CHUNKER_CONFIG } from "../hub/message-aggregator.js";
|
||||
import { isHeartbeatAckEvent } from "../hub/heartbeat-filter.js";
|
||||
import { hasToolUse } from "../agent/extract-text.js";
|
||||
import type { AsyncAgent } from "../agent/async-agent.js";
|
||||
import type { ChannelInfo } from "../agent/system-prompt/types.js";
|
||||
import { transcribeAudio } from "../media/transcribe.js";
|
||||
|
|
@ -279,6 +280,19 @@ export class ChannelManager {
|
|||
this.createAggregator();
|
||||
}
|
||||
|
||||
// Skip tool narration: if the assistant message contains tool_use blocks,
|
||||
// it's intermediate narration (e.g. "Let me search...") before a tool call,
|
||||
// not the final answer. Discard the buffered text instead of sending it.
|
||||
if (event.type === "message_end" && role === "assistant") {
|
||||
const message = (event as { message?: Parameters<typeof hasToolUse>[0] }).message;
|
||||
if (hasToolUse(message)) {
|
||||
console.log("[Channels] Skipping tool narration message (has tool_use blocks)");
|
||||
this.aggregator?.reset();
|
||||
this.aggregator = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.aggregator) {
|
||||
this.aggregator.handleEvent(event);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue