import type { ConversationSummary } from "$lib/stores/conversations"; /** * Sanitises a string for safe JSON serialization through Tauri IPC. * Removes control characters and lone surrogates that could cause issues * during JSON serialization/deserialization. */ export function sanitizeForJson(text: string): string { // Remove control characters except for common whitespace (tab, newline, carriage return) // These can cause JSON parsing issues and are rarely meaningful in conversation summaries. // eslint-disable-next-line no-control-regex -- regex uses control character codes let sanitized = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); // Remove extended ASCII control chars (C1 control codes) sanitized = sanitized.replace(/[\x80-\x9F]/g, ""); // Remove lone surrogates (U+D800 to U+DFFF) which cause "unexpected end of hex escape" // errors in serde_json when they appear without proper pairing. // These are invalid in JSON and can cause parse failures. sanitized = sanitized.replace(/[\uD800-\uDFFF]/g, ""); return sanitized; } /** * Generates a prompt to ask Claude to summarise a conversation. * This can be sent as a user message to get a summary. */ export function generateSummaryPrompt(conversationContent: string): string { return `Please provide a concise summary of our conversation so far. Focus on: 1. Key topics discussed 2. Important decisions or conclusions made 3. Any ongoing tasks or context that would be helpful to remember 4. Code changes or files that were modified Keep the summary brief but comprehensive enough to continue our work in a new session. Here is our conversation: ${conversationContent} Please provide the summary now:`; } /** * Generates a context injection message to prepend to a new conversation. * This provides Claude with context from a previous session. */ export function generateContextInjection(summary: ConversationSummary): string { return `[Previous Session Context] The following is a summary from our previous conversation (${summary.messageCount} messages, approximately ${summary.tokenEstimate.toLocaleString()} tokens): ${summary.content} [End of Previous Context] Please continue from where we left off, or let me know if you need any clarification about the previous context.`; } /** * Estimates the token count for a given string. * Uses a rough approximation of ~4 characters per token. */ export function estimateTokens(text: string): number { return Math.ceil(text.length / 4); } /** * Creates a ConversationSummary object from summary content. */ export function createSummary( content: string, messageCount: number, originalTokenEstimate: number ): ConversationSummary { return { generatedAt: new Date(), content, messageCount, tokenEstimate: originalTokenEstimate, }; } /** * Determines if a conversation should be compacted based on token usage. * Returns true if the conversation is using more than the threshold percentage * of the context window. */ export function shouldSuggestCompaction( contextTokensUsed: number, contextWindowLimit: number, thresholdPercent: number = 60 ): boolean { if (contextWindowLimit === 0) return false; const utilisationPercent = (contextTokensUsed / contextWindowLimit) * 100; return utilisationPercent >= thresholdPercent; } /** * Formats a token count for display. */ export function formatTokenCount(tokens: number): string { if (tokens >= 1000000) { return `${(tokens / 1000000).toFixed(1)}M`; } if (tokens >= 1000) { return `${(tokens / 1000).toFixed(1)}K`; } return tokens.toString(); }