mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-03 20:41:07 +02:00
94 lines
No EOL
3.3 KiB
TypeScript
94 lines
No EOL
3.3 KiB
TypeScript
import { WorkDir } from "../config/config.js";
|
|
import fs from "fs/promises";
|
|
import { glob } from "node:fs/promises";
|
|
import path from "path";
|
|
import z from "zod";
|
|
import { Agent } from "./agents.js";
|
|
import { parse, stringify } from "yaml";
|
|
|
|
const UpdateAgentSchema = Agent.omit({ name: true });
|
|
|
|
export interface IAgentsRepo {
|
|
list(): Promise<z.infer<typeof Agent>[]>;
|
|
fetch(id: string): Promise<z.infer<typeof Agent>>;
|
|
create(agent: z.infer<typeof Agent>): Promise<void>;
|
|
update(id: string, agent: z.infer<typeof Agent>): Promise<void>;
|
|
delete(id: string): Promise<void>;
|
|
}
|
|
|
|
export class FSAgentsRepo implements IAgentsRepo {
|
|
private readonly agentsDir = path.join(WorkDir, "agents");
|
|
|
|
async list(): Promise<z.infer<typeof Agent>[]> {
|
|
const result: z.infer<typeof Agent>[] = [];
|
|
|
|
// list all md files in workdir/agents/
|
|
const matches = await Array.fromAsync(glob("**/*.md", { cwd: this.agentsDir }));
|
|
for (const file of matches) {
|
|
try {
|
|
const agent = await this.parseAgentMd(path.join(this.agentsDir, file));
|
|
result.push(agent);
|
|
} catch (error) {
|
|
console.error(`Error parsing agent ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
continue;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private async parseAgentMd(filePath: string): Promise<z.infer<typeof Agent>> {
|
|
const raw = await fs.readFile(filePath, "utf8");
|
|
|
|
// strip the path prefix from the file name
|
|
// and the .md extension
|
|
const agentName = filePath
|
|
.replace(this.agentsDir + "/", "")
|
|
.replace(/\.md$/, "");
|
|
let agent: z.infer<typeof Agent> = {
|
|
name: agentName,
|
|
instructions: raw,
|
|
};
|
|
let content = raw;
|
|
|
|
// check for frontmatter markers at start
|
|
if (raw.startsWith("---")) {
|
|
const end = raw.indexOf("\n---", 3);
|
|
|
|
if (end !== -1) {
|
|
const fm = raw.slice(3, end).trim(); // YAML text
|
|
content = raw.slice(end + 4).trim(); // body after frontmatter
|
|
const yaml = parse(fm);
|
|
const parsed = Agent
|
|
.omit({ name: true, instructions: true })
|
|
.parse(yaml);
|
|
agent = {
|
|
...agent,
|
|
...parsed,
|
|
instructions: content,
|
|
};
|
|
}
|
|
}
|
|
|
|
return agent;
|
|
}
|
|
|
|
async fetch(id: string): Promise<z.infer<typeof Agent>> {
|
|
return this.parseAgentMd(path.join(this.agentsDir, `${id}.md`));
|
|
}
|
|
|
|
async create(agent: z.infer<typeof Agent>): Promise<void> {
|
|
const { instructions, ...rest } = agent;
|
|
const contents = `---\n${stringify(rest)}\n---\n${instructions}`;
|
|
await fs.writeFile(path.join(this.agentsDir, `${agent.name}.md`), contents);
|
|
}
|
|
|
|
async update(id: string, agent: z.infer<typeof UpdateAgentSchema>): Promise<void> {
|
|
const { instructions, ...rest } = agent;
|
|
const contents = `---\n${stringify(rest)}\n---\n${instructions}`;
|
|
await fs.writeFile(path.join(this.agentsDir, `${id}.md`), contents);
|
|
}
|
|
|
|
async delete(id: string): Promise<void> {
|
|
await fs.unlink(path.join(this.agentsDir, `${id}.md`));
|
|
}
|
|
} |