generated from nhcarrigan/template
1c45507cdf
### Explanation _No response_ ### Issue Closes #102 ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Reviewed-on: #103 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
111 lines
3.6 KiB
TypeScript
111 lines
3.6 KiB
TypeScript
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();
|
|
}
|