glaip-sdk 0.1.0__py3-none-any.whl → 0.6.10__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 -2
- glaip_sdk/_version.py +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1191 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +265 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +251 -173
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +735 -143
- glaip_sdk/cli/commands/mcps.py +266 -134
- glaip_sdk/cli/commands/models.py +13 -9
- glaip_sdk/cli/commands/tools.py +67 -88
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +49 -7
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -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 +45 -32
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +232 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +12 -19
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +807 -225
- 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 +876 -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 +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -499
- glaip_sdk/cli/update_notifier.py +177 -24
- glaip_sdk/cli/utils.py +242 -1308
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +53 -37
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +320 -92
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +123 -15
- glaip_sdk/client/run_rendering.py +136 -101
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +163 -34
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- 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 +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -3
- 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 +232 -0
- glaip_sdk/rich_components.py +58 -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 +706 -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 +257 -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 +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +58 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- 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 +7 -35
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +3 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -49
- glaip_sdk/utils/rendering/renderer/base.py +258 -1577
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +10 -51
- 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 +1 -3
- 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 +1 -3
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +76 -517
- 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 +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +20 -28
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
- glaip_sdk-0.6.10.dist-info/RECORD +159 -0
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
glaip_sdk/utils/serialization.py
CHANGED
|
@@ -67,20 +67,12 @@ def read_yaml(file_path: Path) -> dict[str, Any]:
|
|
|
67
67
|
data = yaml.safe_load(f)
|
|
68
68
|
|
|
69
69
|
# Handle instruction_lines array format for user-friendly YAML
|
|
70
|
-
if (
|
|
71
|
-
isinstance(data, dict)
|
|
72
|
-
and "instruction_lines" in data
|
|
73
|
-
and isinstance(data["instruction_lines"], list)
|
|
74
|
-
):
|
|
70
|
+
if isinstance(data, dict) and "instruction_lines" in data and isinstance(data["instruction_lines"], list):
|
|
75
71
|
data["instruction"] = "\n\n".join(data["instruction_lines"])
|
|
76
72
|
del data["instruction_lines"]
|
|
77
73
|
|
|
78
74
|
# Handle instruction as list from YAML export (convert back to string)
|
|
79
|
-
if (
|
|
80
|
-
isinstance(data, dict)
|
|
81
|
-
and "instruction" in data
|
|
82
|
-
and isinstance(data["instruction"], list)
|
|
83
|
-
):
|
|
75
|
+
if isinstance(data, dict) and "instruction" in data and isinstance(data["instruction"], list):
|
|
84
76
|
data["instruction"] = "\n\n".join(data["instruction"])
|
|
85
77
|
|
|
86
78
|
return data
|
|
@@ -96,11 +88,20 @@ def write_yaml(file_path: Path, data: dict[str, Any]) -> None:
|
|
|
96
88
|
|
|
97
89
|
# Custom YAML dumper for user-friendly instruction formatting
|
|
98
90
|
class LiteralString(str):
|
|
91
|
+
"""String subclass for YAML literal block scalar formatting."""
|
|
92
|
+
|
|
99
93
|
pass
|
|
100
94
|
|
|
101
|
-
def literal_string_representer(
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
def literal_string_representer(dumper: yaml.Dumper, data: "LiteralString") -> yaml.nodes.Node:
|
|
96
|
+
"""YAML representer for LiteralString to use literal block scalar style.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
dumper: YAML dumper instance.
|
|
100
|
+
data: LiteralString instance to represent.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
YAML node with literal block scalar style for multiline strings.
|
|
104
|
+
"""
|
|
104
105
|
# Use literal block scalar (|) for multiline strings to preserve formatting
|
|
105
106
|
if "\n" in data:
|
|
106
107
|
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
|
|
@@ -115,9 +116,7 @@ def write_yaml(file_path: Path, data: dict[str, Any]) -> None:
|
|
|
115
116
|
data["instruction"] = LiteralString(data["instruction"])
|
|
116
117
|
|
|
117
118
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
118
|
-
yaml.dump(
|
|
119
|
-
data, f, default_flow_style=False, allow_unicode=True, sort_keys=False
|
|
120
|
-
)
|
|
119
|
+
yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
121
120
|
|
|
122
121
|
|
|
123
122
|
def load_resource_from_file(file_path: Path) -> dict[str, Any]:
|
|
@@ -137,15 +136,10 @@ def load_resource_from_file(file_path: Path) -> dict[str, Any]:
|
|
|
137
136
|
elif file_path.suffix.lower() == ".json":
|
|
138
137
|
return read_json(file_path)
|
|
139
138
|
else:
|
|
140
|
-
raise ValueError(
|
|
141
|
-
f"Unsupported file format: {file_path.suffix}. "
|
|
142
|
-
f"Only JSON and YAML files are supported."
|
|
143
|
-
)
|
|
139
|
+
raise ValueError(f"Unsupported file format: {file_path.suffix}. Only JSON and YAML files are supported.")
|
|
144
140
|
|
|
145
141
|
|
|
146
|
-
def write_resource_export(
|
|
147
|
-
file_path: Path, data: dict[str, Any], format: str = "json"
|
|
148
|
-
) -> None:
|
|
142
|
+
def write_resource_export(file_path: Path, data: dict[str, Any], format: str = "json") -> None:
|
|
149
143
|
"""Write resource export data to file.
|
|
150
144
|
|
|
151
145
|
Args:
|
|
@@ -190,13 +184,8 @@ def collect_attributes_for_export(resource: Any) -> dict[str, Any]:
|
|
|
190
184
|
data.
|
|
191
185
|
"""
|
|
192
186
|
mapping = _coerce_resource_to_mapping(resource)
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
): # pragma: no cover - defensive fallback when attribute introspection fails
|
|
196
|
-
items = (
|
|
197
|
-
(name, _safe_getattr(resource, name))
|
|
198
|
-
for name in _iter_public_attribute_names(resource)
|
|
199
|
-
)
|
|
187
|
+
if mapping is None: # pragma: no cover - defensive fallback when attribute introspection fails
|
|
188
|
+
items = ((name, _safe_getattr(resource, name)) for name in _iter_public_attribute_names(resource))
|
|
200
189
|
else:
|
|
201
190
|
items = mapping.items()
|
|
202
191
|
|
|
@@ -249,9 +238,7 @@ def _coerce_resource_to_mapping(resource: Any) -> dict[str, Any] | None:
|
|
|
249
238
|
try:
|
|
250
239
|
if hasattr(resource, "__dict__"):
|
|
251
240
|
return dict(resource.__dict__)
|
|
252
|
-
except
|
|
253
|
-
Exception
|
|
254
|
-
): # pragma: no cover - pathological objects can still defeat coercion
|
|
241
|
+
except Exception: # pragma: no cover - pathological objects can still defeat coercion
|
|
255
242
|
return None
|
|
256
243
|
|
|
257
244
|
return None
|
|
@@ -263,6 +250,11 @@ def _iter_public_attribute_names(resource: Any) -> Iterable[str]:
|
|
|
263
250
|
names: list[str] = []
|
|
264
251
|
|
|
265
252
|
def _collect(candidates: Iterable[str] | None) -> None:
|
|
253
|
+
"""Collect unique candidate attribute names.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
candidates: Iterable of candidate attribute names.
|
|
257
|
+
"""
|
|
266
258
|
for candidate in candidates or ():
|
|
267
259
|
if candidate not in seen:
|
|
268
260
|
seen.add(candidate)
|
|
@@ -284,9 +276,7 @@ def _iter_public_attribute_names(resource: Any) -> Iterable[str]:
|
|
|
284
276
|
return iter(names)
|
|
285
277
|
|
|
286
278
|
|
|
287
|
-
def _collect_from_dict(
|
|
288
|
-
resource: Any, collect_func: Callable[[Iterable[str]], None]
|
|
289
|
-
) -> None:
|
|
279
|
+
def _collect_from_dict(resource: Any, collect_func: Callable[[Iterable[str]], None]) -> None:
|
|
290
280
|
"""Safely collect attribute names from __dict__."""
|
|
291
281
|
try:
|
|
292
282
|
if hasattr(resource, "__dict__"):
|
|
@@ -297,18 +287,14 @@ def _collect_from_dict(
|
|
|
297
287
|
pass
|
|
298
288
|
|
|
299
289
|
|
|
300
|
-
def _collect_from_annotations(
|
|
301
|
-
resource: Any, collect_func: Callable[[Iterable[str]], None]
|
|
302
|
-
) -> None:
|
|
290
|
+
def _collect_from_annotations(resource: Any, collect_func: Callable[[Iterable[str]], None]) -> None:
|
|
303
291
|
"""Safely collect attribute names from __annotations__."""
|
|
304
292
|
annotations = getattr(resource, "__annotations__", {})
|
|
305
293
|
if annotations:
|
|
306
294
|
collect_func(annotations.keys())
|
|
307
295
|
|
|
308
296
|
|
|
309
|
-
def _collect_from_dir(
|
|
310
|
-
resource: Any, collect_func: Callable[[Iterable[str]], None]
|
|
311
|
-
) -> None:
|
|
297
|
+
def _collect_from_dir(resource: Any, collect_func: Callable[[Iterable[str]], None]) -> None:
|
|
312
298
|
"""Safely collect attribute names from dir()."""
|
|
313
299
|
try:
|
|
314
300
|
collect_func(name for name in dir(resource) if not name.startswith("__"))
|
|
@@ -317,6 +303,7 @@ def _collect_from_dir(
|
|
|
317
303
|
|
|
318
304
|
|
|
319
305
|
def _safe_getattr(resource: Any, name: str) -> Any:
|
|
306
|
+
"""Return getattr(resource, name) but swallow any exception and return None."""
|
|
320
307
|
try:
|
|
321
308
|
return getattr(resource, name)
|
|
322
309
|
except Exception:
|
|
@@ -324,6 +311,7 @@ def _safe_getattr(resource: Any, name: str) -> Any:
|
|
|
324
311
|
|
|
325
312
|
|
|
326
313
|
def _should_include_attribute(key: str, value: Any) -> bool:
|
|
314
|
+
"""Return True when an attribute should be serialized."""
|
|
327
315
|
if key in _EXCLUDED_ATTRS or key in _EXCLUDED_NAMES:
|
|
328
316
|
return False
|
|
329
317
|
if key.startswith("_"):
|
|
@@ -387,9 +375,7 @@ def build_mcp_export_payload(
|
|
|
387
375
|
ImportError: If required modules (auth helpers) are not available
|
|
388
376
|
"""
|
|
389
377
|
auth_module = importlib.import_module("glaip_sdk.cli.auth")
|
|
390
|
-
prepare_authentication_export =
|
|
391
|
-
auth_module, "prepare_authentication_export"
|
|
392
|
-
)
|
|
378
|
+
prepare_authentication_export = auth_module.prepare_authentication_export
|
|
393
379
|
|
|
394
380
|
# Start with model dump (excludes None values automatically)
|
|
395
381
|
payload = mcp.model_dump(exclude_none=True)
|
|
@@ -435,4 +421,4 @@ def validate_json_string(json_str: str) -> dict[str, Any]:
|
|
|
435
421
|
try:
|
|
436
422
|
return json.loads(json_str)
|
|
437
423
|
except json.JSONDecodeError as e:
|
|
438
|
-
raise ValueError(f"Invalid JSON: {e}")
|
|
424
|
+
raise ValueError(f"Invalid JSON: {e}") from e
|
glaip_sdk/utils/sync.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Agent and tool synchronization (create/update) operations.
|
|
2
|
+
|
|
3
|
+
This module provides convenience functions for tool classes that need bundling.
|
|
4
|
+
|
|
5
|
+
For direct upsert operations, use the client methods:
|
|
6
|
+
- client.agents.upsert_agent(identifier, **kwargs)
|
|
7
|
+
- client.tools.upsert_tool(identifier, code, **kwargs)
|
|
8
|
+
- client.mcps.upsert_mcp(identifier, **kwargs)
|
|
9
|
+
|
|
10
|
+
Authors:
|
|
11
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
from glaip_sdk.utils.bundler import ToolBundler
|
|
19
|
+
from glaip_sdk.utils.import_resolver import load_class
|
|
20
|
+
from gllm_core.utils import LoggerManager
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from glaip_sdk.models import Agent, Tool
|
|
24
|
+
|
|
25
|
+
logger = LoggerManager().get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _extract_tool_name(tool_class: Any) -> str:
|
|
29
|
+
"""Extract tool name from a class, handling Pydantic v2 models."""
|
|
30
|
+
# Direct attribute access (works for non-Pydantic classes)
|
|
31
|
+
if hasattr(tool_class, "name"):
|
|
32
|
+
name = getattr(tool_class, "name", None)
|
|
33
|
+
if isinstance(name, str):
|
|
34
|
+
return name
|
|
35
|
+
|
|
36
|
+
# Pydantic v2 model - check model_fields
|
|
37
|
+
if hasattr(tool_class, "model_fields"):
|
|
38
|
+
model_fields = getattr(tool_class, "model_fields", {})
|
|
39
|
+
if "name" in model_fields:
|
|
40
|
+
field_info = model_fields["name"]
|
|
41
|
+
if hasattr(field_info, "default") and isinstance(field_info.default, str):
|
|
42
|
+
return field_info.default
|
|
43
|
+
|
|
44
|
+
raise ValueError(f"Cannot extract name from tool class: {tool_class}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _extract_tool_description(tool_class: Any) -> str:
|
|
48
|
+
"""Extract tool description from a class, handling Pydantic v2 models."""
|
|
49
|
+
# Direct attribute access
|
|
50
|
+
if hasattr(tool_class, "description"):
|
|
51
|
+
desc = getattr(tool_class, "description", None)
|
|
52
|
+
if isinstance(desc, str):
|
|
53
|
+
return desc
|
|
54
|
+
|
|
55
|
+
# Pydantic v2 model - check model_fields
|
|
56
|
+
if hasattr(tool_class, "model_fields"):
|
|
57
|
+
model_fields = getattr(tool_class, "model_fields", {})
|
|
58
|
+
if "description" in model_fields:
|
|
59
|
+
field_info = model_fields["description"]
|
|
60
|
+
if hasattr(field_info, "default") and isinstance(field_info.default, str):
|
|
61
|
+
return field_info.default
|
|
62
|
+
|
|
63
|
+
return ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def update_or_create_tool(tool_ref: Any) -> Tool:
|
|
67
|
+
"""Create or update a tool from a tool class with bundled source code.
|
|
68
|
+
|
|
69
|
+
This function takes a tool class (LangChain BaseTool), bundles its source
|
|
70
|
+
code with inlined imports, and creates/updates it in the backend.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
tool_ref: A tool class (LangChain BaseTool subclass) or import path string.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The created or updated tool.
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> from glaip_sdk.utils.sync import update_or_create_tool
|
|
80
|
+
>>> from my_tools import WeatherAPITool
|
|
81
|
+
>>> tool = update_or_create_tool(WeatherAPITool)
|
|
82
|
+
"""
|
|
83
|
+
from glaip_sdk.utils.client import get_client # noqa: PLC0415
|
|
84
|
+
|
|
85
|
+
client = get_client()
|
|
86
|
+
|
|
87
|
+
# Handle string import path
|
|
88
|
+
if isinstance(tool_ref, str):
|
|
89
|
+
tool_class = load_class(tool_ref)
|
|
90
|
+
else:
|
|
91
|
+
tool_class = tool_ref
|
|
92
|
+
|
|
93
|
+
# Get tool info - handle Pydantic v2 model classes
|
|
94
|
+
tool_name = _extract_tool_name(tool_class)
|
|
95
|
+
tool_description = _extract_tool_description(tool_class)
|
|
96
|
+
|
|
97
|
+
# Bundle source code
|
|
98
|
+
bundler = ToolBundler(tool_class)
|
|
99
|
+
bundled_source = bundler.bundle()
|
|
100
|
+
|
|
101
|
+
logger.info("Tool info: name='%s', description='%s...'", tool_name, tool_description[:50])
|
|
102
|
+
logger.info("Bundled source code: %d characters", len(bundled_source))
|
|
103
|
+
|
|
104
|
+
# Use client's upsert method
|
|
105
|
+
return client.tools.upsert_tool(
|
|
106
|
+
tool_name,
|
|
107
|
+
code=bundled_source,
|
|
108
|
+
description=tool_description,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def update_or_create_agent(agent_config: dict[str, Any]) -> Agent:
|
|
113
|
+
"""Create or update an agent from configuration.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
agent_config: Agent configuration dictionary containing:
|
|
117
|
+
- name (str): Agent name (required)
|
|
118
|
+
- description (str): Agent description
|
|
119
|
+
- instruction (str): Agent instruction
|
|
120
|
+
- tools (list, optional): List of tool IDs
|
|
121
|
+
- agents (list, optional): List of sub-agent IDs
|
|
122
|
+
- metadata (dict, optional): Additional metadata
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
The created or updated agent.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
>>> from glaip_sdk.utils.sync import update_or_create_agent
|
|
129
|
+
>>> config = {
|
|
130
|
+
... "name": "weather_reporter",
|
|
131
|
+
... "description": "Weather reporting agent",
|
|
132
|
+
... "instruction": "You are a weather reporter.",
|
|
133
|
+
... }
|
|
134
|
+
>>> agent = update_or_create_agent(config)
|
|
135
|
+
"""
|
|
136
|
+
from glaip_sdk.utils.client import get_client # noqa: PLC0415
|
|
137
|
+
|
|
138
|
+
client = get_client()
|
|
139
|
+
agent_name = agent_config.pop("name")
|
|
140
|
+
|
|
141
|
+
# Use client's upsert method
|
|
142
|
+
return client.agents.upsert_agent(agent_name, **agent_config)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Shared utilities for tool type detection.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def is_langchain_tool(ref: Any) -> bool:
|
|
11
|
+
"""Check if ref is a LangChain BaseTool class or instance.
|
|
12
|
+
|
|
13
|
+
Shared by:
|
|
14
|
+
- ToolRegistry._is_custom_tool() (for upload detection)
|
|
15
|
+
- LangChainToolAdapter._is_langchain_tool() (for adaptation)
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
ref: Object to check.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
True if ref is a LangChain BaseTool class or instance.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
from langchain_core.tools import BaseTool # noqa: PLC0415
|
|
25
|
+
|
|
26
|
+
if isinstance(ref, type) and issubclass(ref, BaseTool):
|
|
27
|
+
return True
|
|
28
|
+
if isinstance(ref, BaseTool):
|
|
29
|
+
return True
|
|
30
|
+
except ImportError:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
return False
|
glaip_sdk/utils/validation.py
CHANGED
|
@@ -18,6 +18,16 @@ from glaip_sdk.utils.resource_refs import validate_name_format
|
|
|
18
18
|
RESERVED_NAMES = ["admin", "root", "system", "api", "test", "demo"]
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
def _validate_named_resource(name: str, resource_type: str) -> str:
|
|
22
|
+
"""Shared validator that prevents reserved-name duplication."""
|
|
23
|
+
cleaned_name = validate_name_format(name, resource_type)
|
|
24
|
+
|
|
25
|
+
if cleaned_name.lower() in RESERVED_NAMES:
|
|
26
|
+
raise ValueError(f"{resource_type.capitalize()} name '{cleaned_name}' is reserved and cannot be used")
|
|
27
|
+
|
|
28
|
+
return cleaned_name
|
|
29
|
+
|
|
30
|
+
|
|
21
31
|
def validate_agent_name(name: str) -> str:
|
|
22
32
|
"""Validate agent name and return cleaned version.
|
|
23
33
|
|
|
@@ -30,13 +40,7 @@ def validate_agent_name(name: str) -> str:
|
|
|
30
40
|
Raises:
|
|
31
41
|
ValueError: If name is invalid
|
|
32
42
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Check for reserved names
|
|
36
|
-
if cleaned_name.lower() in RESERVED_NAMES:
|
|
37
|
-
raise ValueError(f"'{cleaned_name}' is a reserved name and cannot be used")
|
|
38
|
-
|
|
39
|
-
return cleaned_name
|
|
43
|
+
return _validate_named_resource(name, "agent")
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
def validate_agent_instruction(instruction: str) -> str:
|
|
@@ -74,13 +78,7 @@ def validate_tool_name(name: str) -> str:
|
|
|
74
78
|
Raises:
|
|
75
79
|
ValueError: If name is invalid
|
|
76
80
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# Check for reserved names
|
|
80
|
-
if cleaned_name.lower() in RESERVED_NAMES:
|
|
81
|
-
raise ValueError(f"'{cleaned_name}' is a reserved name and cannot be used")
|
|
82
|
-
|
|
83
|
-
return cleaned_name
|
|
81
|
+
return _validate_named_resource(name, "tool")
|
|
84
82
|
|
|
85
83
|
|
|
86
84
|
def validate_mcp_name(name: str) -> str:
|
|
@@ -95,13 +93,7 @@ def validate_mcp_name(name: str) -> str:
|
|
|
95
93
|
Raises:
|
|
96
94
|
ValueError: If name is invalid
|
|
97
95
|
"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# Check for reserved names
|
|
101
|
-
if cleaned_name.lower() in RESERVED_NAMES:
|
|
102
|
-
raise ValueError(f"'{cleaned_name}' is a reserved name and cannot be used")
|
|
103
|
-
|
|
104
|
-
return cleaned_name
|
|
96
|
+
return _validate_named_resource(name, "mcp")
|
|
105
97
|
|
|
106
98
|
|
|
107
99
|
def validate_timeout(timeout: int) -> int:
|
|
@@ -155,13 +147,13 @@ def coerce_timeout(value: Any) -> int:
|
|
|
155
147
|
try:
|
|
156
148
|
fval = float(value)
|
|
157
149
|
return validate_timeout(int(fval))
|
|
158
|
-
except ValueError:
|
|
159
|
-
raise ValueError(f"Invalid timeout value: {value}")
|
|
150
|
+
except ValueError as err:
|
|
151
|
+
raise ValueError(f"Invalid timeout value: {value}") from err
|
|
160
152
|
else:
|
|
161
153
|
try:
|
|
162
154
|
return validate_timeout(int(value))
|
|
163
|
-
except (TypeError, ValueError):
|
|
164
|
-
raise ValueError(f"Invalid timeout value: {value}")
|
|
155
|
+
except (TypeError, ValueError) as err:
|
|
156
|
+
raise ValueError(f"Invalid timeout value: {value}") from err
|
|
165
157
|
|
|
166
158
|
|
|
167
159
|
def validate_file_path(file_path: str | Path, must_exist: bool = True) -> Path:
|
|
@@ -213,7 +205,7 @@ def validate_directory_path(dir_path: str | Path, must_exist: bool = True) -> Pa
|
|
|
213
205
|
|
|
214
206
|
|
|
215
207
|
def validate_url(url: str) -> str:
|
|
216
|
-
"""Validate URL format.
|
|
208
|
+
"""Validate URL format (HTTPS only).
|
|
217
209
|
|
|
218
210
|
Args:
|
|
219
211
|
url: URL to validate
|
|
@@ -225,7 +217,7 @@ def validate_url(url: str) -> str:
|
|
|
225
217
|
ValueError: If URL is invalid
|
|
226
218
|
"""
|
|
227
219
|
url_pattern = re.compile(
|
|
228
|
-
r"^https
|
|
220
|
+
r"^https://" # https:// only
|
|
229
221
|
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|" # domain...
|
|
230
222
|
r"localhost|" # localhost...
|
|
231
223
|
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip
|
|
@@ -235,7 +227,7 @@ def validate_url(url: str) -> str:
|
|
|
235
227
|
)
|
|
236
228
|
|
|
237
229
|
if not url_pattern.match(url):
|
|
238
|
-
raise ValueError(
|
|
230
|
+
raise ValueError("API URL must start with https:// and be a valid host.")
|
|
239
231
|
|
|
240
232
|
return url
|
|
241
233
|
|
|
@@ -1,25 +1,42 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: glaip-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.10
|
|
4
4
|
Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Raymond Christopher
|
|
7
7
|
Author-email: raymond.christopher@gdplabs.id
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11,<3.13
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Provides-Extra: memory
|
|
15
|
+
Provides-Extra: privacy
|
|
16
|
+
Requires-Dist: aip-agents-binary (>=0.5.1)
|
|
17
|
+
Requires-Dist: aip-agents[memory] (>=0.5.1) ; (python_version >= "3.11" and python_version < "3.13") and (extra == "memory")
|
|
18
|
+
Requires-Dist: aip-agents[privacy] (>=0.5.1) ; (python_version >= "3.11" and python_version < "3.13") and (extra == "privacy")
|
|
14
19
|
Requires-Dist: click (>=8.2.0,<8.3.0)
|
|
20
|
+
Requires-Dist: gllm-core-binary (>=0.1.0)
|
|
21
|
+
Requires-Dist: gllm-tools-binary (>=0.1.3)
|
|
15
22
|
Requires-Dist: httpx (>=0.28.1)
|
|
23
|
+
Requires-Dist: langchain-core (>=0.3.0)
|
|
16
24
|
Requires-Dist: packaging (>=23.2)
|
|
25
|
+
Requires-Dist: pre-commit (>=4.3.0) ; extra == "dev"
|
|
17
26
|
Requires-Dist: pydantic (>=2.0.0)
|
|
27
|
+
Requires-Dist: pytest (>=7.0.0) ; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio (>=0.23.6) ; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov (>=4.0.0) ; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-dotenv (>=0.5.2) ; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-timeout (>=2.3.1) ; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-xdist (>=3.8.0) ; extra == "dev"
|
|
18
33
|
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
19
34
|
Requires-Dist: pyyaml (>=6.0.0)
|
|
20
35
|
Requires-Dist: questionary (>=2.1.0,<3.0.0)
|
|
21
36
|
Requires-Dist: readchar (>=4.2.1,<5.0.0)
|
|
22
37
|
Requires-Dist: rich (>=13.0.0)
|
|
38
|
+
Requires-Dist: ruff (>=0.14.0) ; extra == "dev"
|
|
39
|
+
Requires-Dist: textual (>=0.52.0)
|
|
23
40
|
Description-Content-Type: text/markdown
|
|
24
41
|
|
|
25
42
|
# GL AIP — GDP Labs AI Agents Package
|
|
@@ -193,3 +210,24 @@ Quick links:
|
|
|
193
210
|
- **[MCP Integration](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/guides/mcps-guide)**: Connect external services
|
|
194
211
|
- **[API Reference](https://gdplabs.gitbook.io/gl-aip/gl-aip-sdk/reference/python-sdk-reference)**: Complete SDK reference
|
|
195
212
|
|
|
213
|
+
## 🧪 Simulate the Update Notifier
|
|
214
|
+
|
|
215
|
+
Need to verify the in-session upgrade flow without hitting PyPI or actually running `pip install`? Use the bundled helper:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
cd python/glaip-sdk
|
|
219
|
+
poetry run python scripts/mock_update_notifier.py
|
|
220
|
+
# or customize the mock payload:
|
|
221
|
+
# poetry run python scripts/mock_update_notifier.py --version 3.3.3 --marker "[nightly build]"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The script:
|
|
225
|
+
|
|
226
|
+
- Launches a SlashSession with prompt-toolkit disabled (so it runs cleanly in tests/CI).
|
|
227
|
+
- Forces the notifier to believe a newer version exists (`--version 9.9.9` by default).
|
|
228
|
+
- Appends a visible marker (default `[mock update]`) to the banner so you can prove the branding reload happened; pass `--marker ""` to skip.
|
|
229
|
+
- Auto-selects “Update now”, mocks the install step, and runs the real branding refresh logic.
|
|
230
|
+
- Resets module metadata afterwards so your environment remains untouched.
|
|
231
|
+
|
|
232
|
+
You should see the Rich banner re-render with the mocked version (and optional marker) at the end of the run.
|
|
233
|
+
|