fix: add message tool read hint for thread reads

This commit is contained in:
Peter Steinberger
2026-04-06 14:42:06 +01:00
parent 4d49c7b8a5
commit f00c8c1b87
3 changed files with 83 additions and 2 deletions

View File

@@ -865,6 +865,69 @@ describe("message tool description", () => {
expect(tool.description).toContain("Current channel (bluebubbles) supports:");
expect(tool.description).not.toContain("Other configured channels");
});
it("includes the thread read hint when the current channel supports read", () => {
const signalPlugin = createChannelPlugin({
id: "signal",
label: "Signal",
docsPath: "/channels/signal",
blurb: "Signal test plugin.",
actions: ["send", "read", "react"],
});
setActivePluginRegistry(
createTestRegistry([{ pluginId: "signal", source: "test", plugin: signalPlugin }]),
);
const tool = createMessageTool({
config: {} as never,
currentChannelProvider: "signal",
});
expect(tool.description).toContain('Use action="read" with threadId');
});
it("omits the thread read hint when the current channel does not support read", () => {
const signalPlugin = createChannelPlugin({
id: "signal",
label: "Signal",
docsPath: "/channels/signal",
blurb: "Signal test plugin.",
actions: ["send", "react"],
});
setActivePluginRegistry(
createTestRegistry([{ pluginId: "signal", source: "test", plugin: signalPlugin }]),
);
const tool = createMessageTool({
config: {} as never,
currentChannelProvider: "signal",
});
expect(tool.description).not.toContain('Use action="read" with threadId');
});
it("includes the thread read hint in the generic fallback when configured actions include read", () => {
const signalPlugin = createChannelPlugin({
id: "signal",
label: "Signal",
docsPath: "/channels/signal",
blurb: "Signal test plugin.",
actions: ["read"],
});
setActivePluginRegistry(
createTestRegistry([{ pluginId: "signal", source: "test", plugin: signalPlugin }]),
);
const tool = createMessageTool({
config: {} as never,
});
expect(tool.description).toContain("Supports actions:");
expect(tool.description).toContain('Use action="read" with threadId');
});
});
describe("message tool reasoning tag sanitization", () => {

View File

@@ -30,6 +30,8 @@ import { jsonResult, readNumberParam, readStringParam } from "./common.js";
import { resolveGatewayOptions } from "./gateway.js";
const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES;
const MESSAGE_TOOL_THREAD_READ_HINT =
' Use action="read" with threadId to fetch prior messages in a thread when you need conversation context you do not have yet.';
const EXPLICIT_TARGET_ACTIONS = new Set<ChannelMessageActionName>([
"send",
"sendWithEffect",
@@ -615,7 +617,7 @@ function buildMessageToolDescription(options?: {
desc += ` Other configured channels: ${otherChannels.join(", ")}.`;
}
return desc;
return appendMessageToolReadHint(desc, allActions);
}
}
@@ -623,13 +625,28 @@ function buildMessageToolDescription(options?: {
if (resolvedOptions.config) {
const actions = listChannelMessageActions(resolvedOptions.config);
if (actions.length > 0) {
return `${baseDescription} Supports actions: ${actions.join(", ")}.`;
return appendMessageToolReadHint(
`${baseDescription} Supports actions: ${actions.join(", ")}.`,
actions,
);
}
}
return `${baseDescription} Supports actions: send, delete, react, poll, pin, threads, and more.`;
}
function appendMessageToolReadHint(
description: string,
actions: Iterable<ChannelMessageActionName | "send">,
): string {
for (const action of actions) {
if (action === "read") {
return `${description}${MESSAGE_TOOL_THREAD_READ_HINT}`;
}
}
return description;
}
export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
const loadConfigForTool = options?.loadConfig ?? loadConfig;
const resolveSecretRefsForTool =