| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- 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<typeof Info>
- 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<string, Info> = {
- 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
- }
|