2525import vertexai
2626from google .auth import default
2727from google .auth .transport .requests import Request as GoogleAuthRequest
28+ from rich .console import Console
29+
30+ console = Console (highlight = False )
31+ console_err = Console (stderr = True , highlight = False )
2832
2933
3034def get_discovery_engine_endpoint (location : str ) -> str :
@@ -51,35 +55,52 @@ def get_discovery_engine_endpoint(location: str) -> str:
5155 return f"https://{ location } -discoveryengine.googleapis.com"
5256
5357
54- def get_agent_engine_id (
55- agent_engine_id : str | None , metadata_file : str = "deployment_metadata.json"
56- ) -> str :
57- """Get the agent engine ID from parameter or deployment metadata.
58+ def get_agent_engine_id_from_metadata (
59+ metadata_file : str = "deployment_metadata.json" ,
60+ ) -> str | None :
61+ """Try to read the agent engine ID from deployment metadata.
5862
5963 Args:
60- agent_engine_id: Optional agent engine resource name
6164 metadata_file: Path to deployment metadata JSON file
6265
6366 Returns:
64- The agent engine resource name
65-
66- Raises:
67- ValueError: If agent_engine_id is not provided and metadata file doesn't exist
67+ The agent engine resource name if found, None otherwise
6868 """
69- if agent_engine_id :
70- return agent_engine_id
71-
72- # Try to read from deployment_metadata.json
7369 metadata_path = Path (metadata_file )
7470 if not metadata_path .exists ():
75- raise ValueError (
76- f"No agent engine ID provided and { metadata_file } not found. "
77- "Please provide --agent-engine-id or deploy your agent first."
78- )
71+ return None
72+
73+ try :
74+ with open (metadata_path , encoding = "utf-8" ) as f :
75+ metadata = json .load (f )
76+ return metadata .get ("remote_agent_engine_id" )
77+ except (json .JSONDecodeError , KeyError ):
78+ return None
79+
80+
81+ def parse_agent_engine_id (agent_engine_id : str ) -> dict [str , str ] | None :
82+ """Parse an Agent Engine resource name to extract components.
83+
84+ Args:
85+ agent_engine_id: Agent Engine resource name
86+ (e.g., projects/PROJECT_NUM/locations/REGION/reasoningEngines/ENGINE_ID)
7987
80- with open (metadata_path , encoding = "utf-8" ) as f :
81- metadata = json .load (f )
82- return metadata ["remote_agent_engine_id" ]
88+ Returns:
89+ Dictionary with 'project', 'location', 'engine_id' keys, or None if invalid format
90+ """
91+ parts = agent_engine_id .split ("/" )
92+ if (
93+ len (parts ) == 6
94+ and parts [0 ] == "projects"
95+ and parts [2 ] == "locations"
96+ and parts [4 ] == "reasoningEngines"
97+ ):
98+ return {
99+ "project" : parts [1 ],
100+ "location" : parts [3 ],
101+ "engine_id" : parts [5 ],
102+ }
103+ return None
83104
84105
85106def get_access_token () -> str :
@@ -136,6 +157,101 @@ def get_agent_engine_metadata(agent_engine_id: str) -> tuple[str | None, str | N
136157 return None , None
137158
138159
160+ def prompt_for_agent_engine_id (default_from_metadata : str | None ) -> str :
161+ """Prompt user for Agent Engine ID with optional default.
162+
163+ Args:
164+ default_from_metadata: Default value from deployment_metadata.json if available
165+
166+ Returns:
167+ The Agent Engine resource name
168+ """
169+ if default_from_metadata :
170+ console .print ("\n Found Agent Engine ID from deployment_metadata.json:" )
171+ console .print (f" [bold]{ default_from_metadata } [/]" )
172+ use_default = click .confirm (
173+ "Use this Agent Engine ID?" , default = True , show_default = True
174+ )
175+ if use_default :
176+ return default_from_metadata
177+
178+ console .print (
179+ "\n Enter your Agent Engine resource name"
180+ "\n [blue]Example: projects/123456789/locations/us-central1/reasoningEngines/1234567890[/]"
181+ "\n (You can find this in the Agent Builder Console or deployment_metadata.json)"
182+ )
183+
184+ while True :
185+ agent_engine_id = click .prompt ("Agent Engine ID" , type = str ).strip ()
186+ parsed = parse_agent_engine_id (agent_engine_id )
187+ if parsed :
188+ return agent_engine_id
189+ else :
190+ console_err .print (
191+ "❌ Invalid format. Expected: projects/{project}/locations/{location}/reasoningEngines/{id}" ,
192+ style = "bold red" ,
193+ )
194+
195+
196+ def prompt_for_gemini_enterprise_components (
197+ default_project : str | None = None ,
198+ ) -> str :
199+ """Prompt user for Gemini Enterprise resource components and construct full ID.
200+
201+ Args:
202+ default_project: Default project number from Agent Engine ID
203+
204+ Returns:
205+ Full Gemini Enterprise app resource name
206+ """
207+ console .print ("\n [blue]" + "=" * 70 + "[/]" )
208+ console .print ("[blue]GEMINI ENTERPRISE CONFIGURATION[/]" )
209+ console .print ("[blue]" + "=" * 70 + "[/]" )
210+
211+ console .print (
212+ "\n You need to provide the Gemini Enterprise app details."
213+ "\n Find these in: Google Cloud Console → Gemini Enterprise → Apps"
214+ "\n Copy the ID from the 'ID' column for your Gemini Enterprise instance."
215+ )
216+
217+ while True :
218+ # Project number
219+ if default_project :
220+ console .print (f"\n [dim]Default from Agent Engine: { default_project } [/]" )
221+ project_number = click .prompt (
222+ "Project number" , type = str , default = default_project or ""
223+ ).strip ()
224+
225+ # Location - GE apps are typically in 'global', 'us', or 'eu'
226+ console .print ("\n Gemini Enterprise apps are in: global, us, or eu" )
227+ location = click .prompt (
228+ "Location/Region" ,
229+ type = str ,
230+ default = "global" ,
231+ show_default = True ,
232+ ).strip ()
233+
234+ # Gemini Enterprise short ID
235+ console .print (
236+ "\n Enter your Gemini Enterprise ID (from the 'ID' column in the Apps table)."
237+ "\n [blue]Example: gemini-enterprise-1762990_8862980842627[/]"
238+ )
239+ ge_short_id = click .prompt ("Gemini Enterprise ID" , type = str ).strip ()
240+
241+ # Construct full resource name
242+ # Format: projects/{project_number}/locations/{location}/collections/default_collection/engines/{ge_id}
243+ full_id = f"projects/{ project_number } /locations/{ location } /collections/default_collection/engines/{ ge_short_id } "
244+
245+ console .print ("\n Constructed Gemini Enterprise App ID:" )
246+ console .print (f" [bold]{ full_id } [/]" )
247+ confirmed = click .confirm ("Is this correct?" , default = True )
248+
249+ if confirmed :
250+ return full_id
251+
252+ click .echo ("Let's try again..." )
253+
254+
139255def register_agent (
140256 agent_engine_id : str ,
141257 gemini_enterprise_app_id : str ,
@@ -237,20 +353,19 @@ def register_agent(
237353 "adk_agent_definition" : adk_agent_definition ,
238354 }
239355
240- print ("Registering agent to Gemini Enterprise..." )
241- print (f" Agent Engine: { agent_engine_id } " )
242- print (f" Gemini Enterprise App: { gemini_enterprise_app_id } " )
243- print (f" Display Name: { display_name } " )
244- print (f" API Endpoint: { url } " )
356+ console .print ("\n [blue]Registering agent to Gemini Enterprise...[/]" )
357+ console .print (f" Agent Engine: { agent_engine_id } " )
358+ console .print (f" Gemini Enterprise App: { gemini_enterprise_app_id } " )
359+ console .print (f" Display Name: { display_name } " )
245360
246361 try :
247362 # Try to create a new registration first
248363 response = requests .post (url , headers = headers , json = payload , timeout = 30 )
249364 response .raise_for_status ()
250365
251366 result = response .json ()
252- print ("\n ✅ Successfully registered agent to Gemini Enterprise!" )
253- print (f" Agent Name: { result .get ('name' , 'N/A' )} " )
367+ console . print ("\n ✅ Successfully registered agent to Gemini Enterprise!" )
368+ console . print (f" Agent Name: { result .get ('name' , 'N/A' )} " )
254369 return result
255370
256371 except requests .exceptions .HTTPError as http_err :
@@ -265,8 +380,8 @@ def register_agent(
265380 "already exists" in error_message .lower ()
266381 or "duplicate" in error_message .lower ()
267382 ):
268- print (
269- "\n ⚠️ Agent already registered. Updating existing registration..."
383+ console . print (
384+ "\n ⚠️ [yellow] Agent already registered. Updating existing registration...[/] "
270385 )
271386
272387 # For update, we need to use the specific agent resource name
@@ -294,7 +409,7 @@ def register_agent(
294409 agent_name = existing_agent .get ("name" )
295410 update_url = f"{ base_endpoint } /v1alpha/{ agent_name } "
296411
297- print (f" Updating agent: { agent_name } " )
412+ console . print (f" Updating agent: { agent_name } " )
298413
299414 # PATCH request to update
300415 update_response = requests .patch (
@@ -303,27 +418,33 @@ def register_agent(
303418 update_response .raise_for_status ()
304419
305420 result = update_response .json ()
306- print (
421+ console . print (
307422 "\n ✅ Successfully updated agent registration in Gemini Enterprise!"
308423 )
309- print (f" Agent Name: { result .get ('name' , 'N/A' )} " )
424+ console . print (f" Agent Name: { result .get ('name' , 'N/A' )} " )
310425 return result
311426 else :
312- print (
313- "\n ❌ Could not find existing agent to update" ,
314- file = sys . stderr ,
427+ console_err . print (
428+ "❌ [red] Could not find existing agent to update[/] " ,
429+ style = "bold red" ,
315430 )
316431 raise
317432 except (ValueError , KeyError ):
318433 # Failed to parse error response, raise original error
319434 pass
320435
321436 # If not an "already exists" error, or update failed, raise the original error
322- print (f"\n ❌ HTTP error occurred: { http_err } " , file = sys .stderr )
323- print (f" Response: { response .text } " , file = sys .stderr )
437+ console_err .print (
438+ f"\n ❌ [red]HTTP error occurred: { http_err } [/]" ,
439+ style = "bold red" ,
440+ )
441+ console_err .print (f" Response: { response .text } " )
324442 raise
325443 except requests .exceptions .RequestException as req_err :
326- print (f"\n ❌ Request error occurred: { req_err } " , file = sys .stderr )
444+ console_err .print (
445+ f"\n ❌ [red]Request error occurred: { req_err } [/]" ,
446+ style = "bold red" ,
447+ )
327448 raise
328449
329450
@@ -343,6 +464,7 @@ def register_agent(
343464 "--gemini-enterprise-app-id" ,
344465 help = "Gemini Enterprise app full resource name "
345466 "(e.g., projects/{project_number}/locations/{location}/collections/{collection}/engines/{engine_id}). "
467+ "If not provided, the command will prompt you interactively. "
346468 "Can also be set via ID or GEMINI_ENTERPRISE_APP_ID env var." ,
347469)
348470@click .option (
@@ -381,33 +503,58 @@ def register_gemini_enterprise(
381503 project_id : str | None ,
382504 authorization_id : str | None ,
383505) -> None :
384- """Register an Agent Engine to Gemini Enterprise."""
385- # Get agent engine ID
386- try :
387- resolved_agent_engine_id = get_agent_engine_id (agent_engine_id , metadata_file )
388- except ValueError as e :
389- raise click .ClickException (str (e )) from e
506+ """Register an Agent Engine to Gemini Enterprise.
390507
391- # Auto-detect display_name and description from Agent Engine
392- auto_display_name , auto_description = get_agent_engine_metadata (
393- resolved_agent_engine_id
394- )
508+ This command can run interactively or accept all parameters via command-line options.
509+ If key parameters are missing, it will prompt the user for input.
510+ """
511+ console .print ("\n 🤖 Agent Engine → Gemini Enterprise Registration\n " )
512+
513+ # Step 1: Get Agent Engine ID (with smart defaults from deployment_metadata.json)
514+ resolved_agent_engine_id = agent_engine_id
515+
516+ if not resolved_agent_engine_id :
517+ # Check if we have ID from env var (backward compatibility)
518+ env_id = os .getenv ("AGENT_ENGINE_ID" )
519+ if env_id :
520+ resolved_agent_engine_id = env_id
521+ else :
522+ # Try to get from metadata file
523+ metadata_id = get_agent_engine_id_from_metadata (metadata_file )
524+ # Prompt user (with default if available)
525+ resolved_agent_engine_id = prompt_for_agent_engine_id (metadata_id )
526+
527+ # Validate and parse Agent Engine ID
528+ parsed_ae = parse_agent_engine_id (resolved_agent_engine_id )
529+ if not parsed_ae :
530+ raise click .ClickException (
531+ f"Invalid Agent Engine ID format: { resolved_agent_engine_id } \n "
532+ "Expected: projects/{{project}}/locations/{{location}}/reasoningEngines/{{id}}"
533+ )
395534
396- # Handle gemini_enterprise_app_id with fallback to ID env var
535+ # Step 2: Get Gemini Enterprise App ID
397536 resolved_gemini_enterprise_app_id = (
398537 gemini_enterprise_app_id
399538 or os .getenv ("ID" )
400539 or os .getenv ("GEMINI_ENTERPRISE_APP_ID" )
401540 )
541+
402542 if not resolved_gemini_enterprise_app_id :
403- raise click .ClickException (
404- "Error: --gemini-enterprise-app-id or ID/GEMINI_ENTERPRISE_APP_ID env var required"
543+ # Interactive mode: prompt for components and construct the full ID
544+ resolved_gemini_enterprise_app_id = prompt_for_gemini_enterprise_components (
545+ default_project = parsed_ae ["project" ]
405546 )
406547
548+ # Step 3: Get display name and description (from Agent Engine metadata or defaults)
549+ auto_display_name , auto_description = get_agent_engine_metadata (
550+ resolved_agent_engine_id
551+ )
552+
407553 resolved_display_name = display_name or auto_display_name or "My Agent"
408554 resolved_description = description or auto_description or "AI Agent"
409555 resolved_tool_description = tool_description or resolved_description
410556
557+ # Step 4: Register the agent
411558 try :
412559 register_agent (
413560 agent_engine_id = resolved_agent_engine_id ,
0 commit comments