121 lines
3.3 KiB
TypeScript
121 lines
3.3 KiB
TypeScript
/**
|
|
* Message Normalization Layer
|
|
*
|
|
* Normalizes messages and comments before they are injected into agent prompts.
|
|
* Prevents malformed, empty, or excessively long content from degrading
|
|
* model output quality. Inspired by Reference/src/utils/messages.ts.
|
|
*
|
|
* Applied at the prompt-builder boundary — the last point before content
|
|
* enters the model's context window.
|
|
*/
|
|
|
|
import type { AgentMessageRecord } from "@shared/types";
|
|
|
|
/** Maximum body length for a single message in prompt context. */
|
|
const MAX_MESSAGE_BODY_LENGTH = 800;
|
|
|
|
/** Maximum comment body length in prompt context. */
|
|
const MAX_COMMENT_BODY_LENGTH = 500;
|
|
|
|
/**
|
|
* Strip control characters (except newlines/tabs) and collapse
|
|
* excessive whitespace runs into single spaces.
|
|
*/
|
|
function cleanText(text: string): string {
|
|
// eslint-disable-next-line no-control-regex
|
|
return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
|
|
.replace(/[ \t]{4,}/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
/**
|
|
* Truncate text to maxLen, appending "…" if truncated.
|
|
*/
|
|
function truncate(text: string, maxLen: number): string {
|
|
if (text.length <= maxLen) return text;
|
|
return text.slice(0, maxLen - 1) + "…";
|
|
}
|
|
|
|
/**
|
|
* Normalize agent messages before prompt injection.
|
|
* - Removes empty-body messages
|
|
* - Cleans control characters
|
|
* - Truncates overly long bodies
|
|
* - Deduplicates consecutive identical messages from the same sender
|
|
*/
|
|
export function normalizeMessagesForPrompt(
|
|
messages: AgentMessageRecord[],
|
|
limit: number = 15,
|
|
): AgentMessageRecord[] {
|
|
const result: AgentMessageRecord[] = [];
|
|
let prevKey = "";
|
|
|
|
for (const msg of messages) {
|
|
const cleaned = cleanText(msg.body);
|
|
if (cleaned.length === 0) continue;
|
|
|
|
// Deduplicate consecutive identical messages from the same sender
|
|
const key = `${msg.fromAgentId}:${cleaned}`;
|
|
if (key === prevKey) continue;
|
|
prevKey = key;
|
|
|
|
result.push({
|
|
...msg,
|
|
body: truncate(cleaned, MAX_MESSAGE_BODY_LENGTH),
|
|
subject: msg.subject ? truncate(cleanText(msg.subject), 120) : msg.subject,
|
|
});
|
|
|
|
if (result.length >= limit) break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Transcript Repair — for session handoff / resume scenarios
|
|
// Inspired by Reference/src/utils/messages.ts tool pairing logic.
|
|
// Applied when session state is restored, not at every message send.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
export interface CommentRecord {
|
|
taskId: string;
|
|
taskTitle: string;
|
|
authorName: string;
|
|
body: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
/**
|
|
* Normalize task comments before prompt injection.
|
|
* - Removes empty-body comments
|
|
* - Cleans control characters
|
|
* - Truncates overly long bodies
|
|
* - Deduplicates consecutive identical comments
|
|
*/
|
|
export function normalizeCommentsForPrompt(
|
|
comments: CommentRecord[],
|
|
limit: number = 10,
|
|
): CommentRecord[] {
|
|
const result: CommentRecord[] = [];
|
|
let prevKey = "";
|
|
|
|
for (const comment of comments) {
|
|
const cleaned = cleanText(comment.body);
|
|
if (cleaned.length === 0) continue;
|
|
|
|
const key = `${comment.taskId}:${comment.authorName}:${cleaned}`;
|
|
if (key === prevKey) continue;
|
|
prevKey = key;
|
|
|
|
result.push({
|
|
...comment,
|
|
body: truncate(cleaned, MAX_COMMENT_BODY_LENGTH),
|
|
});
|
|
|
|
if (result.length >= limit) break;
|
|
}
|
|
|
|
return result;
|
|
}
|