2025-07-25 01:20:43 +00:00
import { Config } from "../config/config"
2025-09-15 07:12:07 +00:00
import z from "zod/v4"
2025-07-25 01:20:43 +00:00
import { Provider } from "../provider/provider"
import { generateObject , type ModelMessage } from "ai"
import PROMPT_GENERATE from "./generate.txt"
import { SystemPrompt } from "../session/system"
2025-09-01 21:15:49 +00:00
import { Instance } from "../project/instance"
2025-08-12 15:39:39 +00:00
import { mergeDeep } from "remeda"
2025-07-25 01:20:43 +00:00
export namespace Agent {
export const Info = z
. object ( {
name : z.string ( ) ,
2025-08-07 20:32:12 +00:00
description : z.string ( ) . optional ( ) ,
mode : z.union ( [ z . literal ( "subagent" ) , z . literal ( "primary" ) , z . literal ( "all" ) ] ) ,
2025-08-14 22:58:44 +00:00
builtIn : z.boolean ( ) ,
2025-08-07 20:32:12 +00:00
topP : z.number ( ) . optional ( ) ,
temperature : z.number ( ) . optional ( ) ,
2025-08-12 15:39:39 +00:00
permission : z.object ( {
edit : Config.Permission ,
bash : z.record ( z . string ( ) , Config . Permission ) ,
webfetch : Config.Permission.optional ( ) ,
} ) ,
2025-07-25 01:20:43 +00:00
model : z
. object ( {
modelID : z.string ( ) ,
providerID : z.string ( ) ,
} )
. optional ( ) ,
prompt : z.string ( ) . optional ( ) ,
2025-09-15 07:12:07 +00:00
tools : z.record ( z . string ( ) , z . boolean ( ) ) ,
2025-08-11 01:34:28 +00:00
options : z.record ( z . string ( ) , z . any ( ) ) ,
2025-07-25 01:20:43 +00:00
} )
2025-09-15 07:12:07 +00:00
. meta ( {
2025-07-25 01:20:43 +00:00
ref : "Agent" ,
} )
export type Info = z . infer < typeof Info >
2025-08-07 20:32:12 +00:00
2025-09-01 21:15:49 +00:00
const state = Instance . state ( async ( ) = > {
2025-07-25 01:20:43 +00:00
const cfg = await Config . get ( )
2025-08-17 02:51:56 +00:00
const defaultTools = cfg . tools ? ? { }
2025-08-12 15:39:39 +00:00
const defaultPermission : Info [ "permission" ] = {
edit : "allow" ,
bash : {
"*" : "allow" ,
} ,
webfetch : "allow" ,
}
2025-08-13 13:01:17 +00:00
const agentPermission = mergeAgentPermissions ( defaultPermission , cfg . permission ? ? { } )
2025-08-21 22:25:31 +00:00
const planPermission = mergeAgentPermissions (
{
2025-09-09 03:00:14 +00:00
edit : "deny" ,
2025-08-21 22:25:31 +00:00
bash : "ask" ,
webfetch : "allow" ,
} ,
cfg . permission ? ? { } ,
)
2025-07-25 01:20:43 +00:00
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 ,
2025-08-17 02:51:56 +00:00
. . . defaultTools ,
2025-07-25 01:20:43 +00:00
} ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-13 13:01:17 +00:00
permission : agentPermission ,
2025-08-07 20:32:12 +00:00
mode : "subagent" ,
2025-08-14 22:58:44 +00:00
builtIn : true ,
2025-08-07 20:32:12 +00:00
} ,
build : {
name : "build" ,
2025-08-17 02:51:56 +00:00
tools : { . . . defaultTools } ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-13 13:01:17 +00:00
permission : agentPermission ,
2025-08-07 20:32:12 +00:00
mode : "primary" ,
2025-08-14 22:58:44 +00:00
builtIn : true ,
2025-08-07 20:32:12 +00:00
} ,
plan : {
name : "plan" ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-21 22:25:31 +00:00
permission : planPermission ,
2025-08-07 20:32:12 +00:00
tools : {
2025-08-17 02:51:56 +00:00
. . . defaultTools ,
2025-08-07 20:32:12 +00:00
} ,
mode : "primary" ,
2025-08-14 22:58:44 +00:00
builtIn : true ,
2025-07-25 01:20:43 +00:00
} ,
}
for ( const [ key , value ] of Object . entries ( cfg . agent ? ? { } ) ) {
2025-08-05 10:20:00 +00:00
if ( value . disable ) {
delete result [ key ]
continue
}
2025-07-25 01:20:43 +00:00
let item = result [ key ]
if ( ! item )
item = result [ key ] = {
name : key ,
2025-08-07 20:32:12 +00:00
mode : "all" ,
2025-08-13 13:01:17 +00:00
permission : agentPermission ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-07 20:32:12 +00:00
tools : { } ,
2025-08-14 22:58:44 +00:00
builtIn : false ,
2025-07-25 01:20:43 +00:00
}
2025-08-18 23:15:20 +00:00
const { name , model , prompt , tools , description , temperature , top_p , mode , permission , . . . extra } = value
2025-08-11 01:34:28 +00:00
item . options = {
. . . item . options ,
. . . extra ,
}
2025-08-12 15:39:39 +00:00
if ( model ) item . model = Provider . parseModel ( model )
if ( prompt ) item . prompt = prompt
if ( tools )
2025-07-25 01:20:43 +00:00
item . tools = {
. . . item . tools ,
2025-08-12 15:39:39 +00:00
. . . tools ,
2025-07-25 01:20:43 +00:00
}
2025-08-17 02:51:56 +00:00
item . tools = {
. . . defaultTools ,
. . . item . tools ,
}
2025-08-12 15:39:39 +00:00
if ( description ) item . description = description
if ( temperature != undefined ) item . temperature = temperature
if ( top_p != undefined ) item . topP = top_p
if ( mode ) item . mode = mode
2025-08-18 23:15:20 +00:00
// just here for consistency & to prevent it from being added as an option
if ( name ) item . name = name
2025-08-12 15:39:39 +00:00
if ( permission ? ? cfg . permission ) {
2025-08-13 13:01:17 +00:00
item . permission = mergeAgentPermissions ( cfg . permission ? ? { } , permission ? ? { } )
2025-08-12 15:39:39 +00:00
}
2025-07-25 01:20:43 +00:00
}
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
}
}
2025-08-13 13:01:17 +00:00
function mergeAgentPermissions ( basePermission : any , overridePermission : any ) : Agent . Info [ "permission" ] {
const merged = mergeDeep ( basePermission ? ? { } , overridePermission ? ? { } ) as any
let mergedBash
if ( merged . bash ) {
if ( typeof merged . bash === "string" ) {
mergedBash = {
"*" : merged . bash ,
}
}
// if granular permissions are provided, default to "ask"
if ( typeof merged . bash === "object" ) {
mergedBash = mergeDeep (
{
"*" : "ask" ,
} ,
merged . bash ,
)
}
}
const result : Agent.Info [ "permission" ] = {
edit : merged.edit ? ? "allow" ,
webfetch : merged.webfetch ? ? "allow" ,
bash : mergedBash ? ? { "*" : "allow" } ,
}
return result
}