glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 +6 -3
- glaip_sdk/_version.py +12 -5
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1126 -0
- glaip_sdk/branding.py +79 -15
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +699 -0
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +503 -183
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +774 -137
- glaip_sdk/cli/commands/mcps.py +1124 -181
- glaip_sdk/cli/commands/models.py +25 -10
- glaip_sdk/cli/commands/tools.py +144 -92
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +61 -0
- glaip_sdk/cli/config.py +95 -0
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +150 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +846 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +143 -53
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +24 -18
- glaip_sdk/cli/main.py +420 -145
- glaip_sdk/cli/masking.py +136 -0
- glaip_sdk/cli/mcp_validators.py +287 -0
- glaip_sdk/cli/pager.py +266 -0
- glaip_sdk/cli/parsers/__init__.py +7 -0
- glaip_sdk/cli/parsers/json_input.py +177 -0
- glaip_sdk/cli/resolution.py +28 -21
- glaip_sdk/cli/rich_helpers.py +27 -0
- glaip_sdk/cli/slash/__init__.py +15 -0
- glaip_sdk/cli/slash/accounts_controller.py +500 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +282 -0
- glaip_sdk/cli/slash/prompt.py +245 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +1679 -0
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +872 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +31 -0
- glaip_sdk/cli/transcript/cache.py +536 -0
- glaip_sdk/cli/transcript/capture.py +329 -0
- glaip_sdk/cli/transcript/export.py +38 -0
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +77 -0
- glaip_sdk/cli/transcript/viewer.py +372 -0
- glaip_sdk/cli/update_notifier.py +290 -0
- glaip_sdk/cli/utils.py +247 -1238
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +520 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +940 -574
- glaip_sdk/client/base.py +163 -48
- glaip_sdk/client/main.py +35 -12
- glaip_sdk/client/mcps.py +126 -18
- glaip_sdk/client/run_rendering.py +415 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +195 -37
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +15 -5
- glaip_sdk/exceptions.py +16 -9
- glaip_sdk/icons.py +25 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +7 -0
- glaip_sdk/payload_schemas/agent.py +85 -0
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +231 -0
- glaip_sdk/rich_components.py +98 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +597 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +59 -13
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +53 -40
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +58 -26
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +65 -32
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +20 -25
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +85 -43
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
- glaip_sdk/utils/rendering/layout/progress.py +202 -0
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +39 -7
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +672 -759
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +75 -22
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +13 -54
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +29 -26
- glaip_sdk/utils/runtime_config.py +422 -0
- glaip_sdk/utils/serialization.py +184 -51
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +21 -30
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
- glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -250
- glaip_sdk/utils/rendering/renderer/progress.py +0 -118
- glaip_sdk/utils/rendering/steps.py +0 -232
- glaip_sdk/utils/rich_utils.py +0 -29
- glaip_sdk-0.0.7.dist-info/RECORD +0 -55
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/tools.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import logging
|
|
@@ -16,11 +17,14 @@ from glaip_sdk.config.constants import (
|
|
|
16
17
|
DEFAULT_TOOL_TYPE,
|
|
17
18
|
DEFAULT_TOOL_VERSION,
|
|
18
19
|
)
|
|
19
|
-
from glaip_sdk.models import
|
|
20
|
+
from glaip_sdk.models import ToolResponse
|
|
21
|
+
from glaip_sdk.tools import Tool
|
|
20
22
|
from glaip_sdk.utils.client_utils import (
|
|
23
|
+
add_kwargs_to_payload,
|
|
21
24
|
create_model_instances,
|
|
22
25
|
find_by_name,
|
|
23
26
|
)
|
|
27
|
+
from glaip_sdk.utils.resource_refs import is_uuid
|
|
24
28
|
|
|
25
29
|
# API endpoints
|
|
26
30
|
TOOLS_ENDPOINT = "/tools/"
|
|
@@ -58,11 +62,11 @@ class ToolClient(BaseClient):
|
|
|
58
62
|
def get_tool_by_id(self, tool_id: str) -> Tool:
|
|
59
63
|
"""Get tool by ID."""
|
|
60
64
|
data = self._request("GET", f"{TOOLS_ENDPOINT}{tool_id}")
|
|
61
|
-
|
|
65
|
+
response = ToolResponse(**data)
|
|
66
|
+
return Tool.from_response(response, client=self)
|
|
62
67
|
|
|
63
68
|
def find_tools(self, name: str | None = None) -> list[Tool]:
|
|
64
69
|
"""Find tools by name."""
|
|
65
|
-
# Backend doesn't support name query parameter, so we fetch all and filter client-side
|
|
66
70
|
data = self._request("GET", TOOLS_ENDPOINT)
|
|
67
71
|
tools = create_model_instances(data, Tool, self)
|
|
68
72
|
return find_by_name(tools, name, case_sensitive=False)
|
|
@@ -96,9 +100,7 @@ class ToolClient(BaseClient):
|
|
|
96
100
|
"""
|
|
97
101
|
return os.path.splitext(os.path.basename(file_path))[0]
|
|
98
102
|
|
|
99
|
-
def _prepare_upload_data(
|
|
100
|
-
self, name: str, framework: str, description: str | None = None, **kwargs
|
|
101
|
-
) -> dict:
|
|
103
|
+
def _prepare_upload_data(self, name: str, framework: str, description: str | None = None, **kwargs) -> dict:
|
|
102
104
|
"""Prepare upload data dictionary.
|
|
103
105
|
|
|
104
106
|
Args:
|
|
@@ -113,6 +115,7 @@ class ToolClient(BaseClient):
|
|
|
113
115
|
data = {
|
|
114
116
|
"name": name,
|
|
115
117
|
"framework": framework,
|
|
118
|
+
"type": kwargs.pop("tool_type", DEFAULT_TOOL_TYPE), # Default to custom
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
if description:
|
|
@@ -154,7 +157,8 @@ class ToolClient(BaseClient):
|
|
|
154
157
|
data=upload_data,
|
|
155
158
|
)
|
|
156
159
|
|
|
157
|
-
|
|
160
|
+
tool_response = ToolResponse(**response)
|
|
161
|
+
return Tool.from_response(tool_response, client=self)
|
|
158
162
|
|
|
159
163
|
def _build_create_payload(
|
|
160
164
|
self,
|
|
@@ -202,9 +206,7 @@ class ToolClient(BaseClient):
|
|
|
202
206
|
|
|
203
207
|
# Add any other kwargs (excluding already handled ones)
|
|
204
208
|
excluded_keys = {"tags", "version"}
|
|
205
|
-
|
|
206
|
-
if key not in excluded_keys:
|
|
207
|
-
payload[key] = value
|
|
209
|
+
add_kwargs_to_payload(payload, kwargs, excluded_keys)
|
|
208
210
|
|
|
209
211
|
return payload
|
|
210
212
|
|
|
@@ -217,31 +219,31 @@ class ToolClient(BaseClient):
|
|
|
217
219
|
elif hasattr(current_tool, "description") and current_tool.description:
|
|
218
220
|
update_data["description"] = current_tool.description
|
|
219
221
|
|
|
220
|
-
def _handle_tags_update(
|
|
221
|
-
self, update_data: dict[str, Any], kwargs: dict[str, Any], current_tool: Tool
|
|
222
|
-
) -> None:
|
|
222
|
+
def _handle_tags_update(self, update_data: dict[str, Any], kwargs: dict[str, Any], current_tool: Tool) -> None:
|
|
223
223
|
"""Handle tags field in update payload."""
|
|
224
224
|
if kwargs.get("tags"):
|
|
225
225
|
if isinstance(kwargs["tags"], list):
|
|
226
|
-
update_data["tags"] = ",".join(
|
|
227
|
-
str(tag).strip() for tag in kwargs["tags"]
|
|
228
|
-
)
|
|
226
|
+
update_data["tags"] = ",".join(str(tag).strip() for tag in kwargs["tags"])
|
|
229
227
|
else:
|
|
230
228
|
update_data["tags"] = str(kwargs["tags"])
|
|
231
229
|
elif hasattr(current_tool, "tags") and current_tool.tags:
|
|
232
230
|
# Preserve existing tags if present
|
|
233
231
|
if isinstance(current_tool.tags, list):
|
|
234
|
-
update_data["tags"] = ",".join(
|
|
235
|
-
str(tag).strip() for tag in current_tool.tags
|
|
236
|
-
)
|
|
232
|
+
update_data["tags"] = ",".join(str(tag).strip() for tag in current_tool.tags)
|
|
237
233
|
else:
|
|
238
234
|
update_data["tags"] = str(current_tool.tags)
|
|
239
235
|
|
|
240
|
-
def _handle_additional_kwargs(
|
|
241
|
-
self, update_data: dict[str, Any], kwargs: dict[str, Any]
|
|
242
|
-
) -> None:
|
|
236
|
+
def _handle_additional_kwargs(self, update_data: dict[str, Any], kwargs: dict[str, Any]) -> None:
|
|
243
237
|
"""Handle additional kwargs in update payload."""
|
|
244
|
-
excluded_keys = {
|
|
238
|
+
excluded_keys = {
|
|
239
|
+
"tags",
|
|
240
|
+
"framework",
|
|
241
|
+
"version",
|
|
242
|
+
"type",
|
|
243
|
+
"tool_type",
|
|
244
|
+
"name",
|
|
245
|
+
"description",
|
|
246
|
+
}
|
|
245
247
|
for key, value in kwargs.items():
|
|
246
248
|
if key not in excluded_keys:
|
|
247
249
|
update_data[key] = value
|
|
@@ -269,15 +271,24 @@ class ToolClient(BaseClient):
|
|
|
269
271
|
- Handles metadata updates properly
|
|
270
272
|
"""
|
|
271
273
|
# Prepare the update payload with current values as defaults
|
|
274
|
+
type_override = kwargs.pop("type", None)
|
|
275
|
+
if type_override is None:
|
|
276
|
+
type_override = kwargs.pop("tool_type", None)
|
|
277
|
+
current_type = (
|
|
278
|
+
type_override
|
|
279
|
+
or getattr(current_tool, "tool_type", None)
|
|
280
|
+
or getattr(current_tool, "type", None)
|
|
281
|
+
or DEFAULT_TOOL_TYPE
|
|
282
|
+
)
|
|
283
|
+
# Convert enum to string value for API payload
|
|
284
|
+
if hasattr(current_type, "value"):
|
|
285
|
+
current_type = current_type.value
|
|
286
|
+
|
|
272
287
|
update_data = {
|
|
273
288
|
"name": name if name is not None else current_tool.name,
|
|
274
|
-
"type":
|
|
275
|
-
"framework": kwargs.get(
|
|
276
|
-
|
|
277
|
-
),
|
|
278
|
-
"version": kwargs.get(
|
|
279
|
-
"version", getattr(current_tool, "version", DEFAULT_TOOL_VERSION)
|
|
280
|
-
),
|
|
289
|
+
"type": current_type,
|
|
290
|
+
"framework": kwargs.get("framework", getattr(current_tool, "framework", DEFAULT_TOOL_FRAMEWORK)),
|
|
291
|
+
"version": kwargs.get("version", getattr(current_tool, "version", DEFAULT_TOOL_VERSION)),
|
|
281
292
|
}
|
|
282
293
|
|
|
283
294
|
# Handle description update
|
|
@@ -337,9 +348,7 @@ class ToolClient(BaseClient):
|
|
|
337
348
|
|
|
338
349
|
try:
|
|
339
350
|
# Prepare upload data
|
|
340
|
-
upload_data = self._prepare_upload_data(
|
|
341
|
-
name=name, framework=framework, description=description, **kwargs
|
|
342
|
-
)
|
|
351
|
+
upload_data = self._prepare_upload_data(name=name, framework=framework, description=description, **kwargs)
|
|
343
352
|
|
|
344
353
|
# Upload file
|
|
345
354
|
return self._upload_tool_file(temp_file_path, upload_data)
|
|
@@ -433,12 +442,147 @@ class ToolClient(BaseClient):
|
|
|
433
442
|
def update_tool(self, tool_id: str, **kwargs) -> Tool:
|
|
434
443
|
"""Update an existing tool."""
|
|
435
444
|
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id}", json=kwargs)
|
|
436
|
-
|
|
445
|
+
response = ToolResponse(**data)
|
|
446
|
+
return Tool.from_response(response, client=self)
|
|
437
447
|
|
|
438
448
|
def delete_tool(self, tool_id: str) -> None:
|
|
439
449
|
"""Delete a tool."""
|
|
440
450
|
self._request("DELETE", f"{TOOLS_ENDPOINT}{tool_id}")
|
|
441
451
|
|
|
452
|
+
def upsert_tool(
|
|
453
|
+
self,
|
|
454
|
+
identifier: str | Tool,
|
|
455
|
+
code: str | None = None,
|
|
456
|
+
description: str | None = None,
|
|
457
|
+
framework: str = "langchain",
|
|
458
|
+
**kwargs,
|
|
459
|
+
) -> Tool:
|
|
460
|
+
"""Create or update a tool by instance, ID, or name.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
identifier: Tool instance, ID (UUID string), or name
|
|
464
|
+
code: Python code containing the tool plugin (required for create)
|
|
465
|
+
description: Tool description
|
|
466
|
+
framework: Tool framework (defaults to "langchain")
|
|
467
|
+
**kwargs: Additional parameters (tags, version, etc.)
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
The created or updated tool.
|
|
471
|
+
|
|
472
|
+
Example:
|
|
473
|
+
>>> # By name with code (creates if not exists)
|
|
474
|
+
>>> tool = client.tools.upsert_tool(
|
|
475
|
+
... "greeting",
|
|
476
|
+
... code=bundled_source,
|
|
477
|
+
... description="A greeting tool",
|
|
478
|
+
... )
|
|
479
|
+
>>> # By instance
|
|
480
|
+
>>> tool = client.tools.upsert_tool(existing_tool, code=new_code)
|
|
481
|
+
>>> # By ID
|
|
482
|
+
>>> tool = client.tools.upsert_tool("uuid-here", code=new_code)
|
|
483
|
+
"""
|
|
484
|
+
# Handle Tool instance
|
|
485
|
+
if isinstance(identifier, Tool):
|
|
486
|
+
if identifier.id:
|
|
487
|
+
logger.info("Updating tool by instance: %s", identifier.name)
|
|
488
|
+
return self._do_tool_upsert_update(
|
|
489
|
+
identifier.id,
|
|
490
|
+
identifier.name,
|
|
491
|
+
code,
|
|
492
|
+
description,
|
|
493
|
+
framework,
|
|
494
|
+
**kwargs,
|
|
495
|
+
)
|
|
496
|
+
identifier = identifier.name
|
|
497
|
+
|
|
498
|
+
# Handle string (ID or name)
|
|
499
|
+
if isinstance(identifier, str):
|
|
500
|
+
if is_uuid(identifier):
|
|
501
|
+
logger.info("Updating tool by ID: %s", identifier)
|
|
502
|
+
existing = self.get_tool_by_id(identifier)
|
|
503
|
+
return self._do_tool_upsert_update(identifier, existing.name, code, description, framework, **kwargs)
|
|
504
|
+
|
|
505
|
+
# It's a name - find or create
|
|
506
|
+
return self._upsert_tool_by_name(identifier, code, description, framework, **kwargs)
|
|
507
|
+
|
|
508
|
+
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
509
|
+
|
|
510
|
+
def _do_tool_upsert_update(
|
|
511
|
+
self,
|
|
512
|
+
tool_id: str,
|
|
513
|
+
name: str | None,
|
|
514
|
+
code: str | None,
|
|
515
|
+
description: str | None,
|
|
516
|
+
framework: str,
|
|
517
|
+
**kwargs,
|
|
518
|
+
) -> Tool:
|
|
519
|
+
"""Perform the update part of tool upsert."""
|
|
520
|
+
if code:
|
|
521
|
+
# Update via file upload
|
|
522
|
+
with tempfile.NamedTemporaryFile(
|
|
523
|
+
mode="w",
|
|
524
|
+
suffix=".py",
|
|
525
|
+
prefix=f"{name or 'tool'}_",
|
|
526
|
+
delete=False,
|
|
527
|
+
encoding="utf-8",
|
|
528
|
+
) as temp_file:
|
|
529
|
+
temp_file.write(code)
|
|
530
|
+
temp_file_path = temp_file.name
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
return self.update_tool_via_file(
|
|
534
|
+
tool_id,
|
|
535
|
+
temp_file_path,
|
|
536
|
+
name=name,
|
|
537
|
+
description=description,
|
|
538
|
+
framework=framework,
|
|
539
|
+
**kwargs,
|
|
540
|
+
)
|
|
541
|
+
finally:
|
|
542
|
+
try:
|
|
543
|
+
os.unlink(temp_file_path)
|
|
544
|
+
except OSError:
|
|
545
|
+
pass
|
|
546
|
+
else:
|
|
547
|
+
# Metadata-only update
|
|
548
|
+
update_kwargs = {"framework": framework, **kwargs}
|
|
549
|
+
if name:
|
|
550
|
+
update_kwargs["name"] = name
|
|
551
|
+
if description:
|
|
552
|
+
update_kwargs["description"] = description
|
|
553
|
+
return self.update_tool(tool_id, **update_kwargs)
|
|
554
|
+
|
|
555
|
+
def _upsert_tool_by_name(
|
|
556
|
+
self,
|
|
557
|
+
name: str,
|
|
558
|
+
code: str | None,
|
|
559
|
+
description: str | None,
|
|
560
|
+
framework: str,
|
|
561
|
+
**kwargs,
|
|
562
|
+
) -> Tool:
|
|
563
|
+
"""Find tool by name and update, or create if not found."""
|
|
564
|
+
existing = self.find_tools(name)
|
|
565
|
+
|
|
566
|
+
if len(existing) == 1:
|
|
567
|
+
logger.info("Updating existing tool: %s", name)
|
|
568
|
+
return self._do_tool_upsert_update(existing[0].id, name, code, description, framework, **kwargs)
|
|
569
|
+
|
|
570
|
+
if len(existing) > 1:
|
|
571
|
+
raise ValueError(f"Multiple tools found with name '{name}'")
|
|
572
|
+
|
|
573
|
+
# Create new tool - code is required
|
|
574
|
+
if not code:
|
|
575
|
+
raise ValueError(f"Tool '{name}' not found and no code provided for creation")
|
|
576
|
+
|
|
577
|
+
logger.info("Creating new tool: %s", name)
|
|
578
|
+
return self.create_tool_from_code(
|
|
579
|
+
name=name,
|
|
580
|
+
code=code,
|
|
581
|
+
framework=framework,
|
|
582
|
+
description=description,
|
|
583
|
+
**kwargs,
|
|
584
|
+
)
|
|
585
|
+
|
|
442
586
|
def get_tool_script(self, tool_id: str) -> str:
|
|
443
587
|
"""Get the tool script content.
|
|
444
588
|
|
|
@@ -476,6 +620,19 @@ class ToolClient(BaseClient):
|
|
|
476
620
|
# Validate file exists
|
|
477
621
|
self._validate_and_read_file(file_path)
|
|
478
622
|
|
|
623
|
+
# Fetch current metadata to ensure required fields are preserved
|
|
624
|
+
current_tool = self.get_tool_by_id(tool_id)
|
|
625
|
+
|
|
626
|
+
payload_kwargs = kwargs.copy()
|
|
627
|
+
name = payload_kwargs.pop("name", None)
|
|
628
|
+
description = payload_kwargs.pop("description", None)
|
|
629
|
+
update_payload = self._build_update_payload(
|
|
630
|
+
current_tool=current_tool,
|
|
631
|
+
name=name,
|
|
632
|
+
description=description,
|
|
633
|
+
**payload_kwargs,
|
|
634
|
+
)
|
|
635
|
+
|
|
479
636
|
try:
|
|
480
637
|
# Prepare multipart upload
|
|
481
638
|
with open(file_path, "rb") as fb:
|
|
@@ -491,11 +648,12 @@ class ToolClient(BaseClient):
|
|
|
491
648
|
"PUT",
|
|
492
649
|
TOOLS_UPLOAD_BY_ID_ENDPOINT_FMT.format(tool_id=tool_id),
|
|
493
650
|
files=files,
|
|
494
|
-
data=
|
|
651
|
+
data=update_payload,
|
|
495
652
|
)
|
|
496
653
|
|
|
497
|
-
|
|
654
|
+
tool_response = ToolResponse(**response)
|
|
655
|
+
return Tool.from_response(tool_response, client=self)
|
|
498
656
|
|
|
499
657
|
except Exception as e:
|
|
500
|
-
logger.error(
|
|
658
|
+
logger.error("Failed to update tool %s via file: %s", tool_id, e)
|
|
501
659
|
raise
|
glaip_sdk/client/validators.py
CHANGED
|
@@ -39,9 +39,7 @@ class ResourceValidator:
|
|
|
39
39
|
if len(found_tools) == 1:
|
|
40
40
|
return str(found_tools[0].id)
|
|
41
41
|
elif len(found_tools) > 1:
|
|
42
|
-
raise AmbiguousResourceError(
|
|
43
|
-
f"Multiple tools found with name '{tool_name}': {[t.id for t in found_tools]}"
|
|
44
|
-
)
|
|
42
|
+
raise AmbiguousResourceError(f"Multiple tools found with name '{tool_name}': {[t.id for t in found_tools]}")
|
|
45
43
|
else:
|
|
46
44
|
raise NotFoundError(f"Tool not found: {tool_name}")
|
|
47
45
|
|
|
@@ -51,9 +49,7 @@ class ResourceValidator:
|
|
|
51
49
|
if len(found_tools) == 1:
|
|
52
50
|
return str(found_tools[0].id)
|
|
53
51
|
elif len(found_tools) > 1:
|
|
54
|
-
raise AmbiguousResourceError(
|
|
55
|
-
f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}"
|
|
56
|
-
)
|
|
52
|
+
raise AmbiguousResourceError(f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}")
|
|
57
53
|
else:
|
|
58
54
|
raise NotFoundError(f"Tool not found: {tool.name}")
|
|
59
55
|
|
|
@@ -73,9 +69,7 @@ class ResourceValidator:
|
|
|
73
69
|
elif hasattr(tool, "name") and tool.name is not None:
|
|
74
70
|
return self._resolve_tool_by_name_attribute(tool, client)
|
|
75
71
|
else:
|
|
76
|
-
raise ValidationError(
|
|
77
|
-
f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute"
|
|
78
|
-
)
|
|
72
|
+
raise ValidationError(f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute")
|
|
79
73
|
|
|
80
74
|
def _process_single_tool(self, tool: str | Tool, client: Any) -> str:
|
|
81
75
|
"""Process a single tool reference and return its ID."""
|
|
@@ -99,22 +93,14 @@ class ResourceValidator:
|
|
|
99
93
|
try:
|
|
100
94
|
tool_id = cls()._process_single_tool(tool, client)
|
|
101
95
|
tool_ids.append(tool_id)
|
|
102
|
-
except (AmbiguousResourceError, NotFoundError) as
|
|
96
|
+
except (AmbiguousResourceError, NotFoundError) as err:
|
|
103
97
|
# Determine the tool name for the error message
|
|
104
|
-
tool_name = (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
raise ValidationError(
|
|
108
|
-
f"Failed to resolve tool name '{tool_name}' to ID: {e}"
|
|
109
|
-
)
|
|
110
|
-
except Exception as e:
|
|
98
|
+
tool_name = tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
|
|
99
|
+
raise ValidationError(f"Failed to resolve tool name '{tool_name}' to ID: {err}") from err
|
|
100
|
+
except Exception as err:
|
|
111
101
|
# For other exceptions, wrap them appropriately
|
|
112
|
-
tool_name = (
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
raise ValidationError(
|
|
116
|
-
f"Failed to resolve tool name '{tool_name}' to ID: {e}"
|
|
117
|
-
)
|
|
102
|
+
tool_name = tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
|
|
103
|
+
raise ValidationError(f"Failed to resolve tool name '{tool_name}' to ID: {err}") from err
|
|
118
104
|
|
|
119
105
|
return tool_ids
|
|
120
106
|
|
|
@@ -158,9 +144,7 @@ class ResourceValidator:
|
|
|
158
144
|
elif hasattr(agent, "name") and agent.name is not None:
|
|
159
145
|
return self._resolve_agent_by_name_attribute(agent, client)
|
|
160
146
|
else:
|
|
161
|
-
raise ValidationError(
|
|
162
|
-
f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute"
|
|
163
|
-
)
|
|
147
|
+
raise ValidationError(f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute")
|
|
164
148
|
|
|
165
149
|
def _process_single_agent(self, agent: str | Any, client: Any) -> str:
|
|
166
150
|
"""Process a single agent reference and return its ID."""
|
|
@@ -184,26 +168,14 @@ class ResourceValidator:
|
|
|
184
168
|
try:
|
|
185
169
|
agent_id = cls()._process_single_agent(agent, client)
|
|
186
170
|
agent_ids.append(agent_id)
|
|
187
|
-
except (AmbiguousResourceError, NotFoundError) as
|
|
171
|
+
except (AmbiguousResourceError, NotFoundError) as err:
|
|
188
172
|
# Determine the agent name for the error message
|
|
189
|
-
agent_name = (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
else getattr(agent, "name", str(agent))
|
|
193
|
-
)
|
|
194
|
-
raise ValidationError(
|
|
195
|
-
f"Failed to resolve agent name '{agent_name}' to ID: {e}"
|
|
196
|
-
)
|
|
197
|
-
except Exception as e:
|
|
173
|
+
agent_name = agent if isinstance(agent, str) else getattr(agent, "name", str(agent))
|
|
174
|
+
raise ValidationError(f"Failed to resolve agent name '{agent_name}' to ID: {err}") from err
|
|
175
|
+
except Exception as err:
|
|
198
176
|
# For other exceptions, wrap them appropriately
|
|
199
|
-
agent_name = (
|
|
200
|
-
|
|
201
|
-
if isinstance(agent, str)
|
|
202
|
-
else getattr(agent, "name", str(agent))
|
|
203
|
-
)
|
|
204
|
-
raise ValidationError(
|
|
205
|
-
f"Failed to resolve agent name '{agent_name}' to ID: {e}"
|
|
206
|
-
)
|
|
177
|
+
agent_name = agent if isinstance(agent, str) else getattr(agent, "name", str(agent))
|
|
178
|
+
raise ValidationError(f"Failed to resolve agent name '{agent_name}' to ID: {err}") from err
|
|
207
179
|
|
|
208
180
|
return agent_ids
|
|
209
181
|
|
|
@@ -213,8 +185,8 @@ class ResourceValidator:
|
|
|
213
185
|
for tool_id in tool_ids:
|
|
214
186
|
try:
|
|
215
187
|
client.get_tool_by_id(tool_id)
|
|
216
|
-
except NotFoundError:
|
|
217
|
-
raise ValidationError(f"Tool not found: {tool_id}")
|
|
188
|
+
except NotFoundError as err:
|
|
189
|
+
raise ValidationError(f"Tool not found: {tool_id}") from err
|
|
218
190
|
|
|
219
191
|
@classmethod
|
|
220
192
|
def validate_agents_exist(cls, agent_ids: list[str], client: Any) -> None:
|
|
@@ -222,5 +194,5 @@ class ResourceValidator:
|
|
|
222
194
|
for agent_id in agent_ids:
|
|
223
195
|
try:
|
|
224
196
|
client.get_agent_by_id(agent_id)
|
|
225
|
-
except NotFoundError:
|
|
226
|
-
raise ValidationError(f"Agent not found: {agent_id}")
|
|
197
|
+
except NotFoundError as err:
|
|
198
|
+
raise ValidationError(f"Agent not found: {agent_id}") from err
|
glaip_sdk/config/constants.py
CHANGED
|
@@ -5,11 +5,7 @@ Authors:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
# Default language model configuration
|
|
8
|
-
DEFAULT_MODEL = "gpt-
|
|
9
|
-
DEFAULT_MODEL_PROVIDER = "openai"
|
|
10
|
-
|
|
11
|
-
# Default timeout values
|
|
12
|
-
DEFAULT_TIMEOUT = 30.0
|
|
8
|
+
DEFAULT_MODEL = "gpt-5-nano"
|
|
13
9
|
DEFAULT_AGENT_RUN_TIMEOUT = 300
|
|
14
10
|
|
|
15
11
|
# User agent and version
|
|
@@ -40,3 +36,17 @@ DEFAULT_TOOL_VERSION = "1.0"
|
|
|
40
36
|
# MCP creation/update constants
|
|
41
37
|
DEFAULT_MCP_TYPE = "server"
|
|
42
38
|
DEFAULT_MCP_TRANSPORT = "stdio"
|
|
39
|
+
|
|
40
|
+
# Default error messages
|
|
41
|
+
DEFAULT_ERROR_MESSAGE = "Unknown error"
|
|
42
|
+
|
|
43
|
+
# Agent configuration fields used for CLI args and payload building
|
|
44
|
+
AGENT_CONFIG_FIELDS = (
|
|
45
|
+
"name",
|
|
46
|
+
"instruction",
|
|
47
|
+
"model",
|
|
48
|
+
"tools",
|
|
49
|
+
"agents",
|
|
50
|
+
"mcps",
|
|
51
|
+
"timeout",
|
|
52
|
+
)
|
glaip_sdk/exceptions.py
CHANGED
|
@@ -26,6 +26,15 @@ class APIError(AIPError):
|
|
|
26
26
|
payload: Any = None,
|
|
27
27
|
request_id: str | None = None,
|
|
28
28
|
):
|
|
29
|
+
"""Initialize the API error.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
message: The error message
|
|
33
|
+
status_code: HTTP status code
|
|
34
|
+
error_type: Type of error
|
|
35
|
+
payload: Additional error payload
|
|
36
|
+
request_id: Request identifier
|
|
37
|
+
"""
|
|
29
38
|
super().__init__(message)
|
|
30
39
|
self.status_code = status_code
|
|
31
40
|
self.error_type = error_type
|
|
@@ -91,16 +100,14 @@ class AgentTimeoutError(TimeoutError):
|
|
|
91
100
|
"""Agent execution timeout with specific duration information."""
|
|
92
101
|
|
|
93
102
|
def __init__(self, timeout_seconds: float, agent_name: str = None):
|
|
103
|
+
"""Initialize the agent timeout error.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
timeout_seconds: The timeout duration in seconds
|
|
107
|
+
agent_name: Optional name of the agent that timed out
|
|
108
|
+
"""
|
|
94
109
|
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
|
-
)
|
|
110
|
+
message = f"Agent execution timed out after {timeout_seconds} seconds{agent_info}"
|
|
98
111
|
super().__init__(message)
|
|
99
112
|
self.timeout_seconds = timeout_seconds
|
|
100
113
|
self.agent_name = agent_name
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class ClientError(APIError):
|
|
104
|
-
"""Client-side error (e.g., invalid request format, missing parameters)."""
|
|
105
|
-
|
|
106
|
-
pass
|
glaip_sdk/icons.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Lightweight icon definitions used across the CLI.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
ICON_AGENT = "🤖"
|
|
8
|
+
ICON_AGENT_STEP = "🤖"
|
|
9
|
+
ICON_TOOL = "🔧"
|
|
10
|
+
ICON_TOOL_STEP = "🔧"
|
|
11
|
+
ICON_DELEGATE = ICON_AGENT_STEP
|
|
12
|
+
ICON_STATUS_SUCCESS = "✓"
|
|
13
|
+
ICON_STATUS_FAILED = "✗"
|
|
14
|
+
ICON_STATUS_WARNING = "⚠"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ICON_AGENT",
|
|
18
|
+
"ICON_AGENT_STEP",
|
|
19
|
+
"ICON_TOOL",
|
|
20
|
+
"ICON_TOOL_STEP",
|
|
21
|
+
"ICON_DELEGATE",
|
|
22
|
+
"ICON_STATUS_SUCCESS",
|
|
23
|
+
"ICON_STATUS_FAILED",
|
|
24
|
+
"ICON_STATUS_WARNING",
|
|
25
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) package for GL AIP platform.
|
|
2
|
+
|
|
3
|
+
This package provides the MCP class and MCPRegistry for managing
|
|
4
|
+
Model Context Protocol configurations on the GL AIP platform.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from glaip_sdk.mcps import MCP, get_mcp_registry
|
|
8
|
+
>>> mcp = MCP.from_native("arxiv-search")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from glaip_sdk.mcps.base import MCP, MCPConfigValue
|
|
14
|
+
from glaip_sdk.registry.mcp import MCPRegistry, get_mcp_registry
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"MCP",
|
|
18
|
+
"MCPConfigValue",
|
|
19
|
+
"MCPRegistry",
|
|
20
|
+
"get_mcp_registry",
|
|
21
|
+
]
|