feat: initial Lam Agent extension (#1)

This commit was merged in pull request #1.
This commit is contained in:
2026-10-03 20:32:40 +00:00
parent 1d39aabb50
commit 8b507e7953
36 changed files with 7136 additions and 2 deletions
+56
View File
@@ -0,0 +1,56 @@
/** Small pure helpers shared across the extension. Kept dependency-free so they are trivial to test. */
/**
* Collapses whitespace and clips to `max` characters, adding an ellipsis when clipped.
* Used for list rows and session titles, where a stray newline would break the layout.
*/
export function truncate(text: string, max: number): string {
const collapsed = text.replace(/\s+/g, " ").trim();
if (collapsed.length <= max) return collapsed;
return `${collapsed.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
}
/** "just now", "12m ago", "3h ago", then an absolute date once it is older than a day. */
export function formatRelativeTime(timestamp: number, now: number = Date.now()): string {
const deltaSeconds = Math.round((now - timestamp) / 1000);
if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) return "just now";
if (deltaSeconds < 45) return "just now";
const minutes = Math.round(deltaSeconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.round(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.round(hours / 24);
if (days < 7) return `${days}d ago`;
return new Date(timestamp).toLocaleDateString(undefined, { month: "short", day: "numeric" });
}
/** Pulls a human-readable message out of anything that can be thrown. */
export function errorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
try {
return JSON.stringify(error);
} catch {
return String(error);
}
}
/** True when a rejected promise came from an AbortController rather than a real failure. */
export function isAbortError(error: unknown): boolean {
return error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted"));
}
/**
* Builds the first line of a prompt into a session title.
* Prompt text often starts with markdown decoration, which reads badly in a list row.
*/
export function titleFromPrompt(prompt: string, max: number): string {
const firstLine = prompt.split("\n").find((line) => line.trim().length > 0) ?? prompt;
const cleaned = firstLine.replace(/^[#>*\-\s]+/, "").trim();
return truncate(cleaned || "New chat", max);
}