glaip-sdk 0.0.2__py3-none-any.whl → 0.0.4__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/branding.py +145 -0
- glaip_sdk/cli/commands/agents.py +876 -166
- glaip_sdk/cli/commands/configure.py +46 -104
- glaip_sdk/cli/commands/init.py +43 -118
- glaip_sdk/cli/commands/mcps.py +86 -161
- glaip_sdk/cli/commands/tools.py +196 -57
- glaip_sdk/cli/main.py +43 -29
- glaip_sdk/cli/utils.py +258 -27
- glaip_sdk/client/__init__.py +54 -2
- glaip_sdk/client/agents.py +196 -237
- glaip_sdk/client/base.py +62 -2
- glaip_sdk/client/mcps.py +63 -20
- glaip_sdk/client/tools.py +236 -81
- glaip_sdk/config/constants.py +10 -3
- glaip_sdk/exceptions.py +13 -0
- glaip_sdk/models.py +21 -5
- 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.2.dist-info → glaip_sdk-0.0.4.dist-info}/METADATA +8 -36
- glaip_sdk-0.0.4.dist-info/RECORD +41 -0
- glaip_sdk/cli/config.py +0 -592
- glaip_sdk/utils.py +0 -167
- glaip_sdk-0.0.2.dist-info/RECORD +0 -28
- {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/entry_points.txt +0 -0
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,81 +46,190 @@ 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 =
|
|
49
|
+
tools = create_model_instances(data, Tool, self)
|
|
50
|
+
return find_by_name(tools, name, case_sensitive=False)
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
tools = [tool for tool in tools if name.lower() in tool.name.lower()]
|
|
52
|
+
def _validate_and_read_file(self, file_path: str) -> str:
|
|
53
|
+
"""Validate file exists and read its content.
|
|
48
54
|
|
|
49
|
-
|
|
55
|
+
Args:
|
|
56
|
+
file_path: Path to the file to read
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
Returns:
|
|
59
|
+
str: File content
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
FileNotFoundError: If file doesn't exist
|
|
63
|
+
"""
|
|
64
|
+
if not os.path.exists(file_path):
|
|
65
|
+
raise FileNotFoundError(f"Tool file not found: {file_path}")
|
|
66
|
+
|
|
67
|
+
with open(file_path, encoding="utf-8") as f:
|
|
68
|
+
return f.read()
|
|
69
|
+
|
|
70
|
+
def _extract_name_from_file(self, file_path: str) -> str:
|
|
71
|
+
"""Extract tool name from file path.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
file_path: Path to the file
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
str: Extracted name (filename without extension)
|
|
78
|
+
"""
|
|
79
|
+
return os.path.splitext(os.path.basename(file_path))[0]
|
|
80
|
+
|
|
81
|
+
def _prepare_upload_data(
|
|
82
|
+
self, name: str, framework: str, description: str | None = None, **kwargs
|
|
83
|
+
) -> dict:
|
|
84
|
+
"""Prepare upload data dictionary.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
name: Tool name
|
|
88
|
+
framework: Tool framework
|
|
89
|
+
description: Optional description
|
|
90
|
+
**kwargs: Additional parameters
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
dict: Upload data dictionary
|
|
94
|
+
"""
|
|
95
|
+
data = {
|
|
96
|
+
"name": name,
|
|
97
|
+
"framework": framework,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if description:
|
|
101
|
+
data["description"] = description
|
|
102
|
+
|
|
103
|
+
# Handle tags if provided in kwargs
|
|
104
|
+
if "tags" in kwargs and kwargs["tags"]:
|
|
105
|
+
if isinstance(kwargs["tags"], list):
|
|
106
|
+
data["tags"] = ",".join(kwargs["tags"])
|
|
107
|
+
else:
|
|
108
|
+
data["tags"] = kwargs["tags"]
|
|
109
|
+
|
|
110
|
+
# Include any other kwargs in the upload data
|
|
111
|
+
for key, value in kwargs.items():
|
|
112
|
+
if key not in ["tags"]: # tags already handled above
|
|
113
|
+
data[key] = value
|
|
114
|
+
|
|
115
|
+
return data
|
|
116
|
+
|
|
117
|
+
def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
|
|
118
|
+
"""Upload tool file to server.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
file_path: Path to temporary file to upload
|
|
122
|
+
upload_data: Dictionary with upload metadata
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Tool: Created tool object
|
|
126
|
+
"""
|
|
127
|
+
with open(file_path, "rb") as fb:
|
|
128
|
+
files = {
|
|
129
|
+
"file": (os.path.basename(file_path), fb, "application/octet-stream"),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
response = self._request(
|
|
133
|
+
"POST",
|
|
134
|
+
"/tools/upload",
|
|
135
|
+
files=files,
|
|
136
|
+
data=upload_data,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return Tool(**response)._set_client(self)
|
|
140
|
+
|
|
141
|
+
def _create_tool_from_file(
|
|
52
142
|
self,
|
|
143
|
+
file_path: str,
|
|
53
144
|
name: str | None = None,
|
|
54
|
-
tool_type: str = "custom",
|
|
55
145
|
description: str | None = None,
|
|
56
|
-
tool_script: str | None = None,
|
|
57
|
-
tool_file: str | None = None,
|
|
58
|
-
file_path: str | None = None,
|
|
59
|
-
code: str | None = None,
|
|
60
146
|
framework: str = "langchain",
|
|
61
147
|
**kwargs,
|
|
62
148
|
) -> Tool:
|
|
63
|
-
"""Create
|
|
149
|
+
"""Create tool from file content using upload endpoint.
|
|
64
150
|
|
|
65
151
|
Args:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
description:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
**kwargs: Additional tool parameters
|
|
152
|
+
file_path: Path to tool file
|
|
153
|
+
name: Optional tool name (auto-detected if not provided)
|
|
154
|
+
description: Optional tool description
|
|
155
|
+
framework: Tool framework
|
|
156
|
+
**kwargs: Additional parameters
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Tool: Created tool object
|
|
75
160
|
"""
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
tool_file = file_path
|
|
79
|
-
if code and not tool_script:
|
|
80
|
-
tool_script = code
|
|
161
|
+
# Read and validate file
|
|
162
|
+
file_content = self._validate_and_read_file(file_path)
|
|
81
163
|
|
|
82
|
-
# Auto-detect name
|
|
83
|
-
if not name
|
|
84
|
-
|
|
164
|
+
# Auto-detect name if not provided
|
|
165
|
+
if not name:
|
|
166
|
+
name = self._extract_name_from_file(file_path)
|
|
85
167
|
|
|
86
|
-
|
|
168
|
+
# Handle description - generate default if not provided or empty
|
|
169
|
+
if description is None or description == "":
|
|
170
|
+
# Generate default description based on tool_type if available
|
|
171
|
+
tool_type = kwargs.get("tool_type", "custom")
|
|
172
|
+
description = f"A {tool_type} tool"
|
|
87
173
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
174
|
+
# Create temporary file for upload
|
|
175
|
+
with tempfile.NamedTemporaryFile(
|
|
176
|
+
mode="w",
|
|
177
|
+
suffix=".py",
|
|
178
|
+
prefix=f"{name}_",
|
|
179
|
+
delete=False,
|
|
180
|
+
encoding="utf-8",
|
|
181
|
+
) as temp_file:
|
|
182
|
+
temp_file.write(file_content)
|
|
183
|
+
temp_file_path = temp_file.name
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# Prepare upload data
|
|
187
|
+
upload_data = self._prepare_upload_data(
|
|
188
|
+
name=name, framework=framework, description=description, **kwargs
|
|
91
189
|
)
|
|
92
190
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
description = f"A {tool_type} tool"
|
|
191
|
+
# Upload file
|
|
192
|
+
return self._upload_tool_file(temp_file_path, upload_data)
|
|
96
193
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
194
|
+
finally:
|
|
195
|
+
# Clean up temporary file
|
|
196
|
+
try:
|
|
197
|
+
os.unlink(temp_file_path)
|
|
198
|
+
except OSError:
|
|
199
|
+
pass # Ignore cleanup errors
|
|
104
200
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
201
|
+
def create_tool(
|
|
202
|
+
self,
|
|
203
|
+
file_path: str,
|
|
204
|
+
name: str | None = None,
|
|
205
|
+
description: str | None = None,
|
|
206
|
+
framework: str = "langchain",
|
|
207
|
+
**kwargs,
|
|
208
|
+
) -> Tool:
|
|
209
|
+
"""Create a new tool from a file.
|
|
109
210
|
|
|
110
|
-
|
|
111
|
-
|
|
211
|
+
Args:
|
|
212
|
+
file_path: File path to tool script (required) - file content will be read and processed as plugin
|
|
213
|
+
name: Tool name (auto-detected from file if not provided)
|
|
214
|
+
description: Tool description (auto-generated if not provided)
|
|
215
|
+
framework: Tool framework (defaults to "langchain")
|
|
216
|
+
**kwargs: Additional tool parameters
|
|
217
|
+
"""
|
|
218
|
+
return self._create_tool_from_file(
|
|
219
|
+
file_path=file_path,
|
|
220
|
+
name=name,
|
|
221
|
+
description=description,
|
|
222
|
+
framework=framework,
|
|
223
|
+
**kwargs,
|
|
224
|
+
)
|
|
112
225
|
|
|
113
226
|
def create_tool_from_code(
|
|
114
227
|
self,
|
|
115
228
|
name: str,
|
|
116
229
|
code: str,
|
|
117
230
|
framework: str = "langchain",
|
|
231
|
+
description: str | None = None,
|
|
232
|
+
tags: list[str] | None = None,
|
|
118
233
|
) -> Tool:
|
|
119
234
|
"""Create a new tool plugin from code string.
|
|
120
235
|
|
|
@@ -126,38 +241,35 @@ class ToolClient(BaseClient):
|
|
|
126
241
|
name: Name for the tool (used for temporary file naming)
|
|
127
242
|
code: Python code containing the tool plugin
|
|
128
243
|
framework: Tool framework (defaults to "langchain")
|
|
244
|
+
description: Optional tool description
|
|
245
|
+
tags: Optional list of tags
|
|
129
246
|
|
|
130
247
|
Returns:
|
|
131
248
|
Tool: The created tool object
|
|
132
249
|
"""
|
|
133
|
-
import os
|
|
134
|
-
import tempfile
|
|
135
|
-
|
|
136
250
|
# Create a temporary file with the tool code
|
|
137
251
|
with tempfile.NamedTemporaryFile(
|
|
138
|
-
mode="w",
|
|
252
|
+
mode="w",
|
|
253
|
+
suffix=".py",
|
|
254
|
+
prefix=f"{name}_",
|
|
255
|
+
delete=False,
|
|
256
|
+
encoding="utf-8",
|
|
139
257
|
) as temp_file:
|
|
140
258
|
temp_file.write(code)
|
|
141
259
|
temp_file_path = temp_file.name
|
|
142
260
|
|
|
143
261
|
try:
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
file_obj = io.BytesIO(script_content.encode("utf-8"))
|
|
152
|
-
file_obj.name = os.path.basename(temp_file_path)
|
|
262
|
+
# Prepare upload data using shared helper
|
|
263
|
+
upload_data = self._prepare_upload_data(
|
|
264
|
+
name=name,
|
|
265
|
+
framework=framework,
|
|
266
|
+
description=description,
|
|
267
|
+
tags=tags if tags else None,
|
|
268
|
+
)
|
|
153
269
|
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
data = {"framework": framework}
|
|
270
|
+
# Upload file using shared helper
|
|
271
|
+
return self._upload_tool_file(temp_file_path, upload_data)
|
|
157
272
|
|
|
158
|
-
# Make the upload request
|
|
159
|
-
response = self._request("POST", "/tools/upload", files=files, data=data)
|
|
160
|
-
return Tool(**response)._set_client(self)
|
|
161
273
|
finally:
|
|
162
274
|
# Clean up the temporary file
|
|
163
275
|
try:
|
|
@@ -174,20 +286,63 @@ class ToolClient(BaseClient):
|
|
|
174
286
|
"""Delete a tool."""
|
|
175
287
|
self._request("DELETE", f"/tools/{tool_id}")
|
|
176
288
|
|
|
177
|
-
def
|
|
178
|
-
"""
|
|
289
|
+
def get_tool_script(self, tool_id: str) -> str:
|
|
290
|
+
"""Get the tool script content.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
tool_id: The ID of the tool
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
str: The tool script content
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
Exception: If the tool script cannot be retrieved
|
|
300
|
+
"""
|
|
179
301
|
try:
|
|
180
|
-
self._request("
|
|
181
|
-
return
|
|
302
|
+
response = self._request("GET", f"/tools/{tool_id}/script")
|
|
303
|
+
return response.get("script", "") or response.get("content", "")
|
|
182
304
|
except Exception as e:
|
|
183
|
-
logger.error(f"Failed to
|
|
184
|
-
|
|
305
|
+
logger.error(f"Failed to get tool script for {tool_id}: {e}")
|
|
306
|
+
raise
|
|
307
|
+
|
|
308
|
+
def update_tool_via_file(self, tool_id: str, file_path: str, **kwargs) -> Tool:
|
|
309
|
+
"""Update a tool plugin via file upload.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
tool_id: The ID of the tool to update
|
|
313
|
+
file_path: Path to the new tool file
|
|
314
|
+
**kwargs: Additional metadata to update (name, description, tags, etc.)
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Tool: The updated tool object
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
FileNotFoundError: If the file doesn't exist
|
|
321
|
+
Exception: If the update fails
|
|
322
|
+
"""
|
|
323
|
+
# Validate file exists
|
|
324
|
+
self._validate_and_read_file(file_path)
|
|
185
325
|
|
|
186
|
-
def uninstall_tool(self, tool_id: str) -> bool:
|
|
187
|
-
"""Uninstall a tool."""
|
|
188
326
|
try:
|
|
189
|
-
|
|
190
|
-
|
|
327
|
+
# Prepare multipart upload
|
|
328
|
+
with open(file_path, "rb") as fb:
|
|
329
|
+
files = {
|
|
330
|
+
"file": (
|
|
331
|
+
os.path.basename(file_path),
|
|
332
|
+
fb,
|
|
333
|
+
"application/octet-stream",
|
|
334
|
+
),
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
response = self._request(
|
|
338
|
+
"PUT",
|
|
339
|
+
f"/tools/{tool_id}/upload",
|
|
340
|
+
files=files,
|
|
341
|
+
data=kwargs, # Pass kwargs directly as data
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return Tool(**response)._set_client(self)
|
|
345
|
+
|
|
191
346
|
except Exception as e:
|
|
192
|
-
logger.error(f"Failed to
|
|
193
|
-
|
|
347
|
+
logger.error(f"Failed to update tool {tool_id} via file: {e}")
|
|
348
|
+
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."""
|
|
@@ -112,7 +128,7 @@ class Tool(BaseModel):
|
|
|
112
128
|
raise RuntimeError(
|
|
113
129
|
"No client available. Use client.get_tool_by_id() to get a client-connected tool."
|
|
114
130
|
)
|
|
115
|
-
self._client.
|
|
131
|
+
self._client.delete_tool(self.id)
|
|
116
132
|
|
|
117
133
|
|
|
118
134
|
class MCP(BaseModel):
|