glaip-sdk 0.0.5b1__py3-none-any.whl → 0.0.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.
Files changed (43) hide show
  1. glaip_sdk/__init__.py +1 -1
  2. glaip_sdk/_version.py +42 -19
  3. glaip_sdk/branding.py +3 -2
  4. glaip_sdk/cli/commands/__init__.py +1 -1
  5. glaip_sdk/cli/commands/agents.py +452 -285
  6. glaip_sdk/cli/commands/configure.py +14 -13
  7. glaip_sdk/cli/commands/mcps.py +30 -20
  8. glaip_sdk/cli/commands/models.py +5 -3
  9. glaip_sdk/cli/commands/tools.py +111 -106
  10. glaip_sdk/cli/display.py +48 -27
  11. glaip_sdk/cli/io.py +1 -1
  12. glaip_sdk/cli/main.py +26 -5
  13. glaip_sdk/cli/resolution.py +5 -4
  14. glaip_sdk/cli/utils.py +437 -188
  15. glaip_sdk/cli/validators.py +7 -2
  16. glaip_sdk/client/agents.py +276 -153
  17. glaip_sdk/client/base.py +69 -27
  18. glaip_sdk/client/tools.py +44 -26
  19. glaip_sdk/client/validators.py +154 -94
  20. glaip_sdk/config/constants.py +0 -2
  21. glaip_sdk/models.py +5 -4
  22. glaip_sdk/utils/__init__.py +7 -7
  23. glaip_sdk/utils/client_utils.py +191 -101
  24. glaip_sdk/utils/display.py +4 -2
  25. glaip_sdk/utils/general.py +8 -6
  26. glaip_sdk/utils/import_export.py +58 -25
  27. glaip_sdk/utils/rendering/formatting.py +12 -6
  28. glaip_sdk/utils/rendering/models.py +1 -1
  29. glaip_sdk/utils/rendering/renderer/base.py +523 -332
  30. glaip_sdk/utils/rendering/renderer/console.py +6 -5
  31. glaip_sdk/utils/rendering/renderer/debug.py +94 -52
  32. glaip_sdk/utils/rendering/renderer/stream.py +93 -48
  33. glaip_sdk/utils/rendering/steps.py +103 -39
  34. glaip_sdk/utils/rich_utils.py +1 -1
  35. glaip_sdk/utils/run_renderer.py +1 -1
  36. glaip_sdk/utils/serialization.py +9 -3
  37. glaip_sdk/utils/validation.py +2 -2
  38. glaip_sdk-0.0.7.dist-info/METADATA +183 -0
  39. glaip_sdk-0.0.7.dist-info/RECORD +55 -0
  40. glaip_sdk-0.0.5b1.dist-info/METADATA +0 -645
  41. glaip_sdk-0.0.5b1.dist-info/RECORD +0 -55
  42. {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/WHEEL +0 -0
  43. {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/base.py CHANGED
@@ -7,13 +7,14 @@ Authors:
7
7
 
8
8
  import logging
9
9
  import os
10
- from typing import Any, Union
10
+ from typing import Any, NoReturn, Union
11
11
 
12
12
  import httpx
13
13
  from dotenv import load_dotenv
14
14
 
15
15
  import glaip_sdk
16
- from glaip_sdk.config.constants import SDK_NAME, SDK_VERSION
16
+ from glaip_sdk._version import __version__ as SDK_VERSION
17
+ from glaip_sdk.config.constants import SDK_NAME
17
18
  from glaip_sdk.exceptions import (
18
19
  AuthenticationError,
19
20
  ConflictError,
@@ -147,7 +148,7 @@ class BaseClient:
147
148
  return self._timeout
148
149
 
149
150
  @timeout.setter
150
- def timeout(self, value: float):
151
+ def timeout(self, value: float) -> None:
151
152
  """Set timeout and rebuild client."""
152
153
  self._timeout = value
153
154
  if (
@@ -165,10 +166,10 @@ class BaseClient:
165
166
  post_endpoint: str,
166
167
  get_endpoint_fmt: str,
167
168
  *,
168
- json=None,
169
- data=None,
170
- files=None,
171
- **kwargs,
169
+ json: Any | None = None,
170
+ data: Any | None = None,
171
+ files: Any | None = None,
172
+ **kwargs: Any,
172
173
  ) -> Any:
173
174
  """Helper for POST-then-GET pattern used in create methods.
174
175
 
@@ -210,7 +211,7 @@ class BaseClient:
210
211
  get_endpoint = get_endpoint_fmt.format(id=resource_id)
211
212
  return self._request("GET", get_endpoint)
212
213
 
213
- def _ensure_client_alive(self):
214
+ def _ensure_client_alive(self) -> None:
214
215
  """Ensure HTTP client is alive, recreate if needed."""
215
216
  if not hasattr(self, "http_client") or self.http_client is None:
216
217
  if not self._parent_client:
@@ -253,25 +254,25 @@ class BaseClient:
253
254
  client_log.error(f"Retry failed for {method} {endpoint}: {e}")
254
255
  raise e
255
256
 
256
- def _handle_response(self, response: httpx.Response) -> Any:
257
- """Handle HTTP response with proper error handling."""
257
+ def _parse_response_content(self, response: httpx.Response) -> Any | None:
258
+ """Parse response content based on content type."""
258
259
  if response.status_code == 204:
259
260
  return None
260
261
 
261
- parsed = None
262
262
  content_type = response.headers.get("content-type", "").lower()
263
263
  if "json" in content_type:
264
264
  try:
265
- parsed = response.json()
265
+ return response.json()
266
266
  except ValueError:
267
267
  pass
268
268
 
269
- if parsed is None:
270
- if 200 <= response.status_code < 300:
271
- return response.text
272
- else:
273
- self._raise_api_error(response.status_code, response.text)
269
+ if 200 <= response.status_code < 300:
270
+ return response.text
271
+ else:
272
+ return None # Let _handle_response deal with error status codes
274
273
 
274
+ def _handle_success_response(self, parsed: Any) -> Any:
275
+ """Handle successful response with success flag."""
275
276
  if isinstance(parsed, dict) and "success" in parsed:
276
277
  if parsed.get("success"):
277
278
  return parsed.get("data", parsed)
@@ -279,18 +280,54 @@ class BaseClient:
279
280
  error_type = parsed.get("error", "UnknownError")
280
281
  message = parsed.get("message", "Unknown error")
281
282
  self._raise_api_error(
282
- response.status_code, message, error_type, payload=parsed
283
+ 400,
284
+ message,
285
+ error_type,
286
+ payload=parsed, # Using 400 as status since original response had error
283
287
  )
284
288
 
285
- if 200 <= response.status_code < 300:
286
- return parsed
289
+ return parsed
290
+
291
+ def _get_error_message(self, response: httpx.Response) -> str:
292
+ """Extract error message from response, preferring parsed content."""
293
+ # Try to get error message from parsed content if available
294
+ error_message = response.text
295
+ try:
296
+ parsed = response.json()
297
+ if isinstance(parsed, dict) and "message" in parsed:
298
+ error_message = parsed["message"]
299
+ elif isinstance(parsed, str):
300
+ error_message = parsed
301
+ except (ValueError, TypeError):
302
+ pass # Use response.text as fallback
303
+ return error_message
304
+
305
+ def _handle_response(self, response: httpx.Response) -> Any:
306
+ """Handle HTTP response with proper error handling."""
307
+ # Handle no-content success before general error handling
308
+ if response.status_code == 204:
309
+ return None
287
310
 
288
- message = parsed.get("message") if isinstance(parsed, dict) else str(parsed)
289
- self._raise_api_error(response.status_code, message, payload=parsed)
311
+ # Handle error status codes
312
+ if not (200 <= response.status_code < 300):
313
+ error_message = self._get_error_message(response)
314
+ self._raise_api_error(response.status_code, error_message)
315
+ return None # Won't be reached but helps with type checking
316
+
317
+ parsed = self._parse_response_content(response)
318
+ if parsed is None:
319
+ return None
320
+
321
+ return self._handle_success_response(parsed)
290
322
 
291
323
  def _raise_api_error(
292
- self, status: int, message: str, error_type: str | None = None, *, payload=None
293
- ):
324
+ self,
325
+ status: int,
326
+ message: str,
327
+ error_type: str | None = None,
328
+ *,
329
+ payload: Any | None = None,
330
+ ) -> NoReturn:
294
331
  """Raise appropriate exception with rich context."""
295
332
  request_id = None
296
333
  try:
@@ -324,7 +361,7 @@ class BaseClient:
324
361
  request_id=request_id,
325
362
  )
326
363
 
327
- def close(self):
364
+ def close(self) -> None:
328
365
  """Close the HTTP client."""
329
366
  if (
330
367
  hasattr(self, "http_client")
@@ -334,11 +371,16 @@ class BaseClient:
334
371
  ):
335
372
  self.http_client.close()
336
373
 
337
- def __enter__(self):
374
+ def __enter__(self) -> "BaseClient":
338
375
  """Context manager entry."""
339
376
  return self
340
377
 
341
- def __exit__(self, _exc_type, _exc_val, _exc_tb):
378
+ def __exit__(
379
+ self,
380
+ _exc_type: type[BaseException] | None,
381
+ _exc_val: BaseException | None,
382
+ _exc_tb: Any,
383
+ ) -> None:
342
384
  """Context manager exit."""
343
385
  # Only close if this is not session-scoped
344
386
  if not self._session_scoped:
glaip_sdk/client/tools.py CHANGED
@@ -208,6 +208,44 @@ class ToolClient(BaseClient):
208
208
 
209
209
  return payload
210
210
 
211
+ def _handle_description_update(
212
+ self, update_data: dict[str, Any], description: str | None, current_tool: Tool
213
+ ) -> None:
214
+ """Handle description field in update payload."""
215
+ if description is not None:
216
+ update_data["description"] = description.strip()
217
+ elif hasattr(current_tool, "description") and current_tool.description:
218
+ update_data["description"] = current_tool.description
219
+
220
+ def _handle_tags_update(
221
+ self, update_data: dict[str, Any], kwargs: dict[str, Any], current_tool: Tool
222
+ ) -> None:
223
+ """Handle tags field in update payload."""
224
+ if kwargs.get("tags"):
225
+ if isinstance(kwargs["tags"], list):
226
+ update_data["tags"] = ",".join(
227
+ str(tag).strip() for tag in kwargs["tags"]
228
+ )
229
+ else:
230
+ update_data["tags"] = str(kwargs["tags"])
231
+ elif hasattr(current_tool, "tags") and current_tool.tags:
232
+ # Preserve existing tags if present
233
+ if isinstance(current_tool.tags, list):
234
+ update_data["tags"] = ",".join(
235
+ str(tag).strip() for tag in current_tool.tags
236
+ )
237
+ else:
238
+ update_data["tags"] = str(current_tool.tags)
239
+
240
+ def _handle_additional_kwargs(
241
+ self, update_data: dict[str, Any], kwargs: dict[str, Any]
242
+ ) -> None:
243
+ """Handle additional kwargs in update payload."""
244
+ excluded_keys = {"tags", "framework", "version"}
245
+ for key, value in kwargs.items():
246
+ if key not in excluded_keys:
247
+ update_data[key] = value
248
+
211
249
  def _build_update_payload(
212
250
  self,
213
251
  current_tool: Tool,
@@ -242,34 +280,14 @@ class ToolClient(BaseClient):
242
280
  ),
243
281
  }
244
282
 
245
- # Handle description with proper None handling
246
- if description is not None:
247
- update_data["description"] = description.strip()
248
- elif hasattr(current_tool, "description") and current_tool.description:
249
- update_data["description"] = current_tool.description
283
+ # Handle description update
284
+ self._handle_description_update(update_data, description, current_tool)
250
285
 
251
- # Handle tags - convert list to comma-separated string for API
252
- if kwargs.get("tags"):
253
- if isinstance(kwargs["tags"], list):
254
- update_data["tags"] = ",".join(
255
- str(tag).strip() for tag in kwargs["tags"]
256
- )
257
- else:
258
- update_data["tags"] = str(kwargs["tags"])
259
- elif hasattr(current_tool, "tags") and current_tool.tags:
260
- # Preserve existing tags if present
261
- if isinstance(current_tool.tags, list):
262
- update_data["tags"] = ",".join(
263
- str(tag).strip() for tag in current_tool.tags
264
- )
265
- else:
266
- update_data["tags"] = str(current_tool.tags)
286
+ # Handle tags update
287
+ self._handle_tags_update(update_data, kwargs, current_tool)
267
288
 
268
- # Add any other kwargs (excluding already handled ones)
269
- excluded_keys = {"tags", "framework", "version"}
270
- for key, value in kwargs.items():
271
- if key not in excluded_keys:
272
- update_data[key] = value
289
+ # Handle additional kwargs
290
+ self._handle_additional_kwargs(update_data, kwargs)
273
291
 
274
292
  return update_data
275
293
 
@@ -25,8 +25,67 @@ class ResourceValidator:
25
25
  """Check if a name is reserved."""
26
26
  return name in cls.RESERVED_NAMES
27
27
 
28
+ def _is_uuid_string(self, value: str) -> bool:
29
+ """Check if a string is a valid UUID."""
30
+ try:
31
+ UUID(value)
32
+ return True
33
+ except ValueError:
34
+ return False
35
+
36
+ def _resolve_tool_by_name(self, tool_name: str, client: Any) -> str:
37
+ """Resolve tool name to ID."""
38
+ found_tools = client.find_tools(name=tool_name)
39
+ if len(found_tools) == 1:
40
+ return str(found_tools[0].id)
41
+ elif len(found_tools) > 1:
42
+ raise AmbiguousResourceError(
43
+ f"Multiple tools found with name '{tool_name}': {[t.id for t in found_tools]}"
44
+ )
45
+ else:
46
+ raise NotFoundError(f"Tool not found: {tool_name}")
47
+
48
+ def _resolve_tool_by_name_attribute(self, tool: Tool, client: Any) -> str:
49
+ """Resolve tool by name attribute."""
50
+ found_tools = client.find_tools(name=tool.name)
51
+ if len(found_tools) == 1:
52
+ return str(found_tools[0].id)
53
+ elif len(found_tools) > 1:
54
+ raise AmbiguousResourceError(
55
+ f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}"
56
+ )
57
+ else:
58
+ raise NotFoundError(f"Tool not found: {tool.name}")
59
+
60
+ def _process_tool_string(self, tool: str, client: Any) -> str:
61
+ """Process a string tool reference."""
62
+ if self._is_uuid_string(tool):
63
+ return tool # Already a UUID string
64
+ else:
65
+ return self._resolve_tool_by_name(tool, client)
66
+
67
+ def _process_tool_object(self, tool: Tool, client: Any) -> str:
68
+ """Process a Tool object reference."""
69
+ if hasattr(tool, "id") and tool.id is not None:
70
+ return str(tool.id)
71
+ elif isinstance(tool, UUID):
72
+ return str(tool)
73
+ elif hasattr(tool, "name") and tool.name is not None:
74
+ return self._resolve_tool_by_name_attribute(tool, client)
75
+ else:
76
+ raise ValidationError(
77
+ f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute"
78
+ )
79
+
80
+ def _process_single_tool(self, tool: str | Tool, client: Any) -> str:
81
+ """Process a single tool reference and return its ID."""
82
+ if isinstance(tool, str):
83
+ return self._process_tool_string(tool, client)
84
+ else:
85
+ return self._process_tool_object(tool, client)
86
+
28
87
  @classmethod
29
- def extract_tool_ids(cls, tools: list[str | Tool], client) -> list[str]:
88
+ def extract_tool_ids(cls, tools: list[str | Tool], client: Any) -> list[str]:
30
89
  """Extract tool IDs from a list of tool names, IDs, or Tool objects.
31
90
 
32
91
  For agent creation, the backend expects tool IDs (UUIDs).
@@ -37,57 +96,81 @@ class ResourceValidator:
37
96
  """
38
97
  tool_ids = []
39
98
  for tool in tools:
40
- if isinstance(tool, str):
41
- # Check if it's already a UUID
42
- try:
43
- UUID(tool)
44
- tool_ids.append(tool) # Already a UUID string
45
- except ValueError:
46
- # It's a name, try to find the tool and get its ID
47
- try:
48
- found_tools = client.find_tools(name=tool)
49
- if len(found_tools) == 1:
50
- tool_ids.append(str(found_tools[0].id))
51
- elif len(found_tools) > 1:
52
- raise AmbiguousResourceError(
53
- f"Multiple tools found with name '{tool}': {[t.id for t in found_tools]}"
54
- )
55
- else:
56
- raise NotFoundError(f"Tool not found: {tool}")
57
- except Exception as e:
58
- raise ValidationError(
59
- f"Failed to resolve tool name '{tool}' to ID: {e}"
60
- )
61
- elif hasattr(tool, "id") and tool.id is not None: # Tool object with ID
62
- tool_ids.append(str(tool.id))
63
- elif isinstance(tool, UUID): # UUID object
64
- tool_ids.append(str(tool))
65
- elif (
66
- hasattr(tool, "name") and tool.name is not None
67
- ): # Tool object with name but no ID
68
- # Try to find the tool by name and get its ID
69
- try:
70
- found_tools = client.find_tools(name=tool.name)
71
- if len(found_tools) == 1:
72
- tool_ids.append(str(found_tools[0].id))
73
- elif len(found_tools) > 1:
74
- raise AmbiguousResourceError(
75
- f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}"
76
- )
77
- else:
78
- raise NotFoundError(f"Tool not found: {tool.name}")
79
- except Exception as e:
80
- raise ValidationError(
81
- f"Failed to resolve tool name '{tool.name}' to ID: {e}"
82
- )
83
- else:
99
+ try:
100
+ tool_id = cls()._process_single_tool(tool, client)
101
+ tool_ids.append(tool_id)
102
+ except (AmbiguousResourceError, NotFoundError) as e:
103
+ # Determine the tool name for the error message
104
+ tool_name = (
105
+ tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
106
+ )
107
+ raise ValidationError(
108
+ f"Failed to resolve tool name '{tool_name}' to ID: {e}"
109
+ )
110
+ except Exception as e:
111
+ # For other exceptions, wrap them appropriately
112
+ tool_name = (
113
+ tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
114
+ )
84
115
  raise ValidationError(
85
- f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute"
116
+ f"Failed to resolve tool name '{tool_name}' to ID: {e}"
86
117
  )
118
+
87
119
  return tool_ids
88
120
 
121
+ def _resolve_agent_by_name(self, agent_name: str, client: Any) -> str:
122
+ """Resolve agent name to ID."""
123
+ found_agents = client.find_agents(name=agent_name)
124
+ if len(found_agents) == 1:
125
+ return str(found_agents[0].id)
126
+ elif len(found_agents) > 1:
127
+ raise AmbiguousResourceError(
128
+ f"Multiple agents found with name '{agent_name}': {[a.id for a in found_agents]}"
129
+ )
130
+ else:
131
+ raise NotFoundError(f"Agent not found: {agent_name}")
132
+
133
+ def _resolve_agent_by_name_attribute(self, agent: Any, client: Any) -> str:
134
+ """Resolve agent by name attribute."""
135
+ found_agents = client.find_agents(name=agent.name)
136
+ if len(found_agents) == 1:
137
+ return str(found_agents[0].id)
138
+ elif len(found_agents) > 1:
139
+ raise AmbiguousResourceError(
140
+ f"Multiple agents found with name '{agent.name}': {[a.id for a in found_agents]}"
141
+ )
142
+ else:
143
+ raise NotFoundError(f"Agent not found: {agent.name}")
144
+
145
+ def _process_agent_string(self, agent: str, client: Any) -> str:
146
+ """Process a string agent reference."""
147
+ if self._is_uuid_string(agent):
148
+ return agent # Already a UUID string
149
+ else:
150
+ return self._resolve_agent_by_name(agent, client)
151
+
152
+ def _process_agent_object(self, agent: Any, client: Any) -> str:
153
+ """Process an Agent object reference."""
154
+ if hasattr(agent, "id") and agent.id is not None:
155
+ return str(agent.id)
156
+ elif isinstance(agent, UUID):
157
+ return str(agent)
158
+ elif hasattr(agent, "name") and agent.name is not None:
159
+ return self._resolve_agent_by_name_attribute(agent, client)
160
+ else:
161
+ raise ValidationError(
162
+ f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute"
163
+ )
164
+
165
+ def _process_single_agent(self, agent: str | Any, client: Any) -> str:
166
+ """Process a single agent reference and return its ID."""
167
+ if isinstance(agent, str):
168
+ return self._process_agent_string(agent, client)
169
+ else:
170
+ return self._process_agent_object(agent, client)
171
+
89
172
  @classmethod
90
- def extract_agent_ids(cls, agents: list[str | Any], client) -> list[str]:
173
+ def extract_agent_ids(cls, agents: list[str | Any], client: Any) -> list[str]:
91
174
  """Extract agent IDs from a list of agent names, IDs, or agent objects.
92
175
 
93
176
  For agent creation, the backend expects agent IDs (UUIDs).
@@ -98,57 +181,34 @@ class ResourceValidator:
98
181
  """
99
182
  agent_ids = []
100
183
  for agent in agents:
101
- if isinstance(agent, str):
102
- # Check if it's already a UUID
103
- try:
104
- UUID(agent)
105
- agent_ids.append(agent) # Already a UUID string
106
- except ValueError:
107
- # It's a name, try to find the agent and get its ID
108
- try:
109
- found_agents = client.find_agents(name=agent)
110
- if len(found_agents) == 1:
111
- agent_ids.append(str(found_agents[0].id))
112
- elif len(found_agents) > 1:
113
- raise AmbiguousResourceError(
114
- f"Multiple agents found with name '{agent}': {[a.id for a in found_agents]}"
115
- )
116
- else:
117
- raise NotFoundError(f"Agent not found: {agent}")
118
- except Exception as e:
119
- raise ValidationError(
120
- f"Failed to resolve agent name '{agent}' to ID: {e}"
121
- )
122
- elif hasattr(agent, "id") and agent.id is not None: # Agent object with ID
123
- agent_ids.append(str(agent.id))
124
- elif isinstance(agent, UUID): # UUID object
125
- agent_ids.append(str(agent))
126
- elif (
127
- hasattr(agent, "name") and agent.name is not None
128
- ): # Agent object with name but no ID
129
- # Try to find the agent by name and get its ID
130
- try:
131
- found_agents = client.find_agents(name=agent.name)
132
- if len(found_agents) == 1:
133
- agent_ids.append(str(found_agents[0].id))
134
- elif len(found_agents) > 1:
135
- raise AmbiguousResourceError(
136
- f"Multiple agents found with name '{agent.name}': {[a.id for a in found_agents]}"
137
- )
138
- else:
139
- raise NotFoundError(f"Agent not found: {agent.name}")
140
- except Exception as e:
141
- raise ValidationError(
142
- f"Failed to resolve agent name '{agent.name}' to ID: {e}"
143
- )
144
- else:
184
+ try:
185
+ agent_id = cls()._process_single_agent(agent, client)
186
+ agent_ids.append(agent_id)
187
+ except (AmbiguousResourceError, NotFoundError) as e:
188
+ # Determine the agent name for the error message
189
+ agent_name = (
190
+ agent
191
+ if isinstance(agent, str)
192
+ else getattr(agent, "name", str(agent))
193
+ )
145
194
  raise ValidationError(
146
- f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute"
195
+ f"Failed to resolve agent name '{agent_name}' to ID: {e}"
196
+ )
197
+ except Exception as e:
198
+ # For other exceptions, wrap them appropriately
199
+ agent_name = (
200
+ agent
201
+ if isinstance(agent, str)
202
+ else getattr(agent, "name", str(agent))
147
203
  )
204
+ raise ValidationError(
205
+ f"Failed to resolve agent name '{agent_name}' to ID: {e}"
206
+ )
207
+
148
208
  return agent_ids
149
209
 
150
210
  @classmethod
151
- def validate_tools_exist(cls, tool_ids: list[str], client) -> None:
211
+ def validate_tools_exist(cls, tool_ids: list[str], client: Any) -> None:
152
212
  """Validate that all tool IDs exist."""
153
213
  for tool_id in tool_ids:
154
214
  try:
@@ -157,7 +217,7 @@ class ResourceValidator:
157
217
  raise ValidationError(f"Tool not found: {tool_id}")
158
218
 
159
219
  @classmethod
160
- def validate_agents_exist(cls, agent_ids: list[str], client) -> None:
220
+ def validate_agents_exist(cls, agent_ids: list[str], client: Any) -> None:
161
221
  """Validate that all agent IDs exist."""
162
222
  for agent_id in agent_ids:
163
223
  try:
@@ -13,9 +13,7 @@ DEFAULT_TIMEOUT = 30.0
13
13
  DEFAULT_AGENT_RUN_TIMEOUT = 300
14
14
 
15
15
  # User agent and version
16
-
17
16
  SDK_NAME = "glaip-sdk"
18
- SDK_VERSION = "0.1.0"
19
17
 
20
18
  # Reserved names that cannot be used for agents/tools
21
19
  RESERVED_NAMES = {
glaip_sdk/models.py CHANGED
@@ -39,7 +39,7 @@ class Agent(BaseModel):
39
39
  updated_at: datetime | None = None # Backend returns last update timestamp
40
40
  _client: Any = None
41
41
 
42
- def _set_client(self, client):
42
+ def _set_client(self, client: Any) -> "Agent":
43
43
  """Set the client reference for this resource."""
44
44
  self._client = client
45
45
  return self
@@ -119,9 +119,10 @@ class Tool(BaseModel):
119
119
  version: str | None = None
120
120
  tool_script: str | None = None
121
121
  tool_file: str | None = None
122
+ tags: str | list[str] | None = None
122
123
  _client: Any = None # Will hold client reference
123
124
 
124
- def _set_client(self, client):
125
+ def _set_client(self, client: Any) -> "Tool":
125
126
  """Set the client reference for this resource."""
126
127
  self._client = client
127
128
  return self
@@ -185,7 +186,7 @@ class MCP(BaseModel):
185
186
  metadata: dict[str, Any] | None = None
186
187
  _client: Any = None # Will hold client reference
187
188
 
188
- def _set_client(self, client):
189
+ def _set_client(self, client: Any) -> "MCP":
189
190
  """Set the client reference for this resource."""
190
191
  self._client = client
191
192
  return self
@@ -239,7 +240,7 @@ class TTYRenderer:
239
240
  def __init__(self, use_color: bool = True):
240
241
  self.use_color = use_color
241
242
 
242
- def render_message(self, message: str, event_type: str = "message"):
243
+ def render_message(self, message: str, event_type: str = "message") -> None:
243
244
  """Render a message with optional color."""
244
245
  if event_type == "error":
245
246
  print(f"ERROR: {message}", flush=True)
@@ -23,18 +23,18 @@ from glaip_sdk.utils.rich_utils import RICH_AVAILABLE
23
23
  from glaip_sdk.utils.run_renderer import RichStreamRenderer
24
24
 
25
25
  __all__ = [
26
+ "RICH_AVAILABLE",
26
27
  "RichStreamRenderer",
27
28
  "RunStats",
28
29
  "Step",
29
30
  "StepManager",
30
- "RICH_AVAILABLE",
31
- "is_uuid",
32
- "sanitize_name",
33
- "format_file_size",
34
31
  "format_datetime",
35
- "progress_bar",
36
- "print_agent_output",
32
+ "format_file_size",
33
+ "is_uuid",
37
34
  "print_agent_created",
38
- "print_agent_updated",
39
35
  "print_agent_deleted",
36
+ "print_agent_output",
37
+ "print_agent_updated",
38
+ "progress_bar",
39
+ "sanitize_name",
40
40
  ]