11import asyncio
2+ import importlib .resources
23import json
34import os
5+ import shutil
46import uuid
7+ from collections .abc import Generator
8+ from enum import Enum
59from typing import Any , Callable , Dict , overload
610
11+ import click
712from langgraph .graph .state import CompiledStateGraph
813from uipath ._cli ._utils ._console import ConsoleLogger
914from uipath ._cli ._utils ._parse_ast import generate_bindings_json # type: ignore
1419console = ConsoleLogger ()
1520
1621
22+ class FileOperationStatus (str , Enum ):
23+ """Status of a file operation."""
24+
25+ CREATED = "created"
26+ UPDATED = "updated"
27+ SKIPPED = "skipped"
28+
29+
1730def resolve_refs (schema , root = None ):
1831 """Recursively resolves $ref references in a JSON schema."""
1932 if root is None :
@@ -96,6 +109,120 @@ def generate_schema_from_graph(
96109 return schema
97110
98111
112+ def generate_agent_md_file (
113+ target_directory : str ,
114+ file_name : str ,
115+ resource_name : str ,
116+ no_agents_md_override : bool ,
117+ ) -> tuple [str , FileOperationStatus ] | None :
118+ """Generate an agent-specific file from the packaged resource.
119+
120+ Args:
121+ target_directory: The directory where the file should be created.
122+ file_name: The name of the file should be created.
123+ resource_name: The name of the resource folder where should be the file.
124+ no_agents_md_override: Whether to override existing files.
125+
126+ Returns:
127+ A tuple of (file_name, status) where status is a FileOperationStatus:
128+ - CREATED: File was created
129+ - UPDATED: File was overwritten
130+ - SKIPPED: File exists and no_agents_md_override is True
131+ Returns None if an error occurred.
132+ """
133+ target_path = os .path .join (target_directory , file_name )
134+ will_override = os .path .exists (target_path )
135+
136+ if will_override and no_agents_md_override :
137+ return file_name , FileOperationStatus .SKIPPED
138+ try :
139+ source_path = importlib .resources .files (resource_name ).joinpath (file_name )
140+
141+ with importlib .resources .as_file (source_path ) as s_path :
142+ shutil .copy (s_path , target_path )
143+
144+ return (
145+ file_name ,
146+ FileOperationStatus .UPDATED
147+ if will_override
148+ else FileOperationStatus .CREATED ,
149+ )
150+
151+ except Exception as e :
152+ console .warning (f"Could not create { file_name } : { e } " )
153+ return None
154+
155+
156+ def generate_specific_agents_md_files (
157+ target_directory : str , no_agents_md_override : bool
158+ ) -> Generator [tuple [str , FileOperationStatus ], None , None ]:
159+ """Generate agent-specific files from the packaged resource.
160+
161+ Args:
162+ target_directory: The directory where the files should be created.
163+ no_agents_md_override: Whether to override existing files.
164+
165+ Yields:
166+ Tuple of (file_name, status) for each file operation, where status is a FileOperationStatus:
167+ - CREATED: File was created
168+ - UPDATED: File was overwritten
169+ - SKIPPED: File exists and was not overwritten
170+ """
171+ agent_dir = os .path .join (target_directory , ".agent" )
172+ os .makedirs (agent_dir , exist_ok = True )
173+
174+ file_configs = [
175+ (target_directory , "CLAUDE.md" , "uipath._resources" ),
176+ (agent_dir , "CLI_REFERENCE.md" , "uipath._resources" ),
177+ (agent_dir , "SDK_REFERENCE.md" , "uipath._resources" ),
178+ (target_directory , "AGENTS.md" , "uipath_langchain._resources" ),
179+ (agent_dir , "REQUIRED_STRUCTURE.md" , "uipath_langchain._resources" ),
180+ ]
181+
182+ for directory , file_name , resource_name in file_configs :
183+ result = generate_agent_md_file (
184+ directory , file_name , resource_name , no_agents_md_override
185+ )
186+ if result :
187+ yield result
188+
189+
190+ def generate_agents_md_files (options : dict [str , Any ]) -> None :
191+ """Generate agent MD files and log categorized summary.
192+
193+ Args:
194+ options: Options dictionary
195+ """
196+ current_directory = os .getcwd ()
197+ no_agents_md_override = options .get ("no_agents_md_override" , False )
198+
199+ created_files = []
200+ updated_files = []
201+ skipped_files = []
202+
203+ for file_name , status in generate_specific_agents_md_files (
204+ current_directory , no_agents_md_override
205+ ):
206+ if status == FileOperationStatus .CREATED :
207+ created_files .append (file_name )
208+ elif status == FileOperationStatus .UPDATED :
209+ updated_files .append (file_name )
210+ elif status == FileOperationStatus .SKIPPED :
211+ skipped_files .append (file_name )
212+
213+ if created_files :
214+ files_str = ", " .join (click .style (f , fg = "cyan" ) for f in created_files )
215+ console .success (f"Created: { files_str } " )
216+
217+ if updated_files :
218+ files_str = ", " .join (click .style (f , fg = "cyan" ) for f in updated_files )
219+ console .success (f"Updated: { files_str } " )
220+
221+ if skipped_files :
222+ files_str = ", " .join (click .style (f , fg = "yellow" ) for f in skipped_files )
223+ console .info (f"Skipped (already exist): { files_str } " )
224+
225+
99226async def langgraph_init_middleware_async (
100227 entrypoint : str ,
101228 options : dict [str , Any ] | None = None ,
@@ -185,7 +312,9 @@ async def langgraph_init_middleware_async(
185312 try :
186313 with open (mermaid_file_path , "w" ) as f :
187314 f .write (mermaid_content )
188- console .success (f" Created '{ mermaid_file_path } ' file." )
315+ console .success (
316+ f"Created { click .style (mermaid_file_path , fg = 'cyan' )} file."
317+ )
189318 except Exception as write_error :
190319 console .error (
191320 f"Error writing mermaid file for '{ graph_name } ': { str (write_error )} "
@@ -194,7 +323,10 @@ async def langgraph_init_middleware_async(
194323 should_continue = False ,
195324 should_include_stacktrace = True ,
196325 )
197- console .success (f" Created '{ config_path } ' file." )
326+ console .success (f"Created { click .style (config_path , fg = 'cyan' )} file." )
327+
328+ generate_agents_md_files (options )
329+
198330 return MiddlewareResult (should_continue = False )
199331
200332 except Exception as e :
0 commit comments