glaip-sdk 0.0.1b10__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.
- glaip_sdk/__init__.py +2 -2
- glaip_sdk/_version.py +51 -0
- glaip_sdk/cli/commands/agents.py +201 -109
- glaip_sdk/cli/commands/configure.py +29 -87
- glaip_sdk/cli/commands/init.py +16 -7
- glaip_sdk/cli/commands/mcps.py +73 -153
- glaip_sdk/cli/commands/tools.py +185 -49
- glaip_sdk/cli/main.py +30 -27
- glaip_sdk/cli/utils.py +126 -13
- glaip_sdk/client/__init__.py +54 -2
- glaip_sdk/client/agents.py +175 -237
- glaip_sdk/client/base.py +62 -2
- glaip_sdk/client/mcps.py +63 -20
- glaip_sdk/client/tools.py +95 -28
- glaip_sdk/config/constants.py +10 -3
- glaip_sdk/exceptions.py +13 -0
- glaip_sdk/models.py +20 -4
- glaip_sdk/utils/__init__.py +116 -18
- glaip_sdk/utils/client_utils.py +284 -0
- glaip_sdk/utils/rendering/__init__.py +1 -0
- glaip_sdk/utils/rendering/formatting.py +211 -0
- glaip_sdk/utils/rendering/models.py +53 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +38 -0
- glaip_sdk/utils/rendering/renderer/base.py +827 -0
- glaip_sdk/utils/rendering/renderer/config.py +33 -0
- glaip_sdk/utils/rendering/renderer/console.py +54 -0
- glaip_sdk/utils/rendering/renderer/debug.py +82 -0
- glaip_sdk/utils/rendering/renderer/panels.py +123 -0
- glaip_sdk/utils/rendering/renderer/progress.py +118 -0
- glaip_sdk/utils/rendering/renderer/stream.py +198 -0
- glaip_sdk/utils/rendering/steps.py +168 -0
- glaip_sdk/utils/run_renderer.py +22 -1086
- {glaip_sdk-0.0.1b10.dist-info → glaip_sdk-0.0.3.dist-info}/METADATA +9 -37
- glaip_sdk-0.0.3.dist-info/RECORD +40 -0
- glaip_sdk/cli/config.py +0 -592
- glaip_sdk/utils.py +0 -167
- glaip_sdk-0.0.1b10.dist-info/RECORD +0 -28
- {glaip_sdk-0.0.1b10.dist-info → glaip_sdk-0.0.3.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.1b10.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
|
-
|
|
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=
|
|
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
|
|
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 =
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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 =
|
|
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
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
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
|
glaip_sdk/config/constants.py
CHANGED
|
@@ -10,11 +10,12 @@ DEFAULT_MODEL_PROVIDER = "openai"
|
|
|
10
10
|
|
|
11
11
|
# Default timeout values
|
|
12
12
|
DEFAULT_TIMEOUT = 30.0
|
|
13
|
-
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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."""
|
glaip_sdk/utils/__init__.py
CHANGED
|
@@ -7,23 +7,24 @@ Authors:
|
|
|
7
7
|
import re
|
|
8
8
|
from uuid import UUID
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
RichStreamRenderer,
|
|
12
|
-
RunStats,
|
|
13
|
-
Step,
|
|
14
|
-
StepManager,
|
|
15
|
-
)
|
|
10
|
+
import click
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
]
|