glaip-sdk 0.0.2__py3-none-any.whl → 0.0.3__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 (39) hide show
  1. glaip_sdk/__init__.py +2 -2
  2. glaip_sdk/_version.py +51 -0
  3. glaip_sdk/cli/commands/agents.py +201 -109
  4. glaip_sdk/cli/commands/configure.py +29 -87
  5. glaip_sdk/cli/commands/init.py +16 -7
  6. glaip_sdk/cli/commands/mcps.py +73 -153
  7. glaip_sdk/cli/commands/tools.py +185 -49
  8. glaip_sdk/cli/main.py +30 -27
  9. glaip_sdk/cli/utils.py +126 -13
  10. glaip_sdk/client/__init__.py +54 -2
  11. glaip_sdk/client/agents.py +175 -237
  12. glaip_sdk/client/base.py +62 -2
  13. glaip_sdk/client/mcps.py +63 -20
  14. glaip_sdk/client/tools.py +95 -28
  15. glaip_sdk/config/constants.py +10 -3
  16. glaip_sdk/exceptions.py +13 -0
  17. glaip_sdk/models.py +20 -4
  18. glaip_sdk/utils/__init__.py +116 -18
  19. glaip_sdk/utils/client_utils.py +284 -0
  20. glaip_sdk/utils/rendering/__init__.py +1 -0
  21. glaip_sdk/utils/rendering/formatting.py +211 -0
  22. glaip_sdk/utils/rendering/models.py +53 -0
  23. glaip_sdk/utils/rendering/renderer/__init__.py +38 -0
  24. glaip_sdk/utils/rendering/renderer/base.py +827 -0
  25. glaip_sdk/utils/rendering/renderer/config.py +33 -0
  26. glaip_sdk/utils/rendering/renderer/console.py +54 -0
  27. glaip_sdk/utils/rendering/renderer/debug.py +82 -0
  28. glaip_sdk/utils/rendering/renderer/panels.py +123 -0
  29. glaip_sdk/utils/rendering/renderer/progress.py +118 -0
  30. glaip_sdk/utils/rendering/renderer/stream.py +198 -0
  31. glaip_sdk/utils/rendering/steps.py +168 -0
  32. glaip_sdk/utils/run_renderer.py +22 -1086
  33. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.3.dist-info}/METADATA +8 -36
  34. glaip_sdk-0.0.3.dist-info/RECORD +40 -0
  35. glaip_sdk/cli/config.py +0 -592
  36. glaip_sdk/utils.py +0 -167
  37. glaip_sdk-0.0.2.dist-info/RECORD +0 -28
  38. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.3.dist-info}/WHEEL +0 -0
  39. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.3.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/base.py CHANGED
@@ -12,6 +12,7 @@ from typing import Any, Union
12
12
  import httpx
13
13
  from dotenv import load_dotenv
14
14
 
