98 lines
2.6 KiB
TypeScript
98 lines
2.6 KiB
TypeScript
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<ChatMessage[]>;
|
|
append(role: MessageRole, content: string): Promise<ChatMessage>;
|
|
}
|
|
|
|
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);
|
|
},
|
|
};
|
|
}
|