import { createHash } from "node:crypto"; import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; import type { Extension } from "../../../kernel"; import { ExtensionId, Hook } from "../../catalog"; export type MessageRole = "system" | "user" | "assistant"; export interface ChatMessage { role: MessageRole; content: string; createdAt: string; } export interface WorkspaceService { home: string; projectPath: string; workspaceId: string; conversationId: string; messages(): Promise; append(role: MessageRole, content: string): Promise; } export interface WorkspaceOptions { home?: string; projectPath?: string; conversationId?: string; } export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extension { const home = resolve( options.home ?? process.env.LLM_TO_AGENT_HOME ?? join(homedir(), ".llm-to-agent"), ); const projectPath = resolve(options.projectPath ?? process.cwd()); const workspaceId = createHash("sha256").update(projectPath).digest("hex").slice(0, 16); const conversationId = options.conversationId ?? "default"; const workspaceDirectory = join(home, "workspaces", workspaceId); const conversationDirectory = join(workspaceDirectory, "conversations", conversationId); const messagesFile = join(conversationDirectory, "messages.jsonl"); const workspace: WorkspaceService = { home, projectPath, workspaceId, conversationId, async messages() { try { const content = await readFile(messagesFile, "utf8"); return content .split("\n") .filter(Boolean) .map((line) => JSON.parse(line) as ChatMessage); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return []; throw error; } }, async append(role, content) { const message = { role, content, createdAt: new Date().toISOString(), }; await appendFile(messagesFile, `${JSON.stringify(message)}\n`, "utf8"); return message; }, }; return { id: ExtensionId.Workspace, async setup(context) { await mkdir(conversationDirectory, { recursive: true }); await writeFile( join(workspaceDirectory, "workspace.json"), `${JSON.stringify( { id: workspaceId, kind: "linked", projectPath, activeConversationId: conversationId, }, null, 2, )}\n`, "utf8", ); context.add(Hook.Workspace, workspace); }, }; }