77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import type { MessageRecord } from "./types";
|
|
|
|
export interface ConversationContextInput {
|
|
contextBefore: MessageRecord[];
|
|
targets: MessageRecord[];
|
|
maxTokens: number;
|
|
}
|
|
|
|
/**
|
|
* Formats a timestamp to ISO 8601 string
|
|
*/
|
|
function formatTimestamp(ms: number): string {
|
|
return new Date(ms).toISOString();
|
|
}
|
|
|
|
/**
|
|
* Estimates token count for a string (rough approximation: ~4 chars per token)
|
|
*/
|
|
function estimateTokens(text: string): number {
|
|
return Math.ceil(text.length / 4);
|
|
}
|
|
|
|
/**
|
|
* Builds conversation prompt messages with context and targets
|
|
* - Marks target messages with [target], prior context with [context]
|
|
* - Uses edited_content when present, otherwise content
|
|
* - Maintains chronological order
|
|
* - Respects maxTokens budget, prioritizing targets and most recent context
|
|
*/
|
|
export function buildConversationPromptMessages(
|
|
input: ConversationContextInput,
|
|
): string[] {
|
|
const { contextBefore, targets, maxTokens } = input;
|
|
|
|
// Format all messages
|
|
const formatMessage = (msg: MessageRecord, label: string): string => {
|
|
const content = msg.edited_content ?? msg.content;
|
|
const timestamp = formatTimestamp(msg.created_at);
|
|
return `[${label}] id=${msg.id} time=${timestamp} user=${msg.username}: ${content}`;
|
|
};
|
|
|
|
const targetLines = targets.map((msg) => formatMessage(msg, "target"));
|
|
const contextLines = contextBefore.map((msg) =>
|
|
formatMessage(msg, "context"),
|
|
);
|
|
|
|
// Calculate tokens for targets (always include)
|
|
let usedTokens = targetLines.reduce(
|
|
(sum, line) => sum + estimateTokens(line),
|
|
0,
|
|
);
|
|
|
|
// Add context lines in reverse chronological order (most recent first)
|
|
// until we hit the token budget
|
|
const selectedContextLines: string[] = [];
|
|
for (let i = contextLines.length - 1; i >= 0; i--) {
|
|
const line = contextLines[i];
|
|
const lineTokens = estimateTokens(line);
|
|
if (usedTokens + lineTokens <= maxTokens) {
|
|
selectedContextLines.unshift(line); // prepend to maintain chronological order
|
|
usedTokens += lineTokens;
|
|
}
|
|
}
|
|
|
|
// Combine: context (chronological) + targets (chronological)
|
|
const allMessages = [...selectedContextLines, ...targetLines];
|
|
|
|
// Sort by timestamp to ensure chronological order
|
|
allMessages.sort((a, b) => {
|
|
const timeA = a.match(/time=([^\s]+)/)?.[1] ?? "";
|
|
const timeB = b.match(/time=([^\s]+)/)?.[1] ?? "";
|
|
return timeA.localeCompare(timeB);
|
|
});
|
|
|
|
return allMessages;
|
|
}
|