src / text.ts
// Text helpers shared by the digest, the tools and the commands.
// A rough token estimate. Good enough for budgets; the exact count depends on the model.
export function estimateTokens(text: string): number {
return Math.ceil(text.length / 4);
}
export function oneLine(text: string): string {
return text.replace(/\s+/g, " ").trim();
}
export function clip(text: string, maxChars: number): string {
const t = oneLine(text);
if (t.length <= maxChars) return t;
const cut = t.slice(0, maxChars - 1);
const space = cut.lastIndexOf(" ");
return (space > maxChars * 0.6 ? cut.slice(0, space) : cut) + "…";
}
const STOPWORDS = new Set("a an and are as at be by for from in into is it of on or so than that the then this to with".split(" "));
// Lowercase words joined by hyphens, for project names that are easy to type. Small words
// are left out so names stay short and distinctive.
export function slugify(text: string, maxChars = 40): string {
const words = text
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ")
.trim()
.split(" ")
.filter((w) => w && !STOPWORDS.has(w));
let slug = "";
for (const w of words) {
const next = slug ? `${slug}-${w}` : w;
if (next.length > maxChars) break;
slug = next;
if (slug.split("-").length >= 6) break;
}
return slug || "project";
}
// Removes blocks throughLine added to a message, so text taken from history is the user's own.
export function stripThroughLineBlocks(text: string): string {
return text.replace(/\n*\[throughLine[^\]]*\][\s\S]*?\[end throughLine\]\n*/g, "\n").replace(/\n*\[throughLine:[^\]]*\]\s*$/g, "").trim();
}
src / text.ts
// Text helpers shared by the digest, the tools and the commands.
// A rough token estimate. Good enough for budgets; the exact count depends on the model.
export function estimateTokens(text: string): number {
return Math.ceil(text.length / 4);
}
export function oneLine(text: string): string {
return text.replace(/\s+/g, " ").trim();
}
export function clip(text: string, maxChars: number): string {
const t = oneLine(text);
if (t.length <= maxChars) return t;
const cut = t.slice(0, maxChars - 1);
const space = cut.lastIndexOf(" ");
return (space > maxChars * 0.6 ? cut.slice(0, space) : cut) + "…";
}
const STOPWORDS = new Set("a an and are as at be by for from in into is it of on or so than that the then this to with".split(" "));
// Lowercase words joined by hyphens, for project names that are easy to type. Small words
// are left out so names stay short and distinctive.
export function slugify(text: string, maxChars = 40): string {
const words = text
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ")
.trim()
.split(" ")
.filter((w) => w && !STOPWORDS.has(w));
let slug = "";
for (const w of words) {
const next = slug ? `${slug}-${w}` : w;
if (next.length > maxChars) break;
slug = next;
if (slug.split("-").length >= 6) break;
}
return slug || "project";
}
// Removes blocks throughLine added to a message, so text taken from history is the user's own.
export function stripThroughLineBlocks(text: string): string {
return text.replace(/\n*\[throughLine[^\]]*\][\s\S]*?\[end throughLine\]\n*/g, "\n").replace(/\n*\[throughLine:[^\]]*\]\s*$/g, "").trim();
}