mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix: strip reasoning tags from messaging tool text to prevent <think> leakage
This commit is contained in:
committed by
Gustavo Madeira Santana
parent
5fab11198d
commit
bb6ebc1eab
@@ -162,6 +162,80 @@ describe("message tool description", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("message tool reasoning tag sanitization", () => {
|
||||
it("strips <think> tags from text field before sending", async () => {
|
||||
mocks.runMessageAction.mockClear();
|
||||
mocks.runMessageAction.mockResolvedValue({
|
||||
kind: "send",
|
||||
action: "send",
|
||||
channel: "signal",
|
||||
to: "signal:+15551234567",
|
||||
handledBy: "plugin",
|
||||
payload: {},
|
||||
dryRun: true,
|
||||
} satisfies MessageActionRunResult);
|
||||
|
||||
const tool = createMessageTool({ config: {} as never });
|
||||
|
||||
await tool.execute("1", {
|
||||
action: "send",
|
||||
target: "signal:+15551234567",
|
||||
text: "<think>internal reasoning</think>Hello!",
|
||||
});
|
||||
|
||||
const call = mocks.runMessageAction.mock.calls[0]?.[0];
|
||||
expect(call?.params?.text).toBe("Hello!");
|
||||
});
|
||||
|
||||
it("strips <think> tags from content field before sending", async () => {
|
||||
mocks.runMessageAction.mockClear();
|
||||
mocks.runMessageAction.mockResolvedValue({
|
||||
kind: "send",
|
||||
action: "send",
|
||||
channel: "discord",
|
||||
to: "discord:123",
|
||||
handledBy: "plugin",
|
||||
payload: {},
|
||||
dryRun: true,
|
||||
} satisfies MessageActionRunResult);
|
||||
|
||||
const tool = createMessageTool({ config: {} as never });
|
||||
|
||||
await tool.execute("1", {
|
||||
action: "send",
|
||||
target: "discord:123",
|
||||
content: "<think>reasoning here</think>Reply text",
|
||||
});
|
||||
|
||||
const call = mocks.runMessageAction.mock.calls[0]?.[0];
|
||||
expect(call?.params?.content).toBe("Reply text");
|
||||
});
|
||||
|
||||
it("passes through text without reasoning tags unchanged", async () => {
|
||||
mocks.runMessageAction.mockClear();
|
||||
mocks.runMessageAction.mockResolvedValue({
|
||||
kind: "send",
|
||||
action: "send",
|
||||
channel: "signal",
|
||||
to: "signal:+15551234567",
|
||||
handledBy: "plugin",
|
||||
payload: {},
|
||||
dryRun: true,
|
||||
} satisfies MessageActionRunResult);
|
||||
|
||||
const tool = createMessageTool({ config: {} as never });
|
||||
|
||||
await tool.execute("1", {
|
||||
action: "send",
|
||||
target: "signal:+15551234567",
|
||||
text: "Normal message without any tags",
|
||||
});
|
||||
|
||||
const call = mocks.runMessageAction.mock.calls[0]?.[0];
|
||||
expect(call?.params?.text).toBe("Normal message without any tags");
|
||||
});
|
||||
});
|
||||
|
||||
describe("message tool sandbox passthrough", () => {
|
||||
it("forwards sandboxRoot to runMessageAction", async () => {
|
||||
mocks.runMessageAction.mockClear();
|
||||
|
||||
@@ -16,6 +16,7 @@ import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES } from "../../gateway/protocol
|
||||
import { getToolResult, runMessageAction } from "../../infra/outbound/message-action-runner.js";
|
||||
import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
|
||||
import { normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { stripReasoningTagsFromText } from "../../shared/text/reasoning-tags.js";
|
||||
import { normalizeMessageChannel } from "../../utils/message-channel.js";
|
||||
import { resolveSessionAgentId } from "../agent-scope.js";
|
||||
import { listChannelSupportedActions } from "../channel-tools.js";
|
||||
@@ -405,7 +406,17 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
|
||||
err.name = "AbortError";
|
||||
throw err;
|
||||
}
|
||||
const params = args as Record<string, unknown>;
|
||||
// Shallow-copy so we don't mutate the original event args (used for logging/dedup).
|
||||
const params = { ...(args as Record<string, unknown>) };
|
||||
|
||||
// Strip reasoning tags from text fields — models may include <think>…</think>
|
||||
// in tool arguments, and the messaging tool send path has no other tag filtering.
|
||||
for (const field of ["text", "content", "message", "caption"]) {
|
||||
if (typeof params[field] === "string") {
|
||||
params[field] = stripReasoningTagsFromText(params[field] as string);
|
||||
}
|
||||
}
|
||||
|
||||
const cfg = options?.config ?? loadConfig();
|
||||
const action = readStringParam(params, "action", {
|
||||
required: true,
|
||||
|
||||
Reference in New Issue
Block a user