reasoning-deployment-service 0.3.5__py3-none-any.whl → 0.3.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reasoning-deployment-service might be problematic. Click here for more details.
- reasoning_deployment_service/cli_editor/api_client.py +135 -1
- reasoning_deployment_service/cli_editor/cli_runner.py +46 -0
- reasoning_deployment_service/gui_editor/agent_checkbox_list.py +48 -0
- reasoning_deployment_service/gui_editor/src/core/api_client.py +88 -0
- reasoning_deployment_service/gui_editor/src/ui/agent_space_view.py +99 -7
- reasoning_deployment_service/gui_editor/src/ui/authorization_view.py +127 -13
- reasoning_deployment_service/reasoning_deployment_service.py +208 -8
- reasoning_deployment_service/runner.py +4 -2
- reasoning_deployment_service-0.3.7.dist-info/METADATA +211 -0
- {reasoning_deployment_service-0.3.5.dist-info → reasoning_deployment_service-0.3.7.dist-info}/RECORD +13 -12
- reasoning_deployment_service-0.3.5.dist-info/METADATA +0 -183
- {reasoning_deployment_service-0.3.5.dist-info → reasoning_deployment_service-0.3.7.dist-info}/WHEEL +0 -0
- {reasoning_deployment_service-0.3.5.dist-info → reasoning_deployment_service-0.3.7.dist-info}/entry_points.txt +0 -0
- {reasoning_deployment_service-0.3.5.dist-info → reasoning_deployment_service-0.3.7.dist-info}/top_level.txt +0 -0
|
@@ -16,28 +16,44 @@ class AuthorizationView(ttk.Frame):
|
|
|
16
16
|
self.api = api
|
|
17
17
|
self.log = log
|
|
18
18
|
self._auth_auto_loaded = False # Track if authorizations have been auto-loaded
|
|
19
|
-
|
|
20
19
|
# Cache authentication state to avoid repeated API calls
|
|
21
20
|
self._cached_auth_state = None
|
|
22
21
|
self._last_auth_check = 0
|
|
23
22
|
self._auth_cache_duration = 30 # 30 seconds
|
|
24
|
-
|
|
25
23
|
self._setup_ui()
|
|
26
24
|
|
|
27
25
|
def _setup_ui(self):
|
|
28
26
|
# Control buttons
|
|
29
27
|
btns = ttk.Frame(self)
|
|
30
28
|
btns.pack(fill="x", pady=(6, 4))
|
|
31
|
-
|
|
29
|
+
|
|
32
30
|
self.refresh_btn = StatusButton(btns, text="Refresh Authorizations", command=self.refresh)
|
|
33
31
|
self.refresh_btn.pack(side="left", padx=4)
|
|
34
|
-
|
|
32
|
+
|
|
35
33
|
self.delete_btn = StatusButton(btns, text="Delete Selected", command=self.delete_selected)
|
|
36
34
|
self.delete_btn.pack(side="left", padx=8)
|
|
37
|
-
|
|
35
|
+
|
|
38
36
|
self.status = tk.StringVar(value="Ready.")
|
|
39
37
|
ttk.Label(btns, textvariable=self.status).pack(side="right")
|
|
40
38
|
|
|
39
|
+
# Update scopes UI
|
|
40
|
+
scopes_frame = ttk.LabelFrame(self, text="Update Authorization Scopes")
|
|
41
|
+
scopes_frame.pack(fill="x", padx=4, pady=4)
|
|
42
|
+
ttk.Label(scopes_frame, text="Authorization ID:").grid(row=0, column=0, sticky="w")
|
|
43
|
+
self.auth_id_entry = ttk.Entry(scopes_frame, width=30)
|
|
44
|
+
self.auth_id_entry.grid(row=0, column=1, sticky="w")
|
|
45
|
+
ttk.Label(scopes_frame, text="Scopes:").grid(row=1, column=0, sticky="nw")
|
|
46
|
+
self.scopes_var_list = []
|
|
47
|
+
self.scopes_check_frame = ttk.Frame(scopes_frame)
|
|
48
|
+
self.scopes_check_frame.grid(row=1, column=1, sticky="nw")
|
|
49
|
+
self.add_scope_btn = ttk.Button(scopes_frame, text="Add Scope", command=self._add_scope_dialog)
|
|
50
|
+
self.add_scope_btn.grid(row=2, column=1, sticky="w", pady=(2,0))
|
|
51
|
+
ttk.Label(scopes_frame, text="OAuth Client ID:").grid(row=2, column=0, sticky="w")
|
|
52
|
+
self.client_id_entry = ttk.Entry(scopes_frame, width=40)
|
|
53
|
+
self.client_id_entry.grid(row=2, column=1, sticky="w")
|
|
54
|
+
self.update_scopes_btn = ttk.Button(scopes_frame, text="Update Scopes", command=self.update_scopes)
|
|
55
|
+
self.update_scopes_btn.grid(row=3, column=0, columnspan=2, pady=4)
|
|
56
|
+
|
|
41
57
|
# Info label
|
|
42
58
|
info_frame = ttk.Frame(self)
|
|
43
59
|
info_frame.pack(fill="x", padx=4, pady=4)
|
|
@@ -49,40 +65,94 @@ class AuthorizationView(ttk.Frame):
|
|
|
49
65
|
wrap.pack(fill="both", expand=True)
|
|
50
66
|
cols = ("id", "name")
|
|
51
67
|
self.tree = ttk.Treeview(wrap, columns=cols, show="headings", selectmode="extended")
|
|
52
|
-
|
|
68
|
+
|
|
53
69
|
for c, t, w in [
|
|
54
70
|
("id", "Authorization ID", 300),
|
|
55
71
|
("name", "Full Resource Name", 600),
|
|
56
72
|
]:
|
|
57
73
|
self.tree.heading(c, text=t)
|
|
58
74
|
self.tree.column(c, width=w, anchor="w")
|
|
59
|
-
|
|
75
|
+
|
|
60
76
|
self.tree.pack(side="left", fill="both", expand=True)
|
|
61
77
|
vsb = ttk.Scrollbar(wrap, orient="vertical", command=self.tree.yview)
|
|
62
78
|
self.tree.configure(yscroll=vsb.set)
|
|
63
79
|
vsb.pack(side="right", fill="y")
|
|
64
|
-
|
|
80
|
+
|
|
65
81
|
# Event bindings
|
|
66
82
|
self.tree.bind("<<TreeviewSelect>>", self._on_selection_change)
|
|
67
83
|
self.tree.bind("<Button-3>", self._popup)
|
|
68
|
-
|
|
84
|
+
|
|
69
85
|
# Context menu
|
|
70
86
|
self.menu = tk.Menu(self, tearoff=0)
|
|
71
87
|
self.menu.add_command(label="Delete", command=self.delete_selected)
|
|
72
|
-
|
|
88
|
+
|
|
73
89
|
# Debouncing for button updates
|
|
74
90
|
self._update_timer = None
|
|
75
|
-
|
|
91
|
+
|
|
76
92
|
# Store full authorization data
|
|
77
93
|
self._authorizations_data = {}
|
|
78
|
-
|
|
94
|
+
|
|
79
95
|
# Cache selection state to avoid redundant tree.selection() calls
|
|
80
96
|
self._cached_selection = None
|
|
81
97
|
self._selection_is_dirty = True
|
|
82
|
-
|
|
98
|
+
|
|
83
99
|
# Initialize button states without immediate API calls
|
|
84
100
|
self._update_button_states()
|
|
85
101
|
|
|
102
|
+
def update_scopes(self):
|
|
103
|
+
"""Update scopes for the given authorization."""
|
|
104
|
+
auth_id = self.auth_id_entry.get().strip()
|
|
105
|
+
scopes = [var.get().strip() for var, cb_var in self.scopes_var_list if cb_var.get() and var.get().strip()]
|
|
106
|
+
client_id = self.client_id_entry.get().strip()
|
|
107
|
+
if not auth_id or not scopes or not client_id:
|
|
108
|
+
messagebox.showwarning("Missing info", "Please fill all fields.")
|
|
109
|
+
return
|
|
110
|
+
self.status.set(f"Updating scopes for {auth_id}...")
|
|
111
|
+
def callback(result):
|
|
112
|
+
if isinstance(result, Exception):
|
|
113
|
+
self.status.set(f"Error: {result}")
|
|
114
|
+
self.log(f"❌ Error updating scopes: {result}")
|
|
115
|
+
return
|
|
116
|
+
self.status.set(f"Scopes updated for {auth_id}.")
|
|
117
|
+
self.log(f"✅ Scopes updated for {auth_id}: {result}")
|
|
118
|
+
async_operation(lambda: self.api.update_authorization_scopes(auth_id, scopes, client_id), callback=callback, ui_widget=self)
|
|
119
|
+
|
|
120
|
+
def _add_scope_dialog(self):
|
|
121
|
+
dialog = tk.Toplevel(self)
|
|
122
|
+
dialog.title("Add Scope")
|
|
123
|
+
ttk.Label(dialog, text="Enter new scope:").pack(padx=8, pady=8)
|
|
124
|
+
entry = ttk.Entry(dialog, width=50)
|
|
125
|
+
entry.pack(padx=8, pady=(0,8))
|
|
126
|
+
def on_add():
|
|
127
|
+
scope = entry.get().strip()
|
|
128
|
+
if scope:
|
|
129
|
+
self._add_scope_checkbox(scope, checked=True)
|
|
130
|
+
dialog.destroy()
|
|
131
|
+
ttk.Button(dialog, text="Add", command=on_add).pack(pady=(0,8))
|
|
132
|
+
entry.focus_set()
|
|
133
|
+
dialog.transient(self)
|
|
134
|
+
dialog.grab_set()
|
|
135
|
+
self.wait_window(dialog)
|
|
136
|
+
|
|
137
|
+
def _add_scope_checkbox(self, scope, checked=False):
|
|
138
|
+
var = tk.StringVar(value=scope)
|
|
139
|
+
cb_var = tk.BooleanVar(value=checked)
|
|
140
|
+
frame = ttk.Frame(self.scopes_check_frame)
|
|
141
|
+
cb = ttk.Checkbutton(frame, text=scope, variable=cb_var)
|
|
142
|
+
cb.pack(side="left")
|
|
143
|
+
del_btn = ttk.Button(frame, text="✕", width=2, command=lambda: self._remove_scope_checkbox(frame, var, cb_var))
|
|
144
|
+
del_btn.pack(side="left", padx=(4,0))
|
|
145
|
+
frame.pack(anchor="w", pady=1)
|
|
146
|
+
self.scopes_var_list.append((var, cb_var))
|
|
147
|
+
# Update checkbutton text if scope changes
|
|
148
|
+
def update_text(*_):
|
|
149
|
+
cb.config(text=var.get())
|
|
150
|
+
var.trace_add('write', update_text)
|
|
151
|
+
|
|
152
|
+
def _remove_scope_checkbox(self, frame, var, cb_var):
|
|
153
|
+
frame.destroy()
|
|
154
|
+
self.scopes_var_list = [(v, c) for v, c in self.scopes_var_list if v != var]
|
|
155
|
+
|
|
86
156
|
def _get_cached_auth_state(self) -> bool:
|
|
87
157
|
"""Get authentication state with local caching to reduce API calls."""
|
|
88
158
|
now = time.time()
|
|
@@ -127,6 +197,50 @@ class AuthorizationView(ttk.Frame):
|
|
|
127
197
|
# Immediate update - no timers or delays
|
|
128
198
|
self._update_button_states()
|
|
129
199
|
|
|
200
|
+
# Auto-populate update scopes fields if a single authorization is selected
|
|
201
|
+
sel = self._get_selection()
|
|
202
|
+
if len(sel) == 1:
|
|
203
|
+
item_id = sel[0]
|
|
204
|
+
auth_data = self._authorizations_data.get(item_id)
|
|
205
|
+
if auth_data:
|
|
206
|
+
self.auth_id_entry.delete(0, tk.END)
|
|
207
|
+
self.auth_id_entry.insert(0, auth_data.get("id", ""))
|
|
208
|
+
# Try to fetch scopes and client id from API if possible
|
|
209
|
+
try:
|
|
210
|
+
info = self.api.get_authorization_info(auth_data.get("id", ""))
|
|
211
|
+
import pprint
|
|
212
|
+
print("[DEBUG] get_authorization_info response:")
|
|
213
|
+
pprint.pprint(info)
|
|
214
|
+
scopes = []
|
|
215
|
+
sso = info.get("serverSideOauth2", {})
|
|
216
|
+
# Prefer parsing scopes from authorizationUri if present
|
|
217
|
+
if "authorizationUri" in sso and isinstance(sso["authorizationUri"], str):
|
|
218
|
+
import urllib.parse
|
|
219
|
+
parsed = urllib.parse.parse_qs(urllib.parse.urlparse(sso["authorizationUri"]).query)
|
|
220
|
+
scope_str = parsed.get("scope", [""])[0]
|
|
221
|
+
if scope_str:
|
|
222
|
+
# Split scopes by space, comma, or plus, and decode each
|
|
223
|
+
import re
|
|
224
|
+
raw_scopes = re.split(r"[ ,+]+", scope_str)
|
|
225
|
+
scopes = [urllib.parse.unquote_plus(s) for s in raw_scopes if s.strip()]
|
|
226
|
+
# Fallback to scopes field if present
|
|
227
|
+
if not scopes and "scopes" in info:
|
|
228
|
+
scopes = info["scopes"]
|
|
229
|
+
# Clear previous checkboxes
|
|
230
|
+
for child in self.scopes_check_frame.winfo_children():
|
|
231
|
+
child.destroy()
|
|
232
|
+
self.scopes_var_list.clear()
|
|
233
|
+
# Add one checkbox per parsed scope
|
|
234
|
+
for scope in scopes:
|
|
235
|
+
self._add_scope_checkbox(scope, checked=True)
|
|
236
|
+
client_id = sso.get("clientId", "")
|
|
237
|
+
if client_id:
|
|
238
|
+
self.client_id_entry.delete(0, tk.END)
|
|
239
|
+
self.client_id_entry.insert(0, client_id)
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(f"[ERROR] Exception in _on_selection_change: {e}")
|
|
242
|
+
pass
|
|
243
|
+
|
|
130
244
|
def refresh(self):
|
|
131
245
|
"""Refresh the list of authorizations."""
|
|
132
246
|
# Update button states immediately on click
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import json, os, subprocess, yaml, sys
|
|
2
2
|
import urllib.parse, vertexai, google.auth
|
|
3
3
|
import requests as _requests
|
|
4
|
-
from typing import Tuple
|
|
4
|
+
from typing import Dict, Optional, Tuple
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from dotenv import load_dotenv
|
|
7
7
|
from vertexai import agent_engines
|
|
8
|
+
from vertexai.agent_engines import AgentEngine
|
|
8
9
|
from google.adk.agents import BaseAgent
|
|
9
10
|
from google.auth.transport.requests import Request as GoogleAuthRequest
|
|
10
11
|
from google.api_core.exceptions import NotFound
|
|
11
12
|
import logging
|
|
12
13
|
from datetime import datetime
|
|
14
|
+
from urllib.parse import urlparse, parse_qs
|
|
13
15
|
|
|
14
16
|
DISCOVERY_ENGINE_URL = "https://discoveryengine.googleapis.com/v1alpha"
|
|
15
17
|
|
|
@@ -101,9 +103,9 @@ class ReasoningEngineDeploymentService:
|
|
|
101
103
|
self._generate_env_agent()
|
|
102
104
|
end_run = True
|
|
103
105
|
|
|
104
|
-
if not os.path.exists("
|
|
106
|
+
if not os.path.exists("agent.yaml"):
|
|
105
107
|
self._generate_example_yaml_config()
|
|
106
|
-
self.warning("Creating
|
|
108
|
+
self.warning("Creating agent.yaml file ... done")
|
|
107
109
|
end_run = True
|
|
108
110
|
|
|
109
111
|
self.warning("Please fill in the required values in the generated files and re-run the deployment.")
|
|
@@ -246,7 +248,7 @@ class ReasoningEngineDeploymentService:
|
|
|
246
248
|
for pattern in new_patterns:
|
|
247
249
|
f.write(f"{pattern}\n")
|
|
248
250
|
|
|
249
|
-
def _generate_example_yaml_config(self, path: str | Path = "
|
|
251
|
+
def _generate_example_yaml_config(self, path: str | Path = "agent.yaml", overwrite: bool = False) -> Path:
|
|
250
252
|
"""
|
|
251
253
|
Create an example YAML config matching the requested schema.
|
|
252
254
|
|
|
@@ -289,11 +291,11 @@ class ReasoningEngineDeploymentService:
|
|
|
289
291
|
|
|
290
292
|
def _load_agent_definition(self):
|
|
291
293
|
try:
|
|
292
|
-
with open("
|
|
294
|
+
with open("agent.yaml", "r") as f:
|
|
293
295
|
config = yaml.safe_load(f)
|
|
294
296
|
except FileNotFoundError:
|
|
295
297
|
self._generate_example_yaml_config()
|
|
296
|
-
self.error("Could not locate a valid
|
|
298
|
+
self.error("Could not locate a valid agent.yaml file. Generating example file in your directory.")
|
|
297
299
|
sys.exit(1)
|
|
298
300
|
|
|
299
301
|
try:
|
|
@@ -320,7 +322,7 @@ class ReasoningEngineDeploymentService:
|
|
|
320
322
|
self._authorization_id = auth.get("oauth_authorization_id", None)
|
|
321
323
|
self._environment_variables = environment_variables or []
|
|
322
324
|
except KeyError as e:
|
|
323
|
-
raise RuntimeError(f"Missing required key in
|
|
325
|
+
raise RuntimeError(f"Missing required key in agent.yaml: {e}")
|
|
324
326
|
|
|
325
327
|
def _load_deployment_environment_variables(self, deployment_environment: str):
|
|
326
328
|
required_vars = ['PROJECT_ID', 'PROJECT_NUMBER', 'PROJECT_LOCATION', 'STAGING_BUCKET', 'AGENT_SPACE_ENGINE']
|
|
@@ -684,4 +686,202 @@ class ReasoningEngineDeploymentService:
|
|
|
684
686
|
self.info("Creating agent space ... ")
|
|
685
687
|
self._deploy_to_agent_space()
|
|
686
688
|
else:
|
|
687
|
-
self._update_in_agent_space()
|
|
689
|
+
self._update_in_agent_space()
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _ensure_vertex_inited(self):
|
|
693
|
+
"""Initialize Vertex AI once and reuse to avoid repeated heavy init calls."""
|
|
694
|
+
if not self._vertex_inited:
|
|
695
|
+
vertexai.init()
|
|
696
|
+
self._vertex_inited = True
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def find_engine_by_name(self, engine_name: str) -> Optional[str]:
|
|
700
|
+
engines = list(agent_engines.list(filter=f'display_name="{engine_name}"'))
|
|
701
|
+
if not engines:
|
|
702
|
+
return None
|
|
703
|
+
if len(engines) > 1:
|
|
704
|
+
raise RuntimeError(f"Multiple engines found with display_name='{engine_name}'. Use unique names/labels.")
|
|
705
|
+
eng = engines[0]
|
|
706
|
+
if not isinstance(eng, AgentEngine) or not hasattr(eng, "resource_name"):
|
|
707
|
+
raise RuntimeError("Unexpected engine object; missing AgentEngine/resource_name.")
|
|
708
|
+
return eng.resource_name
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _get_headers(self) -> dict:
|
|
712
|
+
return {
|
|
713
|
+
"Authorization": f"Bearer {self._access_token()}",
|
|
714
|
+
"Content-Type": "application/json",
|
|
715
|
+
"Accept": "application/json",
|
|
716
|
+
"X-Goog-User-Project": str(self._project_number),
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
def find_authorization_by_id(self, authorization_id: str) -> Optional[str]:
|
|
720
|
+
name = f"projects/{self._project_id}/locations/global/authorizations/{authorization_id}"
|
|
721
|
+
url = f"{DISCOVERY_ENGINE_URL}/{name}"
|
|
722
|
+
r = self._http.get(url, headers=self._get_headers(), timeout=60)
|
|
723
|
+
|
|
724
|
+
if r.status_code == 404:
|
|
725
|
+
return None
|
|
726
|
+
|
|
727
|
+
r.raise_for_status()
|
|
728
|
+
|
|
729
|
+
return r.json().get("name", name)
|
|
730
|
+
|
|
731
|
+
def find_agent_space_agents_by_display(self, display_name: str) -> Optional[dict]:
|
|
732
|
+
base = (
|
|
733
|
+
f"{DISCOVERY_ENGINE_URL}/projects/{self._project_id}/locations/global/"
|
|
734
|
+
f"collections/default_collection/engines/{self._agent_space_engine}/"
|
|
735
|
+
f"assistants/default_assistant/agents"
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
headers = self._get_headers()
|
|
739
|
+
page = None
|
|
740
|
+
matches =[]
|
|
741
|
+
|
|
742
|
+
while True:
|
|
743
|
+
url = base + (f"?pageToken={page}" if page else "")
|
|
744
|
+
r = self._http.get(url, headers=headers, timeout=60)
|
|
745
|
+
r.raise_for_status()
|
|
746
|
+
data = r.json()
|
|
747
|
+
|
|
748
|
+
for a in data.get("agents", []):
|
|
749
|
+
if a.get("displayName") != display_name:
|
|
750
|
+
continue
|
|
751
|
+
full = a.get("name", "")
|
|
752
|
+
matches.append({
|
|
753
|
+
"id": full.split("/")[-1] if full else "",
|
|
754
|
+
"display_name": a.get("displayName", ""),
|
|
755
|
+
"full_name": full,
|
|
756
|
+
"labels": a.get("labels", {}),
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
page = data.get("nextPageToken")
|
|
760
|
+
if not page:
|
|
761
|
+
break
|
|
762
|
+
|
|
763
|
+
if not matches:
|
|
764
|
+
return None
|
|
765
|
+
if len(matches) > 1:
|
|
766
|
+
raise RuntimeError(
|
|
767
|
+
f"Found {len(matches)} agents with displayName='{display_name}'. "
|
|
768
|
+
"Provide an ID or labels (e.g., env/app/agent) to disambiguate."
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
return matches[0]
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def one_github_deployment_to_go(self):
|
|
775
|
+
"""
|
|
776
|
+
CI-friendly deploy:
|
|
777
|
+
- Engine: create or update by display_name.
|
|
778
|
+
- Authorization: create if missing; patch if scopes changed.
|
|
779
|
+
- Agent Space: create if missing; patch if found (by displayName under engine).
|
|
780
|
+
"""
|
|
781
|
+
self.info("Starting GitHub deployment...")
|
|
782
|
+
|
|
783
|
+
# Ensure Vertex SDK calls have context for list/update
|
|
784
|
+
vertexai.init(
|
|
785
|
+
project=self._project_id,
|
|
786
|
+
location=self._project_location,
|
|
787
|
+
staging_bucket=self._staging_bucket,
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
# -----------------------------
|
|
791
|
+
# 1) Reasoning Engine (create or update)
|
|
792
|
+
# -----------------------------
|
|
793
|
+
engine_rn = self.find_engine_by_name(self._reasoning_engine_name)
|
|
794
|
+
if not engine_rn:
|
|
795
|
+
self.info(f"Engine '{self._reasoning_engine_name}' not found. Creating...")
|
|
796
|
+
self.create_reasoning_engine()
|
|
797
|
+
# read back the created id (or re-resolve by name as fallback)
|
|
798
|
+
engine_rn = self._read_engine_deployment_record().get("reasoning_engine_id") or \
|
|
799
|
+
self.find_engine_by_name(self._reasoning_engine_name)
|
|
800
|
+
if not engine_rn:
|
|
801
|
+
self.error("Engine creation did not yield a resource name.")
|
|
802
|
+
raise RuntimeError("Engine creation failed.")
|
|
803
|
+
else:
|
|
804
|
+
self.info(f"Engine '{self._reasoning_engine_name}' exists. Updating...")
|
|
805
|
+
self.update_reasoning_engine(engine_rn)
|
|
806
|
+
|
|
807
|
+
# -----------------------------
|
|
808
|
+
# 2) Authorization (create if missing; update scopes if changed)
|
|
809
|
+
# -----------------------------
|
|
810
|
+
if self._authorization_id:
|
|
811
|
+
want_scopes = set(self._required_scopes or [])
|
|
812
|
+
auth_full_name = self.find_authorization_by_id(self._authorization_id)
|
|
813
|
+
|
|
814
|
+
if not auth_full_name:
|
|
815
|
+
self.info(f"Authorization '{self._authorization_id}' not found. Creating...")
|
|
816
|
+
ok = self._create_authorization()
|
|
817
|
+
if not ok:
|
|
818
|
+
self.error("Authorization creation failed.")
|
|
819
|
+
raise RuntimeError("Authorization creation failed.")
|
|
820
|
+
auth_full_name = self.find_authorization_by_id(self._authorization_id)
|
|
821
|
+
if not auth_full_name:
|
|
822
|
+
self.error("Authorization creation did not resolve to a resource.")
|
|
823
|
+
raise RuntimeError("Authorization creation failed to resolve.")
|
|
824
|
+
else:
|
|
825
|
+
# Compare scopes; patch if different
|
|
826
|
+
r = self._http.get(f"{DISCOVERY_ENGINE_URL}/{auth_full_name}",
|
|
827
|
+
headers=self._get_headers(), timeout=60)
|
|
828
|
+
r.raise_for_status()
|
|
829
|
+
data = r.json() or {}
|
|
830
|
+
existing_uri = (((data.get("serverSideOauth2") or {}).get("authorizationUri")) or "")
|
|
831
|
+
existing_scopes = set()
|
|
832
|
+
if existing_uri:
|
|
833
|
+
parsed = urlparse(existing_uri)
|
|
834
|
+
qs = parse_qs(parsed.query)
|
|
835
|
+
scope_str = (qs.get("scope", [""])[0] or "")
|
|
836
|
+
existing_scopes = set(scope_str.split())
|
|
837
|
+
|
|
838
|
+
if existing_scopes != want_scopes:
|
|
839
|
+
self.info("Authorization scopes changed. Patching authorization...")
|
|
840
|
+
new_auth_uri = self._build_authorization_uri(self._oauth_client_id, list(want_scopes))
|
|
841
|
+
patch_payload = {
|
|
842
|
+
"serverSideOauth2": {
|
|
843
|
+
"clientId": self._oauth_client_id,
|
|
844
|
+
"clientSecret": self._oauth_client_secret,
|
|
845
|
+
"authorizationUri": new_auth_uri,
|
|
846
|
+
"tokenUri": "https://oauth2.googleapis.com/token",
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
pr = self._http.patch(f"{DISCOVERY_ENGINE_URL}/{auth_full_name}",
|
|
850
|
+
headers=self._get_headers(), json=patch_payload, timeout=60)
|
|
851
|
+
pr.raise_for_status()
|
|
852
|
+
self.info("Authorization updated.")
|
|
853
|
+
else:
|
|
854
|
+
self.info("Authorization scopes unchanged; no update needed.")
|
|
855
|
+
else:
|
|
856
|
+
self.info("No authorization_id configured; skipping authorization step.")
|
|
857
|
+
|
|
858
|
+
# -----------------------------
|
|
859
|
+
# 3) Agent Space Agent (create or update by displayName under engine)
|
|
860
|
+
# -----------------------------
|
|
861
|
+
existing_agent = self.find_agent_space_agents_by_display(self._agent_space_name)
|
|
862
|
+
headers, payload = self._get_agent_space_payload(engine_rn)
|
|
863
|
+
|
|
864
|
+
if not existing_agent:
|
|
865
|
+
self.info(f"Agent Space agent '{self._agent_space_name}' not found. Creating...")
|
|
866
|
+
create_url = self._get_agent_space_agent_url_new()
|
|
867
|
+
cr = self._http.post(create_url, headers=headers, json=payload, timeout=90)
|
|
868
|
+
if cr.status_code >= 400:
|
|
869
|
+
# File-only details already handled elsewhere; surface concise error here
|
|
870
|
+
self.error(f"Agent creation failed [{cr.status_code}].")
|
|
871
|
+
cr.raise_for_status()
|
|
872
|
+
agent_name = (cr.json() or {}).get("name")
|
|
873
|
+
if agent_name:
|
|
874
|
+
self._write_engine_deployment({"agent_space_id": agent_name})
|
|
875
|
+
self.info(f"Agent Space agent created: {agent_name}")
|
|
876
|
+
else:
|
|
877
|
+
self.warning("Agent created but response missing name. Verify in console.")
|
|
878
|
+
else:
|
|
879
|
+
self.info(f"Agent Space agent '{self._agent_space_name}' exists. Updating...")
|
|
880
|
+
patch_url = f"{DISCOVERY_ENGINE_URL}/{existing_agent['full_name']}"
|
|
881
|
+
ur = self._http.patch(patch_url, headers=headers, json=payload, timeout=90)
|
|
882
|
+
if ur.status_code >= 400:
|
|
883
|
+
self.error(f"Agent update failed [{ur.status_code}].")
|
|
884
|
+
ur.raise_for_status()
|
|
885
|
+
self.info("Agent Space agent updated.")
|
|
886
|
+
|
|
887
|
+
self.info("GitHub deployment completed successfully.")
|
|
@@ -29,8 +29,8 @@ class Runner:
|
|
|
29
29
|
root_agent = Runner._load_agent(args.agent_path)
|
|
30
30
|
|
|
31
31
|
# --- Require config files ---
|
|
32
|
-
if not Path(".env.agent").exists() or not Path("
|
|
33
|
-
print("Missing .env.agent or
|
|
32
|
+
if not Path(".env.agent").exists() or not Path("agent.yaml").exists():
|
|
33
|
+
print("Missing .env.agent or agent.yaml.")
|
|
34
34
|
print("Options:\n 5) Generate placeholder config\n q) Quit")
|
|
35
35
|
choice = input("Enter choice: ").strip()
|
|
36
36
|
if choice == "5":
|
|
@@ -132,6 +132,8 @@ class Runner:
|
|
|
132
132
|
GUIEditor().run()
|
|
133
133
|
elif mode == "populate_files":
|
|
134
134
|
svc._check_required_files_exist()
|
|
135
|
+
elif mode == "github_deployment":
|
|
136
|
+
svc.one_github_deployment_to_go()
|
|
135
137
|
|
|
136
138
|
@staticmethod
|
|
137
139
|
def _menu(root_agent):
|