13147e537792249d7a2be2d3f76ae0a14499190bfb4e2abc96d5637dd47a5ef8beforeResponsebeforeToolCall1// formatting/clean.js
2// Deterministic post-processing for LLM output.
3
4// Emoji ranges: emoticons, dingbats, symbols, transport, flags, supplemental, etc.
5// Keeps basic punctuation, arrows, math symbols, and currency signs.
6const EMOJI_RE = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2702}-\u{27B0}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}\u{E0020}-\u{E007F}]/gu;
7
8/**
9 * Strip emojis from text.
10 */
11export function stripEmojis(text) {
12 return text.replace(EMOJI_RE, "");
13}
14
15/**
16 * Collapse runs of 3+ newlines into 2.
17 */
18export function normalizeWhitespace(text) {
19 return text.replace(/\n{3,}/g, "\n\n");
20}
21
22/**
23 * Remove trailing filler phrases LLMs like to append.
24 * "Let me know if you need anything else!" etc.
25 */
26const FILLER_RE = /\n{1,2}(?:Let me know if (?:you (?:need|have|want|would like)|there(?:'s| is) anything)|Feel free to (?:ask|reach out|let me know)|Hope (?:this|that) helps|Happy to help|Don't hesitate to)[^\n]*$/i;
27
28export function trimFiller(text) {
29 return text.replace(FILLER_RE, "");
30}
31
32/**
33 * Full cleaning pipeline.
34 */
35export function clean(text) {
36 if (!text || typeof text !== "string") return text;
37 let result = text;
38 result = stripEmojis(result);
39 result = normalizeWhitespace(result);
40 result = trimFiller(result);
41 result = result.trim();
42 return result;
43}
441import log from "../../seed/log.js";
2import { clean } from "./clean.js";
3
4export async function init(core) {
5 // Clean AI responses (emojis, whitespace, filler)
6 core.hooks.register("beforeResponse", async (data) => {
7 if (data.content && typeof data.content === "string") {
8 data.content = clean(data.content);
9 }
10 }, "llm-response-formatting");
11
12 // Normalize tool names against registered tools.
13 // Some models generate tool calls with underscores (navigate_tree) instead
14 // of hyphens (navigate-tree), or with slight misspellings. This hook finds
15 // the closest matching registered tool name before it reaches MCP.
16 const { resolveTools } = await import("../../seed/tools.js");
17 const { getToolsForMode } = await import("../../seed/modes/registry.js");
18
19 core.hooks.register("beforeToolCall", async (data) => {
20 if (!data.toolName) return;
21 // Try the name as-is first (fast path)
22 const exact = resolveTools([data.toolName]);
23 if (exact.length > 0) return;
24
25 // Try replacing underscores with hyphens
26 const hyphenated = data.toolName.replace(/_/g, "-");
27 const hyphenMatch = resolveTools([hyphenated]);
28 if (hyphenMatch.length > 0) {
29 log.debug("Formatting", `Tool name normalized: ${data.toolName} -> ${hyphenated}`);
30 data.toolName = hyphenated;
31 }
32 }, "llm-response-formatting");
33
34 log.verbose("Formatting", "Response cleaning + tool name normalization active");
35
36 return {};
37}
381export default {
2 name: "llm-response-formatting",
3 version: "1.0.2",
4 builtFor: "TreeOS",
5 description:
6 "Post-processing layer between the LLM and the user. Hooks into two lifecycle events " +
7 "(beforeResponse and beforeToolCall) to clean up the messy, inconsistent output that " +
8 "language models produce. The goal is that every response reaching the user follows the " +
9 "same formatting conventions regardless of which model generated it.\n\n" +
10 "The beforeResponse hook runs a three-stage cleaning pipeline on every AI response. First, " +
11 "emoji stripping: a comprehensive regex covering emoticons, dingbats, transport symbols, " +
12 "flags, supplemental symbols, and variation selectors removes all emoji characters while " +
13 "preserving basic punctuation, arrows, math symbols, and currency signs. Second, whitespace " +
14 "normalization: runs of three or more consecutive newlines collapse to exactly two, preventing " +
15 "the excessive vertical spacing many models produce. Third, filler trimming: a pattern-matched " +
16 "removal of trailing pleasantries that LLMs append reflexively. Phrases like 'Let me know if " +
17 "you need anything else', 'Feel free to ask', 'Hope this helps', and 'Don't hesitate to " +
18 "reach out' are stripped from the end of responses.\n\n" +
19 "The beforeToolCall hook fixes tool name mismatches. Some models generate tool calls with " +
20 "underscores (navigate_tree) when the registered tool uses hyphens (navigate-tree). The hook " +
21 "first checks the exact name against the tool registry. If no match is found, it replaces " +
22 "underscores with hyphens and checks again. A successful match silently rewrites the tool " +
23 "name before it reaches MCP dispatch, preventing tool-not-found errors that would otherwise " +
24 "break the conversation flow. The fast path (exact name match) adds zero overhead to " +
25 "correctly-named tool calls.",
26
27 needs: {
28 services: ["hooks"],
29 },
30
31 optional: {},
32
33 provides: {
34 models: {},
35 routes: false,
36 tools: false,
37 jobs: false,
38 orchestrator: false,
39 energyActions: {},
40 sessionTypes: {},
41
42 hooks: {
43 fires: [],
44 listens: ["beforeResponse", "beforeToolCall"],
45 },
46 },
47};
48
| Version | Published | Downloads |
|---|---|---|
| 1.0.2 | 38d ago | 0 |
| 1.0.0 | 48d ago | 0 |
treeos ext star llm-response-formatting
Post comments from the CLI: treeos ext comment llm-response-formatting "your comment"
Max 3 comments per extension. One star and one flag per user.
Loading comments...