2026-05-29 17:32:04 +07:00
|
|
|
import { formatMediaEvidenceForPrompt } from "./messageMetadata.js";
|
2026-05-21 12:03:31 +00:00
|
|
|
import type { MessageRecord } from "./types.js";
|
2026-05-14 19:10:13 +07:00
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-25 23:23:12 +07:00
|
|
|
* Estimates token count for a string (pessimistic approximation for Indonesian slang & JSON overhead)
|
2026-05-14 19:10:13 +07:00
|
|
|
*/
|
2026-05-25 23:23:12 +07:00
|
|
|
export function estimateTokens(text: string): number {
|
|
|
|
|
return Math.ceil(text.length / 3) + 15;
|
2026-05-14 19:10:13 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-25 23:23:12 +07:00
|
|
|
* Formats a single message for context or target display
|
2026-05-14 19:10:13 +07:00
|
|
|
*/
|
2026-05-25 23:23:12 +07:00
|
|
|
export function formatMessageForPrompt(
|
|
|
|
|
msg: MessageRecord,
|
|
|
|
|
label: "context" | "target",
|
|
|
|
|
): string {
|
|
|
|
|
const content = msg.edited_content ?? msg.content;
|
|
|
|
|
const timestamp = formatTimestamp(msg.created_at);
|
2026-05-29 17:32:04 +07:00
|
|
|
const mediaEvidence = formatMediaEvidenceForPrompt(msg.metadata);
|
|
|
|
|
const mediaSuffix = mediaEvidence ? ` ${mediaEvidence}` : "";
|
|
|
|
|
return `[${label}] id=${msg.id} time=${timestamp} user=${msg.username}: ${content}${mediaSuffix}`;
|
2026-05-25 23:23:12 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds conversation historical context without including targets.
|
|
|
|
|
* Calculates how much token budget targets use, and fills the rest with context.
|
|
|
|
|
*/
|
|
|
|
|
export function buildConversationContext(
|
2026-05-14 19:10:13 +07:00
|
|
|
input: ConversationContextInput,
|
|
|
|
|
): string[] {
|
|
|
|
|
const { contextBefore, targets, maxTokens } = input;
|
|
|
|
|
|
2026-05-25 23:23:12 +07:00
|
|
|
// Calculate tokens used by targets
|
|
|
|
|
let usedTokens = targets.reduce((sum, msg) => {
|
|
|
|
|
return sum + estimateTokens(formatMessageForPrompt(msg, "target"));
|
|
|
|
|
}, 0);
|
2026-05-14 19:10:13 +07:00
|
|
|
|
2026-05-25 23:23:12 +07:00
|
|
|
const selectedContextLines: string[] = [];
|
2026-05-14 19:10:13 +07:00
|
|
|
|
2026-05-25 23:23:12 +07:00
|
|
|
// Go backwards through context, taking most recent first
|
2026-05-21 01:55:50 +07:00
|
|
|
for (let i = contextBefore.length - 1; i >= 0; i--) {
|
|
|
|
|
const msg = contextBefore[i];
|
2026-05-25 23:23:12 +07:00
|
|
|
const line = formatMessageForPrompt(msg, "context");
|
2026-05-14 19:10:13 +07:00
|
|
|
const lineTokens = estimateTokens(line);
|
2026-05-25 23:23:12 +07:00
|
|
|
|
2026-05-14 19:10:13 +07:00
|
|
|
if (usedTokens + lineTokens <= maxTokens) {
|
2026-05-25 23:23:12 +07:00
|
|
|
// Unshift so oldest context is first in the array
|
|
|
|
|
selectedContextLines.unshift(line);
|
2026-05-14 19:10:13 +07:00
|
|
|
usedTokens += lineTokens;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 23:23:12 +07:00
|
|
|
return selectedContextLines;
|
2026-05-14 19:10:13 +07:00
|
|
|
}
|