import { Config } from "../config/config" import z from "zod" import { Provider } from "../provider/provider" import { generateObject, type ModelMessage } from "ai" import PROMPT_GENERATE from "./generate.txt" import { SystemPrompt } from "../session/system" import { Instance } from "../project/instance" import { mergeDeep } from "remeda" export namespace Agent { export const Info = z .object({ name: z.string(), description: z.string().optional(), mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]), builtIn: z.boolean(), topP: z.number().optional(), temperature: z.number().optional(), permission: z.object({ edit: Config.Permission, bash: z.record(z.string(), Config.Permission), webfetch: Config.Permission.optional(), }), model: z .object({ modelID: z.string(), providerID: z.string(), }) .optional(), prompt: z.string().optional(), tools: z.record(z.string(), z.boolean()), options: z.record(z.string(), z.any()), }) .meta({ ref: "Agent", }) export type Info = z.infer const state = Instance.state(async () => { const cfg = await Config.get() const defaultTools = cfg.tools ?? {} const defaultPermission: Info["permission"] = { edit: "allow", bash: { "*": "allow", }, webfetch: "allow", } const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {}) const planPermission = mergeAgentPermissions( { edit: "deny", bash: { "cut*": "allow", "diff*": "allow", "du*": "allow", "file *": "allow", "find * -delete*": "ask", "find * -exec*": "ask", "find * -fprint*": "ask", "find * -fls*": "ask", "find * -fprintf*": "ask", "find * -ok*": "ask", "find *": "allow", "git diff*": "allow", "git log*": "allow", "git show*": "allow", "git status*": "allow", "git branch": "allow", "git branch -v": "allow", "grep*": "allow", "head*": "allow", "less*": "allow", "ls*": "allow", "more*": "allow", "pwd*": "allow", "rg*": "allow", "sort --output=*": "ask", "sort -o *": "ask", "sort*": "allow", "stat*": "allow", "tail*": "allow", "tree -o *": "ask", "tree*": "allow", "uniq*": "allow", "wc*": "allow", "whereis*": "allow", "which*": "allow", "*": "ask", }, webfetch: "allow", }, cfg.permission ?? {}, ) const result: Record = { general: { name: "general", description: "General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.", tools: { todoread: false, todowrite: false, ...defaultTools, }, options: {}, permission: agentPermission, mode: "subagent", builtIn: true, }, build: { name: "build", tools: { ...defaultTools }, options: {}, permission: agentPermission, mode: "primary", builtIn: true, }, plan: { name: "plan", options: {}, permission: planPermission, tools: { ...defaultTools, }, mode: "primary", builtIn: true, }, } for (const [key, value] of Object.entries(cfg.agent ?? {})) { if (value.disable) { delete result[key] continue } let item = result[key] if (!item) item = result[key] = { name: key, mode: "all", permission: agentPermission, options: {}, tools: {}, builtIn: false, } const { name, model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value item.options = { ...item.options, ...extra, } if (model) item.model = Provider.parseModel(model) if (prompt) item.prompt = prompt if (tools) item.tools = { ...item.tools, ...tools, } item.tools = { ...defaultTools, ...item.tools, } if (description) item.description = description if (temperature != undefined) item.temperature = temperature if (top_p != undefined) item.topP = top_p if (mode) item.mode = mode // just here for consistency & to prevent it from being added as an option if (name) item.name = name if (permission ?? cfg.permission) { item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {}) } } return result }) export async function get(agent: string) { return state().then((x) => x[agent]) } export async function list() { return state().then((x) => Object.values(x)) } export async function generate(input: { description: string }) { const defaultModel = await Provider.defaultModel() const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID) const system = SystemPrompt.header(defaultModel.providerID) system.push(PROMPT_GENERATE) const existing = await list() const result = await generateObject({ temperature: 0.3, prompt: [ ...system.map( (item): ModelMessage => ({ role: "system", content: item, }), ), { role: "user", content: `Create an agent configuration based on this request: \"${input.description}\".\n\nIMPORTANT: The following identifiers already exist and must NOT be used: ${existing.map((i) => i.name).join(", ")}\n Return ONLY the JSON object, no other text, do not wrap in backticks`, }, ], model: model.language, schema: z.object({ identifier: z.string(), whenToUse: z.string(), systemPrompt: z.string(), }), }) return result.object } } function mergeAgentPermissions( basePermission: any, overridePermission: any, ): Agent.Info["permission"] { if (typeof basePermission.bash === "string") { basePermission.bash = { "*": basePermission.bash, } } if (typeof overridePermission.bash === "string") { overridePermission.bash = { "*": overridePermission.bash, } } const merged = mergeDeep(basePermission ?? {}, overridePermission ?? {}) as any let mergedBash if (merged.bash) { if (typeof merged.bash === "string") { mergedBash = { "*": merged.bash, } } else if (typeof merged.bash === "object") { mergedBash = mergeDeep( { "*": "allow", }, merged.bash, ) } } const result: Agent.Info["permission"] = { edit: merged.edit ?? "allow", webfetch: merged.webfetch ?? "allow", bash: mergedBash ?? { "*": "allow" }, } return result }