markdown agent files

This commit is contained in:
Ramnique Singh 2025-12-18 10:10:10 +05:30
parent 8c686029cb
commit e40c767336
7 changed files with 79 additions and 19 deletions

View file

@ -29,7 +29,7 @@ export const Agent = z.object({
name: z.string(),
provider: z.string().optional(),
model: z.string().optional(),
description: z.string(),
description: z.string().optional(),
instructions: z.string(),
tools: z.record(z.string(), ToolAttachment).optional(),
});

View file

@ -1,8 +1,12 @@
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>[]>;
@ -13,33 +17,76 @@ export interface IAgentsRepo {
}
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 json files in workdir/agents/
const agentsDir = path.join(WorkDir, "agents");
const files = await fs.readdir(agentsDir);
for (const file of files) {
const contents = await fs.readFile(path.join(agentsDir, file), "utf8");
result.push(Agent.parse(JSON.parse(contents)));
// 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>> {
const contents = await fs.readFile(path.join(WorkDir, "agents", `${id}.json`), "utf8");
return Agent.parse(JSON.parse(contents));
return this.parseAgentMd(path.join(this.agentsDir, `${id}.md`));
}
async create(agent: z.infer<typeof Agent>): Promise<void> {
await fs.writeFile(path.join(WorkDir, "agents", `${agent.name}.json`), JSON.stringify(agent, null, 2));
await fs.writeFile(path.join(this.agentsDir, `${agent.name}.md`), agent.instructions);
}
async update(id: string, agent: z.infer<typeof Agent>): Promise<void> {
await fs.writeFile(path.join(WorkDir, "agents", `${id}.json`), JSON.stringify(agent, null, 2));
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(WorkDir, "agents", `${id}.json`));
await fs.unlink(path.join(this.agentsDir, `${id}.md`));
}
}

View file

@ -13,7 +13,6 @@ export class InMemoryBus implements IBus {
private subscribers: Map<string, ((event: z.infer<typeof RunEvent>) => Promise<void>)[]> = new Map();
async publish(event: z.infer<typeof RunEvent>): Promise<void> {
console.log(this.subscribers);
const pending: Promise<void>[] = [];
for (const subscriber of this.subscribers.get(event.runId) || []) {
pending.push(subscriber(event));
@ -21,7 +20,6 @@ export class InMemoryBus implements IBus {
for (const subscriber of this.subscribers.get('*') || []) {
pending.push(subscriber(event));
}
console.log(pending.length);
await Promise.all(pending);
}
@ -30,7 +28,6 @@ export class InMemoryBus implements IBus {
this.subscribers.set(runId, []);
}
this.subscribers.get(runId)!.push(handler);
console.log(this.subscribers);
return () => {
this.subscribers.get(runId)!.splice(this.subscribers.get(runId)!.indexOf(handler), 1);
};

View file

@ -621,7 +621,6 @@ const routes = new Hono()
unsub = await bus.subscribe('*', async (event) => {
if (aborted) return;
console.log('got ev', event);
await stream.writeSSE({
data: JSON.stringify(event),
event: "message",

View file

@ -941,7 +941,7 @@ function AgentPickerModal({
onCancel: () => void;
}) {
const items = agents.map((agent) => ({
label: `${agent.name} ${truncate(agent.description, 40)}`,
label: `${agent.name}${agent.description ? ` ${truncate(agent.description, 40)}` : ""}`,
value: agent.name,
}));
return (