15
+ from glaip_sdk.config.constants import SDK_NAME, SDK_VERSION
15
16
  from glaip_sdk.exceptions import (
16
17
  AuthenticationError,
17
18
  ConflictError,
@@ -82,7 +83,15 @@ class BaseClient:
82
83
 
83
84
  def _build_client(self, timeout: float) -> httpx.Client:
84
85
  """Build HTTP client with configuration."""
85
- from glaip_sdk.config.constants import SDK_NAME, SDK_VERSION
86
+ # For streaming operations, we need more generous read timeouts
87
+ # while keeping reasonable connect timeouts
88
+ timeout_config = httpx.Timeout(
89
+ timeout=timeout, # Total timeout
90
+ connect=min(30.0, timeout), # Connect timeout (max 30s)
91
+ read=timeout, # Read timeout (same as total for streaming)
92
+ write=min(30.0, timeout), # Write timeout (max 30s)
93
+ pool=timeout, # Pool timeout (same as total)
94
+ )
86
95
 
87
96
  return httpx.Client(
88
97
  base_url=self.api_url,
@@ -90,7 +99,7 @@ class BaseClient:
90
99
  "X-API-Key": self.api_key,
91
100
  "User-Agent": f"{SDK_NAME}/{SDK_VERSION}",
92
101
  },
93
- timeout=httpx.Timeout(timeout),
102
+ timeout=timeout_config,
94
103
  follow_redirects=True,
95
104
  http2=False,
96
105
  limits=httpx.Limits(max_keepalive_connections=10, max_connections=100),
@@ -113,6 +122,57 @@ class BaseClient:
113
122
  self.http_client.close()
114
123
  self.http_client = self._build_client(value)
115
124
 
125
+ def _post_then_fetch(
126
+ self,
127
+ id_key: str,
128
+ post_endpoint: str,
129
+ get_endpoint_fmt: str,
130
+ *,
131
+ json=None,
132
+ data=None,
133
+ files=None,
134
+ **kwargs,
135
+ ) -> Any:
136
+ """Helper for POST-then-GET pattern used in create methods.
137
+
138
+ Args:
139
+ id_key: Key in POST response containing the ID
140
+ post_endpoint: Endpoint for POST request
141
+ get_endpoint_fmt: Format string for GET endpoint (e.g., "/items/{id}")
142
+ json: JSON data for POST
143
+ data: Form data for POST
144
+ files: Files for POST
145
+ **kwargs: Additional kwargs for POST
146
+
147
+ Returns:
148
+ Full resource data from GET request
149
+ """
150
+ # Create the resource
151
+ post_kwargs = {}
152
+ if json is not None:
153
+ post_kwargs["json"] = json
154
+ if data is not None:
155
+ post_kwargs["data"] = data
156
+ if files is not None:
157
+ post_kwargs["files"] = files
158
+ post_kwargs.update(kwargs)
159
+
160
+ response_data = self._request("POST", post_endpoint, **post_kwargs)
161
+
162
+ # Extract the ID
163
+ if isinstance(response_data, dict):
164
+ resource_id = response_data.get(id_key)
165
+ else:
166
+ # Fallback: assume response_data is the ID directly
167
+ resource_id = str(response_data)
168
+
169
+ if not resource_id:
170
+ raise ValueError(f"Backend did not return {id_key}")
171
+
172
+ # Fetch the full resource details
173
+ get_endpoint = get_endpoint_fmt.format(id=resource_id)
174
+ return self._request("GET", get_endpoint)
175
+
116
176
  def _request(self, method: str, endpoint: str, **kwargs) -> Any:
117
177
  """Make HTTP request with error handling."""
118
178
  client_log.debug(f"Making {method} request to {endpoint}")
glaip_sdk/client/mcps.py CHANGED
@@ -10,6 +10,7 @@ from typing import Any
10
10
 
11
11
  from glaip_sdk.client.base import BaseClient
12
12
  from glaip_sdk.models import MCP
13
+ from glaip_sdk.utils.client_utils import create_model_instances, find_by_name
13
14
 
14
15
  # Set up module-level logger
15
16
  logger = logging.getLogger("glaip_sdk.mcps")
@@ -30,7 +31,7 @@ class MCPClient(BaseClient):
30
31
  def list_mcps(self) -> list[MCP]:
31
32
  """List all MCPs."""
32
33
  data = self._request("GET", "/mcps/")
33
- return [MCP(**mcp_data)._set_client(self) for mcp_data in (data or [])]
34
+ return create_model_instances(data, MCP, self)
34
35
 
35
36
  def get_mcp_by_id(self, mcp_id: str) -> MCP:
36
37
  """Get MCP by ID."""
@@ -41,13 +42,8 @@ class MCPClient(BaseClient):
41
42
  """Find MCPs by name."""
42
43
  # Backend doesn't support name query parameter, so we fetch all and filter client-side
43
44
  data = self._request("GET", "/mcps/")
44
- mcps = [MCP(**mcp_data)._set_client(self) for mcp_data in (data or [])]
45
-
46
- if name:
47
- # Client-side filtering by name (case-insensitive)
48
- mcps = [mcp for mcp in mcps if name.lower() in mcp.name.lower()]
49
-
50
- return mcps
45
+ mcps = create_model_instances(data, MCP, self)
46
+ return find_by_name(mcps, name, case_sensitive=False)
51
47
 
52
48
  def create_mcp(
53
49
  self,
@@ -66,18 +62,14 @@ class MCPClient(BaseClient):
66
62
  if config:
67
63
  payload["config"] = config
68
64
 
69
- # Create the MCP (backend returns only the ID)
70
- response_data = self._request("POST", "/mcps/", json=payload)
71
-
72
- # Extract the ID from the response
73
- if isinstance(response_data, dict) and "id" in response_data:
74
- mcp_id = response_data["id"]
75
- else:
76
- # Fallback: assume response_data is the ID directly
77
- mcp_id = str(response_data)
78
-
79
- # Fetch the full MCP details
80
- return self.get_mcp_by_id(mcp_id)
65
+ # Create the MCP and fetch full details
66
+ full_mcp_data = self._post_then_fetch(
67
+ id_key="id",
68
+ post_endpoint="/mcps/",
69
+ get_endpoint_fmt="/mcps/{id}",
70
+ json=payload,
71
+ )
72
+ return MCP(**full_mcp_data)._set_client(self)
81
73
 
82
74
  def update_mcp(self, mcp_id: str, **kwargs) -> MCP:
83
75
  """Update an existing MCP."""
@@ -92,3 +84,54 @@ class MCPClient(BaseClient):
92
84
  """Get tools available from an MCP."""
93
85
  data = self._request("GET", f"/mcps/{mcp_id}/tools")
94
86
  return data or []
87
+
88
+ def test_mcp_connection(self, config: dict[str, Any]) -> dict[str, Any]:
89
+ """Test MCP connection using configuration.
90
+
91
+ Args:
92
+ config: MCP configuration dictionary
93
+
94
+ Returns:
95
+ dict: Connection test result
96
+
97
+ Raises:
98
+ Exception: If connection test fails
99
+ """
100
+ try:
101
+ response = self._request("POST", "/mcps/connect", json=config)
102
+ return response
103
+ except Exception as e:
104
+ logger.error(f"Failed to test MCP connection: {e}")
105
+ raise
106
+
107
+ def test_mcp_connection_from_config(self, config: dict[str, Any]) -> dict[str, Any]:
108
+ """Test MCP connection using configuration (alias for test_mcp_connection).
109
+
110
+ Args:
111
+ config: MCP configuration dictionary
112
+
113
+ Returns:
114
+ dict: Connection test result
115
+ """
116
+ return self.test_mcp_connection(config)
117
+
118
+ def get_mcp_tools_from_config(self, config: dict[str, Any]) -> list[dict[str, Any]]:
119
+ """Fetch tools from MCP configuration without saving.
120
+
121
+ Args:
122
+ config: MCP configuration dictionary
123
+
124
+ Returns:
125
+ list: List of available tools from the MCP
126
+
127
+ Raises:
128
+ Exception: If tool fetching fails
129
+ """
130
+ try:
131
+ response = self._request("POST", "/mcps/connect/tools", json=config)
132
+ if response is None:
133
+ return []
134
+ return response.get("tools", []) or []
135
+ except Exception as e:
136
+ logger.error(f"Failed to get MCP tools from config: {e}")
137
+ raise
glaip_sdk/client/tools.py CHANGED
@@ -6,9 +6,15 @@ Authors:
6
6
  """
7
7
 
8
8
  import logging
9
+ import os
10
+ import tempfile
9
11
 
10
12
  from glaip_sdk.client.base import BaseClient
11
13
  from glaip_sdk.models import Tool
14
+ from glaip_sdk.utils.client_utils import (
15
+ create_model_instances,
16
+ find_by_name,
17
+ )
12
18
 
13
19
  # Set up module-level logger
14
20
  logger = logging.getLogger("glaip_sdk.tools")
@@ -29,7 +35,7 @@ class ToolClient(BaseClient):
29
35
  def list_tools(self) -> list[Tool]:
30
36
  """List all tools."""
31
37
  data = self._request("GET", "/tools/")
32
- return [Tool(**tool_data)._set_client(self) for tool_data in (data or [])]
38
+ return create_model_instances(data, Tool, self)
33
39
 
34
40
  def get_tool_by_id(self, tool_id: str) -> Tool:
35
41
  """Get tool by ID."""
@@ -40,13 +46,8 @@ class ToolClient(BaseClient):
40
46
  """Find tools by name."""
41
47
  # Backend doesn't support name query parameter, so we fetch all and filter client-side
42
48
  data = self._request("GET", "/tools/")
43
- tools = [Tool(**tool_data)._set_client(self) for tool_data in (data or [])]
44
-
45
- if name:
46
- # Client-side filtering by name (case-insensitive)
47
- tools = [tool for tool in tools if name.lower() in tool.name.lower()]
48
-
49
- return tools
49
+ tools = create_model_instances(data, Tool, self)
50
+ return find_by_name(tools, name, case_sensitive=False)
50
51
 
51
52
  def create_tool(
52
53
  self,
@@ -115,6 +116,8 @@ class ToolClient(BaseClient):
115
116
  name: str,
116
117
  code: str,
117
118
  framework: str = "langchain",
119
+ description: str | None = None,
120
+ tags: list[str] | None = None,
118
121
  ) -> Tool:
119
122
  """Create a new tool plugin from code string.
120
123
 
@@ -130,9 +133,6 @@ class ToolClient(BaseClient):
130
133
  Returns:
131
134
  Tool: The created tool object
132
135
  """
133
- import os
134
- import tempfile
135
-
136
136
  # Create a temporary file with the tool code
137
137
  with tempfile.NamedTemporaryFile(
138
138
  mode="w", suffix=".py", prefix=f"{name}_", delete=False, encoding="utf-8"
@@ -141,22 +141,28 @@ class ToolClient(BaseClient):
141
141
  temp_file_path = temp_file.name
142
142
 
143
143
  try:
144
- # Read the file content and upload it
145
- with open(temp_file_path, encoding="utf-8") as f:
146
- script_content = f.read()
147
-
148
- # Create a file-like object for upload
149
- import io
150
-
151
- file_obj = io.BytesIO(script_content.encode("utf-8"))
152
- file_obj.name = os.path.basename(temp_file_path)
153
-
154
- # Use multipart form data for file upload
155
- files = {"file": (file_obj.name, file_obj, "text/plain")}
156
- data = {"framework": framework}
157
-
158
- # Make the upload request
159
- response = self._request("POST", "/tools/upload", files=files, data=data)
144
+ # Prepare multipart upload
145
+ filename = os.path.basename(temp_file_path)
146
+ with open(temp_file_path, "rb") as fb:
147
+ files = {
148
+ "file": (filename, fb, "application/octet-stream"),
149
+ }
150
+ data = {
151
+ "name": name,
152
+ "framework": framework,
153
+ }
154
+ if description:
155
+ data["description"] = description
156
+ if tags:
157
+ # Backend might expect comma-separated or JSON; start with comma-separated
158
+ data["tags"] = ",".join(tags)
159
+
160
+ response = self._request(
161
+ "POST",
162
+ "/tools/upload",
163
+ files=files,
164
+ data=data,
165
+ )
160
166
  return Tool(**response)._set_client(self)
161
167
  finally:
162
168
  # Clean up the temporary file
@@ -189,5 +195,66 @@ class ToolClient(BaseClient):
189
195
  self._request("POST", f"/tools/{tool_id}/uninstall")
190
196
  return True
191
197
  except Exception as e:
192
- logger.error(f"Failed to uninstall tool {tool_id}: {e}")
198
+ logger.error(f"Failed to install tool {tool_id}: {e}")
193
199
  return False
200
+
201
+ def get_tool_script(self, tool_id: str) -> str:
202
+ """Get the tool script content.
203
+
204
+ Args:
205
+ tool_id: The ID of the tool
206
+
207
+ Returns:
208
+ str: The tool script content
209
+
210
+ Raises:
211
+ Exception: If the tool script cannot be retrieved
212
+ """
213
+ try:
214
+ response = self._request("GET", f"/tools/{tool_id}/script")
215
+ return response.get("script", "") or response.get("content", "")
216
+ except Exception as e:
217
+ logger.error(f"Failed to get tool script for {tool_id}: {e}")
218
+ raise
219
+
220
+ def update_tool_via_file(self, tool_id: str, file_path: str, **kwargs) -> Tool:
221
+ """Update a tool plugin via file upload.
222
+
223
+ Args:
224
+ tool_id: The ID of the tool to update
225
+ file_path: Path to the new tool file
226
+ **kwargs: Additional metadata to update (name, description, tags, etc.)
227
+
228
+ Returns:
229
+ Tool: The updated tool object
230
+
231
+ Raises:
232
+ FileNotFoundError: If the file doesn't exist
233
+ Exception: If the update fails
234
+ """
235
+ import os
236
+
237
+ if not os.path.exists(file_path):
238
+ raise FileNotFoundError(f"Tool file not found: {file_path}")
239
+
240
+ try:
241
+ # Prepare multipart upload
242
+ filename = os.path.basename(file_path)
243
+ with open(file_path, "rb") as fb:
244
+ files = {
245
+ "file": (filename, fb, "application/octet-stream"),
246
+ }
247
+
248
+ # Add any additional metadata
249
+ data = kwargs.copy()
250
+
251
+ response = self._request(
252
+ "PUT",
253
+ f"/tools/{tool_id}/upload",
254
+ files=files,
255
+ data=data,
256
+ )
257
+ return Tool(**response)._set_client(self)
258
+ except Exception as e:
259
+ logger.error(f"Failed to update tool {tool_id} via file: {e}")
260
+ raise
@@ -10,11 +10,12 @@ DEFAULT_MODEL_PROVIDER = "openai"
10
10
 
11
11
  # Default timeout values
12
12
  DEFAULT_TIMEOUT = 30.0
13
- DEFAULT_AGENT_TIMEOUT = 300.0
13
+ DEFAULT_AGENT_RUN_TIMEOUT = 300.0
14
+
15
+ # User agent and version
14
16
 
15
- # User agent
16
17
  SDK_NAME = "glaip-sdk"
17
- SDK_VERSION = "0.1.1"
18
+ SDK_VERSION = "0.1.0"
18
19
 
19
20
  # Reserved names that cannot be used for agents/tools
20
21
  RESERVED_NAMES = {
@@ -26,3 +27,9 @@ RESERVED_NAMES = {
26
27
  "demo",
27
28
  "sample",
28
29
  }
30
+
31
+ # Agent creation/update constants
32
+ DEFAULT_AGENT_TYPE = "config"
33
+ DEFAULT_AGENT_FRAMEWORK = "langchain"
34
+ DEFAULT_AGENT_VERSION = "1.0"
35
+ DEFAULT_AGENT_PROVIDER = "openai"
glaip_sdk/exceptions.py CHANGED
@@ -87,6 +87,19 @@ class TimeoutError(APIError):
87
87
  pass
88
88
 
89
89
 
90
+ class AgentTimeoutError(TimeoutError):
91
+ """Agent execution timeout with specific duration information."""
92
+
93
+ def __init__(self, timeout_seconds: float, agent_name: str = None):
94
+ agent_info = f" for agent '{agent_name}'" if agent_name else ""
95
+ message = (
96
+ f"Agent execution timed out after {timeout_seconds} seconds{agent_info}"
97
+ )
98
+ super().__init__(message)
99
+ self.timeout_seconds = timeout_seconds
100
+ self.agent_name = agent_name
101
+
102
+
90
103
  class ClientError(APIError):
91
104
  """Client-side error (e.g., invalid request format, missing parameters)."""
92
105
 
glaip_sdk/models.py CHANGED
@@ -5,10 +5,13 @@ Authors:
5
5
  Raymond Christopher (raymond.christopher@gdplabs.id)
6
6
  """
7
7
 
8
+ from datetime import datetime
8
9
  from typing import Any
9
10
 
10
11
  from pydantic import BaseModel
11
12
 
13
+ from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
14
+
12
15
 
13
16
  class Agent(BaseModel):
14
17
  """Agent model for API responses."""
@@ -22,11 +25,17 @@ class Agent(BaseModel):
22
25
  version: str | None = None
23
26
  tools: list[dict[str, Any]] | None = None # Backend returns ToolReference objects
24
27
  agents: list[dict[str, Any]] | None = None # Backend returns AgentReference objects
28
+ mcps: list[dict[str, Any]] | None = None # Backend returns MCPReference objects
29
+ tool_configs: dict[str, Any] | None = (
30
+ None # Backend returns tool configurations keyed by tool UUID
31
+ )
25
32
  agent_config: dict[str, Any] | None = None
26
- timeout: int = 300
33
+ timeout: int = DEFAULT_AGENT_RUN_TIMEOUT
27
34
  metadata: dict[str, Any] | None = None
28
35
  language_model_id: str | None = None
29
36
  a2a_profile: dict[str, Any] | None = None
37
+ created_at: datetime | None = None # Backend returns creation timestamp
38
+ updated_at: datetime | None = None # Backend returns last update timestamp
30
39
  _client: Any = None
31
40
 
32
41
  def _set_client(self, client):
@@ -34,15 +43,22 @@ class Agent(BaseModel):
34
43
  self._client = client
35
44
  return self
36
45
 
37
- def run(self, message: str, **kwargs) -> str:
38
- """Run the agent with a message."""
46
+ def run(self, message: str, verbose: bool = False, **kwargs) -> str:
47
+ """Run the agent with a message.
48
+
49
+ Args:
50
+ message: The message to send to the agent
51
+ verbose: Enable verbose output and event JSON logging
52
+ **kwargs: Additional arguments passed to run_agent
53
+ """
39
54
  if not self._client:
40
55
  raise RuntimeError(
41
56
  "No client available. Use client.get_agent_by_id() to get a client-connected agent."
42
57
  )
43
58
  # Automatically pass the agent name for better renderer display
44
59
  kwargs.setdefault("agent_name", self.name)
45
- return self._client.run_agent(self.id, message, **kwargs)
60
+ # Pass verbose flag through to enable event JSON output
61
+ return self._client.run_agent(self.id, message, verbose=verbose, **kwargs)
46
62
 
47
63
  def update(self, **kwargs) -> "Agent":
48
64
  """Update agent attributes."""
@@ -7,23 +7,24 @@ Authors:
7
7
  import re
8
8
  from uuid import UUID
9
9
 
10
- from .run_renderer import (
11
- RichStreamRenderer,
12
- RunStats,
13
- Step,
14
- StepManager,
15
- )
10
+ import click
16
11
 
17
- __all__ = [
18
- "RichStreamRenderer",
19
- "RunStats",
20
- "Step",
21
- "StepManager",
22
- "is_uuid",
23
- "sanitize_name",
24
- "format_file_size",
25
- "progress_bar",
26
- ]
12
+ from .rendering.models import RunStats, Step
13
+ from .rendering.steps import StepManager
14
+ from .run_renderer import RichStreamRenderer
15
+
16
+ # Rich imports with availability check
17
+ try:
18
+ from rich import box
19
+ from rich.console import Console
20
+ from rich.panel import Panel
21
+ from rich.text import Text
22
+
23
+ RICH_AVAILABLE = True
24
+ except ImportError:
25
+ RICH_AVAILABLE = False
26
+
27
+ # Functions are defined below in this file
27
28
 
28
29
 
29
30
  def is_uuid(value: str) -> bool:
@@ -84,8 +85,6 @@ def progress_bar(iterable, description: str = "Processing"):
84
85
  Items from iterable with progress display
85
86
  """
86
87
  try:
87
- import click
88
-
89
88
  with click.progressbar(iterable, label=description) as bar:
90
89
  for item in bar:
91
90
  yield item
@@ -93,3 +92,102 @@ def progress_bar(iterable, description: str = "Processing"):
93
92
  # Fallback if click not available
94
93
  for item in iterable:
95
94
  yield item
95
+
96
+
97
+ # Rich display utilities for enhanced output
98
+ # RICH_AVAILABLE is determined by the try/except block above
99
+
100
+
101
+ def print_agent_output(output: str, title: str = "Agent Output") -> None:
102
+ """Print agent output with rich formatting.
103
+
104
+ Args:
105
+ output: The agent's response text
106
+ title: Title for the output panel
107
+ """
108
+ if RICH_AVAILABLE:
109
+ console = Console()
110
+ panel = Panel(
111
+ Text(output, style="green"),
112
+ title=title,
113
+ border_style="green",
114
+ box=box.ROUNDED,
115
+ )
116
+ console.print(panel)
117
+ else:
118
+ print(f"\n=== {title} ===")
119
+ print(output)
120
+ print("=" * (len(title) + 8))
121
+
122
+
123
+ def print_agent_created(agent, title: str = "🤖 Agent Created") -> None:
124
+ """Print agent creation success with rich formatting.
125
+
126
+ Args:
127
+ agent: The created agent object
128
+ title: Title for the output panel
129
+ """
130
+ if RICH_AVAILABLE:
131
+ console = Console()
132
+ panel = Panel(
133
+ f"[green]✅ Agent '{agent.name}' created successfully![/green]\n\n"
134
+ f"ID: {agent.id}\n"
135
+ f"Model: {getattr(agent, 'model', 'N/A')}\n"
136
+ f"Type: {getattr(agent, 'type', 'config')}\n"
137
+ f"Framework: {getattr(agent, 'framework', 'langchain')}\n"
138
+ f"Version: {getattr(agent, 'version', '1.0')}",
139
+ title=title,
140
+ border_style="green",
141
+ box=box.ROUNDED,
142
+ )
143
+ console.print(panel)
144
+ else:
145
+ print(f"✅ Agent '{agent.name}' created successfully!")
146
+ print(f"ID: {agent.id}")
147
+ print(f"Model: {getattr(agent, 'model', 'N/A')}")
148
+ print(f"Type: {getattr(agent, 'type', 'config')}")
149
+ print(f"Framework: {getattr(agent, 'framework', 'langchain')}")
150
+ print(f"Version: {getattr(agent, 'version', '1.0')}")
151
+
152
+
153
+ def print_agent_updated(agent) -> None:
154
+ """Print agent update success with rich formatting.
155
+
156
+ Args:
157
+ agent: The updated agent object
158
+ """
159
+ if RICH_AVAILABLE:
160
+ console = Console()
161
+ console.print(f"[green]✅ Agent '{agent.name}' updated successfully[/green]")
162
+ else:
163
+ print(f"✅ Agent '{agent.name}' updated successfully")
164
+
165
+
166
+ def print_agent_deleted(agent_id: str) -> None:
167
+ """Print agent deletion success with rich formatting.
168
+
169
+ Args:
170
+ agent_id: The deleted agent's ID
171
+ """
172
+ if RICH_AVAILABLE:
173
+ console = Console()
174
+ console.print(f"[green]✅ Agent deleted successfully (ID: {agent_id})[/green]")
175
+ else:
176
+ print(f"✅ Agent deleted successfully (ID: {agent_id})")
177
+
178
+
179
+ __all__ = [
180
+ "RichStreamRenderer",
181
+ "RunStats",
182
+ "Step",
183
+ "StepManager",
184
+ "RICH_AVAILABLE",
185
+ "is_uuid",
186
+ "sanitize_name",
187
+ "format_file_size",
188
+ "progress_bar",
189
+ "print_agent_output",
190
+ "print_agent_created",
191
+ "print_agent_updated",
192
+ "print_agent_deleted",
193
+ ]