@@ -740,6 +740,130 @@ impl CodeConfig {
740740 . map_err ( |e| CodeError :: Config ( format ! ( "Failed to deserialize HCL config: {}" , e) ) )
741741 }
742742
743+ /// Parse configuration from an ACL string.
744+ ///
745+ /// ACL (Agent Configuration Language) is similar to HCL but uses labeled blocks
746+ /// like `providers "openai" { }` instead of `providers { name = "openai" }`.
747+ pub fn from_acl ( content : & str ) -> Result < Self > {
748+ use a3s_acl:: { parse_acl, Value as AclValue } ;
749+
750+ let doc = parse_acl ( content)
751+ . map_err ( |e| CodeError :: Config ( format ! ( "Failed to parse ACL: {}" , e) ) ) ?;
752+
753+ let mut config = Self :: default ( ) ;
754+
755+ for block in doc. blocks {
756+ match block. name . as_str ( ) {
757+ "default_model" => {
758+ // ACL: default_model = "openai/gpt-4" or just "openai/gpt-4" as label
759+ if let Some ( v) = block. attributes . get ( "default_model" ) {
760+ if let AclValue :: String ( s) = v {
761+ config. default_model = Some ( s. clone ( ) ) ;
762+ }
763+ } else if let Some ( s) = block. labels . first ( ) {
764+ config. default_model = Some ( s. clone ( ) ) ;
765+ }
766+ }
767+ "providers" => {
768+ // ACL: providers "name" { ... }
769+ // HCL: providers { name = "name" }
770+ let provider_name = block. labels . first ( ) . cloned ( ) . ok_or_else ( || {
771+ CodeError :: Config (
772+ "providers block requires a label (e.g., providers \" openai\" )" . into ( ) ,
773+ )
774+ } ) ?;
775+
776+ let mut provider = ProviderConfig {
777+ name : provider_name. clone ( ) ,
778+ api_key : None ,
779+ base_url : None ,
780+ headers : HashMap :: new ( ) ,
781+ session_id_header : None ,
782+ models : Vec :: new ( ) ,
783+ } ;
784+
785+ for ( key, value) in & block. attributes {
786+ match key. as_str ( ) {
787+ "apiKey" | "api_key" => {
788+ if let AclValue :: String ( s) = value {
789+ provider. api_key = Some ( s. clone ( ) ) ;
790+ }
791+ }
792+ "baseUrl" | "base_url" => {
793+ if let AclValue :: String ( s) = value {
794+ provider. base_url = Some ( s. clone ( ) ) ;
795+ }
796+ }
797+ _ => { }
798+ }
799+ }
800+
801+ // Process nested models blocks
802+ for model_block in & block. blocks {
803+ if model_block. name == "models" {
804+ let model_name =
805+ model_block. labels . first ( ) . cloned ( ) . ok_or_else ( || {
806+ CodeError :: Config (
807+ "models block requires a label (e.g., models \" gpt-4\" )"
808+ . into ( ) ,
809+ )
810+ } ) ?;
811+
812+ let mut model = ModelConfig {
813+ id : model_name. clone ( ) ,
814+ name : model_name. clone ( ) ,
815+ family : String :: new ( ) ,
816+ api_key : None ,
817+ base_url : None ,
818+ headers : HashMap :: new ( ) ,
819+ session_id_header : None ,
820+ attachment : false ,
821+ reasoning : false ,
822+ tool_call : true ,
823+ temperature : true ,
824+ release_date : None ,
825+ modalities : ModelModalities :: default ( ) ,
826+ cost : ModelCost :: default ( ) ,
827+ limit : ModelLimit :: default ( ) ,
828+ } ;
829+
830+ for ( key, value) in & model_block. attributes {
831+ match key. as_str ( ) {
832+ "name" => {
833+ if let AclValue :: String ( s) = value {
834+ model. name = s. clone ( ) ;
835+ }
836+ }
837+ "apiKey" | "api_key" => {
838+ if let AclValue :: String ( s) = value {
839+ model. api_key = Some ( s. clone ( ) ) ;
840+ }
841+ }
842+ "baseUrl" | "base_url" => {
843+ if let AclValue :: String ( s) = value {
844+ model. base_url = Some ( s. clone ( ) ) ;
845+ }
846+ }
847+ _ => { }
848+ }
849+ }
850+
851+ provider. models . push ( model) ;
852+ }
853+ }
854+
855+ config. providers . push ( provider) ;
856+ }
857+ _ => {
858+ // Other top-level blocks are not supported in ACL format for now
859+ // (queue, search, etc. are HCL-only)
860+ }
861+ }
862+ }
863+
864+ Ok ( config)
865+ }
866+
743867 /// Save configuration to a JSON file (used for persistence)
744868 ///
745869 /// Note: This saves as JSON format. To use HCL format, manually create .hcl files.
@@ -1042,6 +1166,59 @@ fn eval_template_expr(tmpl: &hcl::expr::TemplateExpr) -> JsonValue {
10421166 JsonValue :: String ( format ! ( "{}" , tmpl) )
10431167}
10441168
1169+ // ============================================================================
1170+ // ACL Parsing Helpers
1171+ // ============================================================================
1172+
1173+ use a3s_acl:: Value as AclValue ;
1174+ use std:: result:: Result as StdResult ;
1175+
1176+ /// Convert an ACL Value to a serde_json::Value for general deserialization.
1177+ fn acl_value_to_json ( value : & AclValue ) -> StdResult < serde_json:: Value , serde_json:: Error > {
1178+ match value {
1179+ AclValue :: String ( s) => Ok ( serde_json:: Value :: String ( s. clone ( ) ) ) ,
1180+ AclValue :: Number ( n) => {
1181+ // n is f64 - check if it's a whole number that fits in i64
1182+ if * n == n. floor ( ) && * n >= i64:: MIN as f64 && * n <= i64:: MAX as f64 {
1183+ let i = * n as i64 ;
1184+ Ok ( serde_json:: Value :: Number ( i. into ( ) ) )
1185+ } else {
1186+ serde_json:: Number :: from_f64 ( * n)
1187+ . map ( serde_json:: Value :: Number )
1188+ . ok_or_else ( || {
1189+ serde_json:: Error :: io ( std:: io:: Error :: new (
1190+ std:: io:: ErrorKind :: InvalidData ,
1191+ "Invalid number" ,
1192+ ) )
1193+ } )
1194+ }
1195+ }
1196+ AclValue :: Bool ( b) => Ok ( serde_json:: Value :: Bool ( * b) ) ,
1197+ AclValue :: Null => Ok ( serde_json:: Value :: Null ) ,
1198+ AclValue :: List ( items) => {
1199+ let arr: StdResult < Vec < _ > , _ > = items. iter ( ) . map ( acl_value_to_json) . collect ( ) ;
1200+ Ok ( serde_json:: Value :: Array ( arr?) )
1201+ }
1202+ AclValue :: Object ( pairs) => {
1203+ let map: StdResult < serde_json:: Map < String , _ > , _ > = pairs
1204+ . iter ( )
1205+ . map ( |( k, v) | acl_value_to_json ( v) . map ( |vv| ( k. clone ( ) , vv) ) )
1206+ . collect ( ) ;
1207+ Ok ( serde_json:: Value :: Object ( map?) )
1208+ }
1209+ AclValue :: Call ( name, args) => {
1210+ let args_json: StdResult < Vec < _ > , _ > = args. iter ( ) . map ( acl_value_to_json) . collect ( ) ;
1211+ let mut map = serde_json:: Map :: new ( ) ;
1212+ map. insert (
1213+ "__call" . to_string ( ) ,
1214+ serde_json:: Value :: String ( name. clone ( ) ) ,
1215+ ) ;
1216+ map. insert ( "__args" . to_string ( ) , serde_json:: Value :: Array ( args_json?) ) ;
1217+ Ok ( serde_json:: Value :: Object ( map) )
1218+ }
1219+ }
1220+ }
1221+
10451222#[ cfg( test) ]
10461223mod tests {
10471224 use super :: * ;
0 commit comments