Download packages/coding-agent/examples/extensions/subagent/agents.ts from SaylorTwift/pi: direct link, hf CLI and curl.
- Browser
- Download file 4.56 kB
-
https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/examples/extensions/subagent/agents.ts
- Command line
-
hf download hf://SaylorTwift/pi/packages/coding-agent/examples/extensions/subagent/agents.ts
-
curl -L -o agents.ts https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/examples/extensions/subagent/agents.ts
4.56 kB
| /** | |
| * Agent discovery and configuration | |
| */ | |
| import * as fs from "node:fs"; | |
| import * as path from "node:path"; | |
| import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent"; | |
| export type AgentScope = "user" | "project" | "both"; | |
| export interface AgentConfig { | |
| name: string; | |
| description: string; | |
| tools?: string[]; | |
| model?: string; | |
| systemPrompt: string; | |
| source: "user" | "project"; | |
| filePath: string; | |
| } | |
| export interface AgentDiscoveryResult { | |
| agents: AgentConfig[]; | |
| projectAgentsDir: string | null; | |
| } | |
| /** | |
| * Raw agent frontmatter. Values are `unknown` because `parseFrontmatter` runs a | |
| * real YAML parser, so any scalar or collection can appear here. | |
| * | |
| * A type alias rather than an interface: `parseFrontmatter` constrains its | |
| * parameter to `Record<string, unknown>`, and only an alias picks up the | |
| * implicit index signature that satisfies it. | |
| */ | |
| type AgentFrontmatter = { | |
| name?: unknown; | |
| description?: unknown; | |
| tools?: unknown; | |
| model?: unknown; | |
| }; | |
| /** | |
| * Normalize a frontmatter `tools` value to a list of tool names. | |
| * | |
| * Both spellings are valid YAML and both are in use: | |
| * | |
| * tools: read, bash # string | |
| * tools: [read, bash] # array | |
| * | |
| * so accept either. Anything else (a number, a map, a nested list) yields no | |
| * tools rather than throwing: this runs inside agent discovery, where a single | |
| * bad file must not take down every other agent in the same directory. | |
| */ | |
| function parseToolList(value: unknown): string[] | undefined { | |
| const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : []; | |
| const tools = raw | |
| .filter((t): t is string => typeof t === "string") | |
| .map((t) => t.trim()) | |
| .filter(Boolean); | |
| return tools.length > 0 ? tools : undefined; | |
| } | |
| function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] { | |
| const agents: AgentConfig[] = []; | |
| if (!fs.existsSync(dir)) { | |
| return agents; | |
| } | |
| let entries: fs.Dirent[]; | |
| try { | |
| entries = fs.readdirSync(dir, { withFileTypes: true }); | |
| } catch { | |
| return agents; | |
| } | |
| for (const entry of entries) { | |
| if (!entry.name.endsWith(".md")) continue; | |
| if (!entry.isFile() && !entry.isSymbolicLink()) continue; | |
| const filePath = path.join(dir, entry.name); | |
| let content: string; | |
| try { | |
| content = fs.readFileSync(filePath, "utf-8"); | |
| } catch { | |
| continue; | |
| } | |
| const { frontmatter, body } = parseFrontmatter<AgentFrontmatter>(content); | |
| if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") { | |
| continue; | |
| } | |
| agents.push({ | |
| name: frontmatter.name, | |
| description: frontmatter.description, | |
| tools: parseToolList(frontmatter.tools), | |
| model: typeof frontmatter.model === "string" ? frontmatter.model : undefined, | |
| systemPrompt: body, | |
| source, | |
| filePath, | |
| }); | |
| } | |
| return agents; | |
| } | |
| function isDirectory(p: string): boolean { | |
| try { | |
| return fs.statSync(p).isDirectory(); | |
| } catch { | |
| return false; | |
| } | |
| } | |
| function findNearestProjectAgentsDir(cwd: string): string | null { | |
| let currentDir = cwd; | |
| while (true) { | |
| const candidate = path.join(currentDir, CONFIG_DIR_NAME, "agents"); | |
| if (isDirectory(candidate)) return candidate; | |
| const parentDir = path.dirname(currentDir); | |
| if (parentDir === currentDir) return null; | |
| currentDir = parentDir; | |
| } | |
| } | |
| export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult { | |
| const userDir = path.join(getAgentDir(), "agents"); | |
| const projectAgentsDir = findNearestProjectAgentsDir(cwd); | |
| const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user"); | |
| const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project"); | |
| const agentMap = new Map<string, AgentConfig>(); | |
| if (scope === "both") { | |
| for (const agent of userAgents) agentMap.set(agent.name, agent); | |
| for (const agent of projectAgents) agentMap.set(agent.name, agent); | |
| } else if (scope === "user") { | |
| for (const agent of userAgents) agentMap.set(agent.name, agent); | |
| } else { | |
| for (const agent of projectAgents) agentMap.set(agent.name, agent); | |
| } | |
| return { agents: Array.from(agentMap.values()), projectAgentsDir }; | |
| } | |
| export function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } { | |
| if (agents.length === 0) return { text: "none", remaining: 0 }; | |
| const listed = agents.slice(0, maxItems); | |
| const remaining = agents.length - listed.length; | |
| return { | |
| text: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join("; "), | |
| remaining, | |
| }; | |
| } | |