glaip-sdk 0.0.3__py3-none-any.whl → 0.0.5__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 +5 -5
- glaip_sdk/branding.py +146 -0
- glaip_sdk/cli/__init__.py +1 -1
- glaip_sdk/cli/agent_config.py +82 -0
- glaip_sdk/cli/commands/__init__.py +3 -3
- glaip_sdk/cli/commands/agents.py +786 -271
- glaip_sdk/cli/commands/configure.py +19 -19
- glaip_sdk/cli/commands/mcps.py +151 -141
- glaip_sdk/cli/commands/models.py +1 -1
- glaip_sdk/cli/commands/tools.py +252 -178
- glaip_sdk/cli/display.py +244 -0
- glaip_sdk/cli/io.py +106 -0
- glaip_sdk/cli/main.py +27 -20
- glaip_sdk/cli/resolution.py +59 -0
- glaip_sdk/cli/utils.py +372 -213
- glaip_sdk/cli/validators.py +235 -0
- glaip_sdk/client/__init__.py +3 -224
- glaip_sdk/client/agents.py +632 -171
- glaip_sdk/client/base.py +66 -4
- glaip_sdk/client/main.py +226 -0
- glaip_sdk/client/mcps.py +143 -18
- glaip_sdk/client/tools.py +327 -104
- glaip_sdk/config/constants.py +10 -1
- glaip_sdk/models.py +43 -3
- glaip_sdk/rich_components.py +29 -0
- glaip_sdk/utils/__init__.py +18 -171
- glaip_sdk/utils/agent_config.py +181 -0
- glaip_sdk/utils/client_utils.py +159 -79
- glaip_sdk/utils/display.py +100 -0
- glaip_sdk/utils/general.py +94 -0
- glaip_sdk/utils/import_export.py +140 -0
- glaip_sdk/utils/rendering/formatting.py +6 -1
- glaip_sdk/utils/rendering/renderer/__init__.py +67 -8
- glaip_sdk/utils/rendering/renderer/base.py +340 -247
- glaip_sdk/utils/rendering/renderer/debug.py +3 -2
- glaip_sdk/utils/rendering/renderer/panels.py +11 -10
- glaip_sdk/utils/rendering/steps.py +1 -1
- glaip_sdk/utils/resource_refs.py +192 -0
- glaip_sdk/utils/rich_utils.py +29 -0
- glaip_sdk/utils/serialization.py +285 -0
- glaip_sdk/utils/validation.py +273 -0
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.5.dist-info}/METADATA +6 -5
- glaip_sdk-0.0.5.dist-info/RECORD +55 -0
- glaip_sdk/cli/commands/init.py +0 -177
- glaip_sdk-0.0.3.dist-info/RECORD +0 -40
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.5.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.5.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/tools.py
CHANGED
|
@@ -8,14 +8,25 @@ Authors:
|
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
10
|
import tempfile
|
|
11
|
+
from typing import Any
|
|
11
12
|
|
|
12
13
|
from glaip_sdk.client.base import BaseClient
|
|
14
|
+
from glaip_sdk.config.constants import (
|
|
15
|
+
DEFAULT_TOOL_FRAMEWORK,
|
|
16
|
+
DEFAULT_TOOL_TYPE,
|
|
17
|
+
DEFAULT_TOOL_VERSION,
|
|
18
|
+
)
|
|
13
19
|
from glaip_sdk.models import Tool
|
|
14
20
|
from glaip_sdk.utils.client_utils import (
|
|
15
21
|
create_model_instances,
|
|
16
22
|
find_by_name,
|
|
17
23
|
)
|
|
18
24
|
|
|
25
|
+
# API endpoints
|
|
26
|
+
TOOLS_ENDPOINT = "/tools/"
|
|
27
|
+
TOOLS_UPLOAD_ENDPOINT = "/tools/upload"
|
|
28
|
+
TOOLS_UPLOAD_BY_ID_ENDPOINT_FMT = "/tools/{tool_id}/upload"
|
|
29
|
+
|
|
19
30
|
# Set up module-level logger
|
|
20
31
|
logger = logging.getLogger("glaip_sdk.tools")
|
|
21
32
|
|
|
@@ -32,84 +43,320 @@ class ToolClient(BaseClient):
|
|
|
32
43
|
"""
|
|
33
44
|
super().__init__(parent_client=parent_client, **kwargs)
|
|
34
45
|
|
|
35
|
-
def list_tools(self) -> list[Tool]:
|
|
36
|
-
"""List all tools.
|
|
37
|
-
|
|
46
|
+
def list_tools(self, tool_type: str | None = None) -> list[Tool]:
|
|
47
|
+
"""List all tools, optionally filtered by type.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
tool_type: Filter tools by type (e.g., "custom", "native")
|
|
51
|
+
"""
|
|
52
|
+
endpoint = TOOLS_ENDPOINT
|
|
53
|
+
if tool_type:
|
|
54
|
+
endpoint += f"?type={tool_type}"
|
|
55
|
+
data = self._request("GET", endpoint)
|
|
38
56
|
return create_model_instances(data, Tool, self)
|
|
39
57
|
|
|
40
58
|
def get_tool_by_id(self, tool_id: str) -> Tool:
|
|
41
59
|
"""Get tool by ID."""
|
|
42
|
-
data = self._request("GET", f"
|
|
60
|
+
data = self._request("GET", f"{TOOLS_ENDPOINT}{tool_id}")
|
|
43
61
|
return Tool(**data)._set_client(self)
|
|
44
62
|
|
|
45
63
|
def find_tools(self, name: str | None = None) -> list[Tool]:
|
|
46
64
|
"""Find tools by name."""
|
|
47
65
|
# Backend doesn't support name query parameter, so we fetch all and filter client-side
|
|
48
|
-
data = self._request("GET",
|
|
66
|
+
data = self._request("GET", TOOLS_ENDPOINT)
|
|
49
67
|
tools = create_model_instances(data, Tool, self)
|
|
50
68
|
return find_by_name(tools, name, case_sensitive=False)
|
|
51
69
|
|
|
52
|
-
def
|
|
70
|
+
def _validate_and_read_file(self, file_path: str) -> str:
|
|
71
|
+
"""Validate file exists and read its content.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
file_path: Path to the file to read
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
str: File content
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
FileNotFoundError: If file doesn't exist
|
|
81
|
+
"""
|
|
82
|
+
if not os.path.exists(file_path):
|
|
83
|
+
raise FileNotFoundError(f"Tool file not found: {file_path}")
|
|
84
|
+
|
|
85
|
+
with open(file_path, encoding="utf-8") as f:
|
|
86
|
+
return f.read()
|
|
87
|
+
|
|
88
|
+
def _extract_name_from_file(self, file_path: str) -> str:
|
|
89
|
+
"""Extract tool name from file path.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
file_path: Path to the file
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
str: Extracted name (filename without extension)
|
|
96
|
+
"""
|
|
97
|
+
return os.path.splitext(os.path.basename(file_path))[0]
|
|
98
|
+
|
|
99
|
+
def _prepare_upload_data(
|
|
100
|
+
self, name: str, framework: str, description: str | None = None, **kwargs
|
|
101
|
+
) -> dict:
|
|
102
|
+
"""Prepare upload data dictionary.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
name: Tool name
|
|
106
|
+
framework: Tool framework
|
|
107
|
+
description: Optional description
|
|
108
|
+
**kwargs: Additional parameters
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
dict: Upload data dictionary
|
|
112
|
+
"""
|
|
113
|
+
data = {
|
|
114
|
+
"name": name,
|
|
115
|
+
"framework": framework,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if description:
|
|
119
|
+
data["description"] = description
|
|
120
|
+
|
|
121
|
+
# Handle tags if provided in kwargs
|
|
122
|
+
if kwargs.get("tags"):
|
|
123
|
+
if isinstance(kwargs["tags"], list):
|
|
124
|
+
data["tags"] = ",".join(kwargs["tags"])
|
|
125
|
+
else:
|
|
126
|
+
data["tags"] = kwargs["tags"]
|
|
127
|
+
|
|
128
|
+
# Include any other kwargs in the upload data
|
|
129
|
+
for key, value in kwargs.items():
|
|
130
|
+
if key not in ["tags"]: # tags already handled above
|
|
131
|
+
data[key] = value
|
|
132
|
+
|
|
133
|
+
return data
|
|
134
|
+
|
|
135
|
+
def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
|
|
136
|
+
"""Upload tool file to server.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
file_path: Path to temporary file to upload
|
|
140
|
+
upload_data: Dictionary with upload metadata
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Tool: Created tool object
|
|
144
|
+
"""
|
|
145
|
+
with open(file_path, "rb") as fb:
|
|
146
|
+
files = {
|
|
147
|
+
"file": (os.path.basename(file_path), fb, "application/octet-stream"),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
response = self._request(
|
|
151
|
+
"POST",
|
|
152
|
+
TOOLS_UPLOAD_ENDPOINT,
|
|
153
|
+
files=files,
|
|
154
|
+
data=upload_data,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return Tool(**response)._set_client(self)
|
|
158
|
+
|
|
159
|
+
def _build_create_payload(
|
|
160
|
+
self,
|
|
161
|
+
name: str,
|
|
162
|
+
description: str | None = None,
|
|
163
|
+
framework: str = DEFAULT_TOOL_FRAMEWORK,
|
|
164
|
+
tool_type: str = DEFAULT_TOOL_TYPE,
|
|
165
|
+
**kwargs,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""Build payload for tool creation with proper metadata handling.
|
|
168
|
+
|
|
169
|
+
CENTRALIZED PAYLOAD BUILDING LOGIC:
|
|
170
|
+
- Handles file vs metadata-only tool creation
|
|
171
|
+
- Sets proper defaults and required fields
|
|
172
|
+
- Processes tags and other metadata consistently
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
name: Tool name
|
|
176
|
+
description: Tool description
|
|
177
|
+
framework: Tool framework (defaults to langchain)
|
|
178
|
+
tool_type: Tool type (defaults to custom)
|
|
179
|
+
**kwargs: Additional parameters (tags, version, etc.)
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Complete payload dictionary for tool creation
|
|
183
|
+
"""
|
|
184
|
+
# Prepare the creation payload with required fields
|
|
185
|
+
payload: dict[str, any] = {
|
|
186
|
+
"name": name.strip(),
|
|
187
|
+
"type": tool_type,
|
|
188
|
+
"framework": framework,
|
|
189
|
+
"version": kwargs.get("version", DEFAULT_TOOL_VERSION),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Add description if provided
|
|
193
|
+
if description:
|
|
194
|
+
payload["description"] = description.strip()
|
|
195
|
+
|
|
196
|
+
# Handle tags - convert list to comma-separated string for API
|
|
197
|
+
if kwargs.get("tags"):
|
|
198
|
+
if isinstance(kwargs["tags"], list):
|
|
199
|
+
payload["tags"] = ",".join(str(tag).strip() for tag in kwargs["tags"])
|
|
200
|
+
else:
|
|
201
|
+
payload["tags"] = str(kwargs["tags"])
|
|
202
|
+
|
|
203
|
+
# Add any other kwargs (excluding already handled ones)
|
|
204
|
+
excluded_keys = {"tags", "version"}
|
|
205
|
+
for key, value in kwargs.items():
|
|
206
|
+
if key not in excluded_keys:
|
|
207
|
+
payload[key] = value
|
|
208
|
+
|
|
209
|
+
return payload
|
|
210
|
+
|
|
211
|
+
def _build_update_payload(
|
|
53
212
|
self,
|
|
213
|
+
current_tool: Tool,
|
|
214
|
+
name: str | None = None,
|
|
215
|
+
description: str | None = None,
|
|
216
|
+
**kwargs,
|
|
217
|
+
) -> dict[str, Any]:
|
|
218
|
+
"""Build payload for tool update with proper current state preservation.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
current_tool: Current tool object to update
|
|
222
|
+
name: New tool name (None to keep current)
|
|
223
|
+
description: New description (None to keep current)
|
|
224
|
+
**kwargs: Additional parameters (tags, framework, etc.)
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Complete payload dictionary for tool update
|
|
228
|
+
|
|
229
|
+
Notes:
|
|
230
|
+
- Preserves current values as defaults when new values not provided
|
|
231
|
+
- Handles metadata updates properly
|
|
232
|
+
"""
|
|
233
|
+
# Prepare the update payload with current values as defaults
|
|
234
|
+
update_data = {
|
|
235
|
+
"name": name if name is not None else current_tool.name,
|
|
236
|
+
"type": DEFAULT_TOOL_TYPE, # Required by backend
|
|
237
|
+
"framework": kwargs.get(
|
|
238
|
+
"framework", getattr(current_tool, "framework", DEFAULT_TOOL_FRAMEWORK)
|
|
239
|
+
),
|
|
240
|
+
"version": kwargs.get(
|
|
241
|
+
"version", getattr(current_tool, "version", DEFAULT_TOOL_VERSION)
|
|
242
|
+
),
|
|
243
|
+
}
|
|
244
|
+
|
|
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
|
|
250
|
+
|
|
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)
|
|
267
|
+
|
|
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
|
|
273
|
+
|
|
274
|
+
return update_data
|
|
275
|
+
|
|
276
|
+
def _create_tool_from_file(
|
|
277
|
+
self,
|
|
278
|
+
file_path: str,
|
|
54
279
|
name: str | None = None,
|
|
55
|
-
tool_type: str = "custom",
|
|
56
280
|
description: str | None = None,
|
|
57
|
-
tool_script: str | None = None,
|
|
58
|
-
tool_file: str | None = None,
|
|
59
|
-
file_path: str | None = None,
|
|
60
|
-
code: str | None = None,
|
|
61
281
|
framework: str = "langchain",
|
|
62
282
|
**kwargs,
|
|
63
283
|
) -> Tool:
|
|
64
|
-
"""Create
|
|
284
|
+
"""Create tool from file content using upload endpoint.
|
|
65
285
|
|
|
66
286
|
Args:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
description:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
**kwargs: Additional tool parameters
|
|
287
|
+
file_path: Path to tool file
|
|
288
|
+
name: Optional tool name (auto-detected if not provided)
|
|
289
|
+
description: Optional tool description
|
|
290
|
+
framework: Tool framework
|
|
291
|
+
**kwargs: Additional parameters
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Tool: Created tool object
|
|
76
295
|
"""
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
tool_file = file_path
|
|
80
|
-
if code and not tool_script:
|
|
81
|
-
tool_script = code
|
|
296
|
+
# Read and validate file
|
|
297
|
+
file_content = self._validate_and_read_file(file_path)
|
|
82
298
|
|
|
83
|
-
# Auto-detect name
|
|
84
|
-
if not name
|
|
85
|
-
|
|
299
|
+
# Auto-detect name if not provided
|
|
300
|
+
if not name:
|
|
301
|
+
name = self._extract_name_from_file(file_path)
|
|
86
302
|
|
|
87
|
-
|
|
303
|
+
# Handle description - generate default if not provided or empty
|
|
304
|
+
if description is None or description == "":
|
|
305
|
+
# Generate default description based on tool_type if available
|
|
306
|
+
tool_type = kwargs.get("tool_type", "custom")
|
|
307
|
+
description = f"A {tool_type} tool"
|
|
88
308
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
309
|
+
# Create temporary file for upload
|
|
310
|
+
with tempfile.NamedTemporaryFile(
|
|
311
|
+
mode="w",
|
|
312
|
+
suffix=".py",
|
|
313
|
+
prefix=f"{name}_",
|
|
314
|
+
delete=False,
|
|
315
|
+
encoding="utf-8",
|
|
316
|
+
) as temp_file:
|
|
317
|
+
temp_file.write(file_content)
|
|
318
|
+
temp_file_path = temp_file.name
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
# Prepare upload data
|
|
322
|
+
upload_data = self._prepare_upload_data(
|
|
323
|
+
name=name, framework=framework, description=description, **kwargs
|
|
92
324
|
)
|
|
93
325
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
description = f"A {tool_type} tool"
|
|
326
|
+
# Upload file
|
|
327
|
+
return self._upload_tool_file(temp_file_path, upload_data)
|
|
97
328
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
329
|
+
finally:
|
|
330
|
+
# Clean up temporary file
|
|
331
|
+
try:
|
|
332
|
+
os.unlink(temp_file_path)
|
|
333
|
+
except OSError:
|
|
334
|
+
pass # Ignore cleanup errors
|
|
105
335
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
336
|
+
def create_tool(
|
|
337
|
+
self,
|
|
338
|
+
file_path: str,
|
|
339
|
+
name: str | None = None,
|
|
340
|
+
description: str | None = None,
|
|
341
|
+
framework: str = "langchain",
|
|
342
|
+
**kwargs,
|
|
343
|
+
) -> Tool:
|
|
344
|
+
"""Create a new tool from a file.
|
|
110
345
|
|
|
111
|
-
|
|
112
|
-
|
|
346
|
+
Args:
|
|
347
|
+
file_path: File path to tool script (required) - file content will be read and processed as plugin
|
|
348
|
+
name: Tool name (auto-detected from file if not provided)
|
|
349
|
+
description: Tool description (auto-generated if not provided)
|
|
350
|
+
framework: Tool framework (defaults to "langchain")
|
|
351
|
+
**kwargs: Additional tool parameters
|
|
352
|
+
"""
|
|
353
|
+
return self._create_tool_from_file(
|
|
354
|
+
file_path=file_path,
|
|
355
|
+
name=name,
|
|
356
|
+
description=description,
|
|
357
|
+
framework=framework,
|
|
358
|
+
**kwargs,
|
|
359
|
+
)
|
|
113
360
|
|
|
114
361
|
def create_tool_from_code(
|
|
115
362
|
self,
|
|
@@ -129,41 +376,35 @@ class ToolClient(BaseClient):
|
|
|
129
376
|
name: Name for the tool (used for temporary file naming)
|
|
130
377
|
code: Python code containing the tool plugin
|
|
131
378
|
framework: Tool framework (defaults to "langchain")
|
|
379
|
+
description: Optional tool description
|
|
380
|
+
tags: Optional list of tags
|
|
132
381
|
|
|
133
382
|
Returns:
|
|
134
383
|
Tool: The created tool object
|
|
135
384
|
"""
|
|
136
385
|
# Create a temporary file with the tool code
|
|
137
386
|
with tempfile.NamedTemporaryFile(
|
|
138
|
-
mode="w",
|
|
387
|
+
mode="w",
|
|
388
|
+
suffix=".py",
|
|
389
|
+
prefix=f"{name}_",
|
|
390
|
+
delete=False,
|
|
391
|
+
encoding="utf-8",
|
|
139
392
|
) as temp_file:
|
|
140
393
|
temp_file.write(code)
|
|
141
394
|
temp_file_path = temp_file.name
|
|
142
395
|
|
|
143
396
|
try:
|
|
144
|
-
# Prepare
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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)
|
|
397
|
+
# Prepare upload data using shared helper
|
|
398
|
+
upload_data = self._prepare_upload_data(
|
|
399
|
+
name=name,
|
|
400
|
+
framework=framework,
|
|
401
|
+
description=description,
|
|
402
|
+
tags=tags if tags else None,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Upload file using shared helper
|
|
406
|
+
return self._upload_tool_file(temp_file_path, upload_data)
|
|
159
407
|
|
|
160
|
-
response = self._request(
|
|
161
|
-
"POST",
|
|
162
|
-
"/tools/upload",
|
|
163
|
-
files=files,
|
|
164
|
-
data=data,
|
|
165
|
-
)
|
|
166
|
-
return Tool(**response)._set_client(self)
|
|
167
408
|
finally:
|
|
168
409
|
# Clean up the temporary file
|
|
169
410
|
try:
|
|
@@ -173,30 +414,12 @@ class ToolClient(BaseClient):
|
|
|
173
414
|
|
|
174
415
|
def update_tool(self, tool_id: str, **kwargs) -> Tool:
|
|
175
416
|
"""Update an existing tool."""
|
|
176
|
-
data = self._request("PUT", f"
|
|
417
|
+
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id}", json=kwargs)
|
|
177
418
|
return Tool(**data)._set_client(self)
|
|
178
419
|
|
|
179
420
|
def delete_tool(self, tool_id: str) -> None:
|
|
180
421
|
"""Delete a tool."""
|
|
181
|
-
self._request("DELETE", f"
|
|
182
|
-
|
|
183
|
-
def install_tool(self, tool_id: str) -> bool:
|
|
184
|
-
"""Install a tool."""
|
|
185
|
-
try:
|
|
186
|
-
self._request("POST", f"/tools/{tool_id}/install")
|
|
187
|
-
return True
|
|
188
|
-
except Exception as e:
|
|
189
|
-
logger.error(f"Failed to install tool {tool_id}: {e}")
|
|
190
|
-
return False
|
|
191
|
-
|
|
192
|
-
def uninstall_tool(self, tool_id: str) -> bool:
|
|
193
|
-
"""Uninstall a tool."""
|
|
194
|
-
try:
|
|
195
|
-
self._request("POST", f"/tools/{tool_id}/uninstall")
|
|
196
|
-
return True
|
|
197
|
-
except Exception as e:
|
|
198
|
-
logger.error(f"Failed to install tool {tool_id}: {e}")
|
|
199
|
-
return False
|
|
422
|
+
self._request("DELETE", f"{TOOLS_ENDPOINT}{tool_id}")
|
|
200
423
|
|
|
201
424
|
def get_tool_script(self, tool_id: str) -> str:
|
|
202
425
|
"""Get the tool script content.
|
|
@@ -211,7 +434,7 @@ class ToolClient(BaseClient):
|
|
|
211
434
|
Exception: If the tool script cannot be retrieved
|
|
212
435
|
"""
|
|
213
436
|
try:
|
|
214
|
-
response = self._request("GET", f"
|
|
437
|
+
response = self._request("GET", f"{TOOLS_ENDPOINT}{tool_id}/script")
|
|
215
438
|
return response.get("script", "") or response.get("content", "")
|
|
216
439
|
except Exception as e:
|
|
217
440
|
logger.error(f"Failed to get tool script for {tool_id}: {e}")
|
|
@@ -232,29 +455,29 @@ class ToolClient(BaseClient):
|
|
|
232
455
|
FileNotFoundError: If the file doesn't exist
|
|
233
456
|
Exception: If the update fails
|
|
234
457
|
"""
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if not os.path.exists(file_path):
|
|
238
|
-
raise FileNotFoundError(f"Tool file not found: {file_path}")
|
|
458
|
+
# Validate file exists
|
|
459
|
+
self._validate_and_read_file(file_path)
|
|
239
460
|
|
|
240
461
|
try:
|
|
241
462
|
# Prepare multipart upload
|
|
242
|
-
filename = os.path.basename(file_path)
|
|
243
463
|
with open(file_path, "rb") as fb:
|
|
244
464
|
files = {
|
|
245
|
-
"file": (
|
|
465
|
+
"file": (
|
|
466
|
+
os.path.basename(file_path),
|
|
467
|
+
fb,
|
|
468
|
+
"application/octet-stream",
|
|
469
|
+
),
|
|
246
470
|
}
|
|
247
471
|
|
|
248
|
-
# Add any additional metadata
|
|
249
|
-
data = kwargs.copy()
|
|
250
|
-
|
|
251
472
|
response = self._request(
|
|
252
473
|
"PUT",
|
|
253
|
-
|
|
474
|
+
TOOLS_UPLOAD_BY_ID_ENDPOINT_FMT.format(tool_id=tool_id),
|
|
254
475
|
files=files,
|
|
255
|
-
data=data
|
|
476
|
+
data=kwargs, # Pass kwargs directly as data
|
|
256
477
|
)
|
|
478
|
+
|
|
257
479
|
return Tool(**response)._set_client(self)
|
|
480
|
+
|
|
258
481
|
except Exception as e:
|
|
259
482
|
logger.error(f"Failed to update tool {tool_id} via file: {e}")
|
|
260
483
|
raise
|
glaip_sdk/config/constants.py
CHANGED
|
@@ -10,7 +10,7 @@ DEFAULT_MODEL_PROVIDER = "openai"
|
|
|
10
10
|
|
|
11
11
|
# Default timeout values
|
|
12
12
|
DEFAULT_TIMEOUT = 30.0
|
|
13
|
-
DEFAULT_AGENT_RUN_TIMEOUT = 300
|
|
13
|
+
DEFAULT_AGENT_RUN_TIMEOUT = 300
|
|
14
14
|
|
|
15
15
|
# User agent and version
|
|
16
16
|
|
|
@@ -33,3 +33,12 @@ DEFAULT_AGENT_TYPE = "config"
|
|
|
33
33
|
DEFAULT_AGENT_FRAMEWORK = "langchain"
|
|
34
34
|
DEFAULT_AGENT_VERSION = "1.0"
|
|
35
35
|
DEFAULT_AGENT_PROVIDER = "openai"
|
|
36
|
+
|
|
37
|
+
# Tool creation/update constants
|
|
38
|
+
DEFAULT_TOOL_TYPE = "custom"
|
|
39
|
+
DEFAULT_TOOL_FRAMEWORK = "langchain"
|
|
40
|
+
DEFAULT_TOOL_VERSION = "1.0"
|
|
41
|
+
|
|
42
|
+
# MCP creation/update constants
|
|
43
|
+
DEFAULT_MCP_TYPE = "server"
|
|
44
|
+
DEFAULT_MCP_TRANSPORT = "stdio"
|
glaip_sdk/models.py
CHANGED
|
@@ -5,6 +5,7 @@ Authors:
|
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from collections.abc import AsyncGenerator
|
|
8
9
|
from datetime import datetime
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
@@ -60,6 +61,31 @@ class Agent(BaseModel):
|
|
|
60
61
|
# Pass verbose flag through to enable event JSON output
|
|
61
62
|
return self._client.run_agent(self.id, message, verbose=verbose, **kwargs)
|
|
62
63
|
|
|
64
|
+
async def arun(self, message: str, **kwargs) -> AsyncGenerator[dict, None]:
|
|
65
|
+
"""Async run the agent with a message, yielding streaming JSON chunks.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
message: The message to send to the agent
|
|
69
|
+
**kwargs: Additional arguments passed to arun_agent
|
|
70
|
+
|
|
71
|
+
Yields:
|
|
72
|
+
Dictionary containing parsed JSON chunks from the streaming response
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
RuntimeError: When no client is available
|
|
76
|
+
AgentTimeoutError: When agent execution times out
|
|
77
|
+
Exception: For other unexpected errors
|
|
78
|
+
"""
|
|
79
|
+
if not self._client:
|
|
80
|
+
raise RuntimeError(
|
|
81
|
+
"No client available. Use client.get_agent_by_id() to get a client-connected agent."
|
|
82
|
+
)
|
|
83
|
+
# Automatically pass the agent name for better context
|
|
84
|
+
kwargs.setdefault("agent_name", self.name)
|
|
85
|
+
|
|
86
|
+
async for chunk in self._client.arun_agent(self.id, message, **kwargs):
|
|
87
|
+
yield chunk
|
|
88
|
+
|
|
63
89
|
def update(self, **kwargs) -> "Agent":
|
|
64
90
|
"""Update agent attributes."""
|
|
65
91
|
if not self._client:
|
|
@@ -110,12 +136,26 @@ class Tool(BaseModel):
|
|
|
110
136
|
return "No script content available"
|
|
111
137
|
|
|
112
138
|
def update(self, **kwargs) -> "Tool":
|
|
113
|
-
"""Update tool attributes.
|
|
139
|
+
"""Update tool attributes.
|
|
140
|
+
|
|
141
|
+
Supports both metadata updates and file uploads.
|
|
142
|
+
Pass 'file' parameter to update tool code via file upload.
|
|
143
|
+
"""
|
|
114
144
|
if not self._client:
|
|
115
145
|
raise RuntimeError(
|
|
116
146
|
"No client available. Use client.get_tool_by_id() to get a client-connected tool."
|
|
117
147
|
)
|
|
118
|
-
|
|
148
|
+
|
|
149
|
+
# Check if file upload is requested
|
|
150
|
+
if "file" in kwargs:
|
|
151
|
+
file_path = kwargs.pop("file") # Remove file from kwargs for metadata
|
|
152
|
+
updated_tool = self._client.tools.update_tool_via_file(
|
|
153
|
+
self.id, file_path, **kwargs
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
# Regular metadata update
|
|
157
|
+
updated_tool = self._client.tools.update_tool(self.id, **kwargs)
|
|
158
|
+
|
|
119
159
|
# Update current instance with new data
|
|
120
160
|
for key, value in updated_tool.model_dump().items():
|
|
121
161
|
if hasattr(self, key):
|
|
@@ -128,7 +168,7 @@ class Tool(BaseModel):
|
|
|
128
168
|
raise RuntimeError(
|
|
129
169
|
"No client available. Use client.get_tool_by_id() to get a client-connected tool."
|
|
130
170
|
)
|
|
131
|
-
self._client.
|
|
171
|
+
self._client.delete_tool(self.id)
|
|
132
172
|
|
|
133
173
|
|
|
134
174
|
class MCP(BaseModel):
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Custom Rich components with copy-friendly defaults."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich import box
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AIPPanel(Panel):
|
|
11
|
+
"""Rich Panel configured without vertical borders by default."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, *args, **kwargs):
|
|
14
|
+
kwargs.setdefault("box", box.HORIZONTALS)
|
|
15
|
+
kwargs.setdefault("padding", (0, 1))
|
|
16
|
+
super().__init__(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AIPTable(Table):
|
|
20
|
+
"""Rich Table configured without vertical borders by default."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, *args, **kwargs):
|
|
23
|
+
kwargs.setdefault("box", box.HORIZONTALS)
|
|
24
|
+
kwargs.setdefault("show_edge", False)
|
|
25
|
+
kwargs.setdefault("pad_edge", False)
|
|
26
|
+
super().__init__(*args, **kwargs)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = ["AIPPanel", "AIPTable"]
|