glaip-sdk 0.6.5b6__py3-none-any.whl → 0.7.12__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.
Files changed (127) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. glaip_sdk/agents/base.py +217 -42
  3. glaip_sdk/branding.py +113 -2
  4. glaip_sdk/cli/account_store.py +15 -0
  5. glaip_sdk/cli/auth.py +14 -8
  6. glaip_sdk/cli/commands/accounts.py +1 -1
  7. glaip_sdk/cli/commands/agents/__init__.py +119 -0
  8. glaip_sdk/cli/commands/agents/_common.py +561 -0
  9. glaip_sdk/cli/commands/agents/create.py +151 -0
  10. glaip_sdk/cli/commands/agents/delete.py +64 -0
  11. glaip_sdk/cli/commands/agents/get.py +89 -0
  12. glaip_sdk/cli/commands/agents/list.py +129 -0
  13. glaip_sdk/cli/commands/agents/run.py +264 -0
  14. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  15. glaip_sdk/cli/commands/agents/update.py +112 -0
  16. glaip_sdk/cli/commands/common_config.py +15 -12
  17. glaip_sdk/cli/commands/configure.py +2 -3
  18. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  19. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  20. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  21. glaip_sdk/cli/commands/mcps/create.py +152 -0
  22. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  23. glaip_sdk/cli/commands/mcps/get.py +212 -0
  24. glaip_sdk/cli/commands/mcps/list.py +69 -0
  25. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  26. glaip_sdk/cli/commands/mcps/update.py +190 -0
  27. glaip_sdk/cli/commands/models.py +2 -4
  28. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  29. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  30. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  31. glaip_sdk/cli/commands/tools/_common.py +80 -0
  32. glaip_sdk/cli/commands/tools/create.py +228 -0
  33. glaip_sdk/cli/commands/tools/delete.py +61 -0
  34. glaip_sdk/cli/commands/tools/get.py +103 -0
  35. glaip_sdk/cli/commands/tools/list.py +69 -0
  36. glaip_sdk/cli/commands/tools/script.py +49 -0
  37. glaip_sdk/cli/commands/tools/update.py +102 -0
  38. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  39. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  40. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  41. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  42. glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
  43. glaip_sdk/cli/commands/update.py +163 -17
  44. glaip_sdk/cli/config.py +1 -0
  45. glaip_sdk/cli/core/output.py +12 -7
  46. glaip_sdk/cli/entrypoint.py +20 -0
  47. glaip_sdk/cli/main.py +127 -39
  48. glaip_sdk/cli/pager.py +3 -3
  49. glaip_sdk/cli/resolution.py +2 -1
  50. glaip_sdk/cli/slash/accounts_controller.py +112 -32
  51. glaip_sdk/cli/slash/agent_session.py +5 -2
  52. glaip_sdk/cli/slash/prompt.py +11 -0
  53. glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
  54. glaip_sdk/cli/slash/session.py +369 -23
  55. glaip_sdk/cli/slash/tui/__init__.py +26 -1
  56. glaip_sdk/cli/slash/tui/accounts.tcss +79 -5
  57. glaip_sdk/cli/slash/tui/accounts_app.py +1027 -88
  58. glaip_sdk/cli/slash/tui/clipboard.py +195 -0
  59. glaip_sdk/cli/slash/tui/context.py +87 -0
  60. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  61. glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
  62. glaip_sdk/cli/slash/tui/layouts/harlequin.py +160 -0
  63. glaip_sdk/cli/slash/tui/remote_runs_app.py +119 -12
  64. glaip_sdk/cli/slash/tui/terminal.py +407 -0
  65. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  66. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  67. glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
  68. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  69. glaip_sdk/cli/slash/tui/toast.py +374 -0
  70. glaip_sdk/cli/transcript/history.py +1 -1
  71. glaip_sdk/cli/transcript/viewer.py +5 -3
  72. glaip_sdk/cli/tui_settings.py +125 -0
  73. glaip_sdk/cli/update_notifier.py +215 -7
  74. glaip_sdk/cli/validators.py +1 -1
  75. glaip_sdk/client/__init__.py +2 -1
  76. glaip_sdk/client/_schedule_payloads.py +89 -0
  77. glaip_sdk/client/agents.py +50 -8
  78. glaip_sdk/client/hitl.py +136 -0
  79. glaip_sdk/client/main.py +7 -1
  80. glaip_sdk/client/mcps.py +44 -13
  81. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  82. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
  83. glaip_sdk/client/payloads/agent/responses.py +43 -0
  84. glaip_sdk/client/run_rendering.py +414 -3
  85. glaip_sdk/client/schedules.py +439 -0
  86. glaip_sdk/client/tools.py +57 -26
  87. glaip_sdk/guardrails/__init__.py +80 -0
  88. glaip_sdk/guardrails/serializer.py +89 -0
  89. glaip_sdk/hitl/__init__.py +48 -0
  90. glaip_sdk/hitl/base.py +64 -0
  91. glaip_sdk/hitl/callback.py +43 -0
  92. glaip_sdk/hitl/local.py +121 -0
  93. glaip_sdk/hitl/remote.py +523 -0
  94. glaip_sdk/models/__init__.py +17 -0
  95. glaip_sdk/models/agent_runs.py +2 -1
  96. glaip_sdk/models/schedule.py +224 -0
  97. glaip_sdk/payload_schemas/agent.py +1 -0
  98. glaip_sdk/payload_schemas/guardrails.py +34 -0
  99. glaip_sdk/registry/tool.py +273 -59
  100. glaip_sdk/runner/__init__.py +20 -3
  101. glaip_sdk/runner/deps.py +5 -8
  102. glaip_sdk/runner/langgraph.py +318 -42
  103. glaip_sdk/runner/logging_config.py +77 -0
  104. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +104 -5
  105. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +72 -7
  106. glaip_sdk/schedules/__init__.py +22 -0
  107. glaip_sdk/schedules/base.py +291 -0
  108. glaip_sdk/tools/base.py +67 -14
  109. glaip_sdk/utils/__init__.py +1 -0
  110. glaip_sdk/utils/bundler.py +138 -2
  111. glaip_sdk/utils/import_resolver.py +43 -11
  112. glaip_sdk/utils/rendering/renderer/base.py +58 -0
  113. glaip_sdk/utils/runtime_config.py +15 -12
  114. glaip_sdk/utils/sync.py +31 -11
  115. glaip_sdk/utils/tool_detection.py +274 -6
  116. glaip_sdk/utils/tool_storage_provider.py +140 -0
  117. {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/METADATA +49 -37
  118. glaip_sdk-0.7.12.dist-info/RECORD +219 -0
  119. {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/WHEEL +2 -1
  120. glaip_sdk-0.7.12.dist-info/entry_points.txt +2 -0
  121. glaip_sdk-0.7.12.dist-info/top_level.txt +1 -0
  122. glaip_sdk/cli/commands/agents.py +0 -1509
  123. glaip_sdk/cli/commands/mcps.py +0 -1356
  124. glaip_sdk/cli/commands/tools.py +0 -576
  125. glaip_sdk/cli/utils.py +0 -263
  126. glaip_sdk-0.6.5b6.dist-info/RECORD +0 -159
  127. glaip_sdk-0.6.5b6.dist-info/entry_points.txt +0 -3
@@ -4,8 +4,140 @@ Authors:
4
4
  Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+ import importlib
11
+ import inspect
12
+ import pkgutil
13
+ from functools import lru_cache
7
14
  from typing import Any
8
15
 
16
+ # Constants for frequently used strings to avoid duplication (S1192)
17
+ _NAME = "name"
18
+ _AIP_AGENTS_TOOLS = "aip_agents.tools"
19
+ _BASE_TOOL = "BaseTool"
20
+
21
+ # Internal map to store all discovered tools in the session
22
+ _DISCOVERED_TOOLS: dict[str, type] | None = None
23
+
24
+
25
+ def _should_skip_module(module_name: str) -> bool:
26
+ """Check if module should be skipped during tool discovery."""
27
+ short_name = module_name.rsplit(".", 1)[-1]
28
+ return short_name.startswith("_") or "test" in short_name
29
+
30
+
31
+ def _get_pydantic_field_default(cls: type, attr_name: str, field_name: str) -> str | None:
32
+ """Extract default value from a Pydantic field."""
33
+ try:
34
+ fields = getattr(cls, attr_name, {})
35
+ field = fields.get(field_name)
36
+ # Broad exception handling needed because:
37
+ # - model_fields/__fields__ might be a descriptor that raises AttributeError
38
+ # - field.default might raise during access
39
+ # - Various Pydantic internals can raise unexpected exceptions
40
+ if field and hasattr(field, "default") and isinstance(field.default, str):
41
+ return field.default
42
+ except Exception: # pylint: disable=broad-except
43
+ pass
44
+ return None
45
+
46
+
47
+ def _get_name_from_pydantic_v2(cls: type) -> str | None:
48
+ """Extract name from Pydantic v2 model_fields."""
49
+ return _get_pydantic_field_default(cls, "model_fields", _NAME)
50
+
51
+
52
+ def _get_name_from_pydantic_v1(cls: type) -> str | None:
53
+ """Extract name from Pydantic v1 __fields__."""
54
+ return _get_pydantic_field_default(cls, "__fields__", _NAME)
55
+
56
+
57
+ def get_tool_name(ref: Any) -> str | None:
58
+ """Extract tool name from a tool class or instance.
59
+
60
+ Handles LangChain BaseTool (Pydantic v1/v2) and standard classes.
61
+
62
+ Args:
63
+ ref: Tool class or instance.
64
+
65
+ Returns:
66
+ The extracted tool name, or None if not found.
67
+ """
68
+ if ref is None:
69
+ return None
70
+
71
+ # 1. Try instance 'name' attribute
72
+ if not isinstance(ref, type):
73
+ try:
74
+ name = getattr(ref, _NAME, None)
75
+ if isinstance(name, str):
76
+ return name
77
+ except Exception: # pylint: disable=broad-except
78
+ pass
79
+
80
+ cls = ref if isinstance(ref, type) else type(ref)
81
+
82
+ # 2. Try class 'model_fields' (Pydantic v2)
83
+ # Check Pydantic v2 first for forward compatibility
84
+ name = _get_name_from_pydantic_v2(cls)
85
+ if name:
86
+ return name
87
+
88
+ # 3. Try class '__fields__' (Pydantic v1)
89
+ name = _get_name_from_pydantic_v1(cls)
90
+ if name:
91
+ return name
92
+
93
+ # 4. Try direct class attribute
94
+ if hasattr(cls, _NAME):
95
+ try:
96
+ name_attr = getattr(cls, _NAME)
97
+ if isinstance(name_attr, str):
98
+ return name_attr
99
+ except Exception: # pylint: disable=broad-except
100
+ pass
101
+
102
+ return None
103
+
104
+
105
+ def _check_langchain_standard(ref: Any) -> bool:
106
+ """Perform standard isinstance/issubclass check for LangChain tool."""
107
+ try:
108
+ from langchain_core.tools import BaseTool # noqa: PLC0415
109
+
110
+ # Check if BaseTool is actually a type to avoid TypeError in issubclass/isinstance
111
+ if isinstance(BaseTool, type):
112
+ if isinstance(ref, type) and issubclass(ref, BaseTool):
113
+ return True
114
+ if isinstance(ref, BaseTool):
115
+ return True
116
+ except (ImportError, TypeError):
117
+ pass
118
+ return False
119
+
120
+
121
+ def _check_langchain_fallback(ref: Any) -> bool:
122
+ """Perform name-based fallback check for LangChain tool (robust for mocks).
123
+
124
+ This fallback handles cases where:
125
+ - BaseTool is mocked in tests
126
+ - BaseTool is re-imported through internal modules (e.g., runner)
127
+ - isinstance/issubclass checks fail due to module reloading
128
+ """
129
+ try:
130
+ cls = ref if isinstance(ref, type) else getattr(ref, "__class__", None)
131
+ if cls and hasattr(cls, "__mro__"):
132
+ for c in cls.__mro__:
133
+ c_name = getattr(c, "__name__", None)
134
+ c_module = getattr(c, "__module__", "")
135
+ if c_name == _BASE_TOOL and ("langchain" in c_module or "runner" in c_module):
136
+ return True
137
+ except (AttributeError, TypeError):
138
+ pass
139
+ return False
140
+
9
141
 
10
142
  def is_langchain_tool(ref: Any) -> bool:
11
143
  """Check if ref is a LangChain BaseTool class or instance.
@@ -20,14 +152,150 @@ def is_langchain_tool(ref: Any) -> bool:
20
152
  Returns:
21
153
  True if ref is a LangChain BaseTool class or instance.
22
154
  """
155
+ if ref is None:
156
+ return False
157
+
158
+ # 1. Standard check (preferred)
159
+ if _check_langchain_standard(ref):
160
+ return True
161
+
162
+ # 2. Name-based check (robust fallback for mocks and re-imports)
163
+ return _check_langchain_fallback(ref)
164
+
165
+
166
+ def is_aip_agents_tool(ref: Any) -> bool:
167
+ """Check if ref is an aip-agents tool class or instance.
168
+
169
+ Args:
170
+ ref: Object to check.
171
+
172
+ Returns:
173
+ True if ref is from aip_agents.tools package.
174
+ """
23
175
  try:
24
- from langchain_core.tools import BaseTool # noqa: PLC0415
176
+ # Check class module
177
+ if isinstance(ref, type):
178
+ return ref.__module__.startswith(_AIP_AGENTS_TOOLS)
179
+
180
+ # Check instance class
181
+ if hasattr(ref, "__class__"):
182
+ return ref.__class__.__module__.startswith(_AIP_AGENTS_TOOLS)
183
+
184
+ return False
185
+ except (AttributeError, TypeError):
186
+ return False
187
+
25
188
 
26
- if isinstance(ref, type) and issubclass(ref, BaseTool):
27
- return True
28
- if isinstance(ref, BaseTool):
29
- return True
30
- except ImportError:
189
+ def _get_discovered_classes_from_module(module: Any) -> list[type]:
190
+ """Extract BaseTool subclasses from a module."""
191
+ discovered_classes = []
192
+ for attr_name in dir(module):
193
+ if attr_name.startswith("_"):
194
+ continue
195
+
196
+ try:
197
+ attr = getattr(module, attr_name)
198
+ if inspect.isclass(attr) and is_langchain_tool(attr):
199
+ # Ensure it's not the BaseTool class itself
200
+ if getattr(attr, "__name__", None) != _BASE_TOOL:
201
+ discovered_classes.append(attr)
202
+ except Exception: # pylint: disable=broad-except
203
+ continue
204
+ return discovered_classes
205
+
206
+
207
+ def _import_and_map_module(module_name: str, tools_map: dict[str, type]) -> None:
208
+ """Import a single module and extract its tools."""
209
+ try:
210
+ module = importlib.import_module(module_name)
211
+ classes = _get_discovered_classes_from_module(module)
212
+ for tool_class in classes:
213
+ name = get_tool_name(tool_class)
214
+ if name:
215
+ tools_map[name] = tool_class
216
+ except Exception: # pylint: disable=broad-except
217
+ # Broad catch to skip broken modules during discovery
31
218
  pass
32
219
 
220
+
221
+ def _walk_and_map_package(package: Any, tools_map: dict[str, type]) -> None:
222
+ """Walk through a package and map all tools found."""
223
+ try:
224
+ # Walk packages using the package's path and name
225
+ for _, module_name, _ in pkgutil.walk_packages(package.__path__, package.__name__ + "."):
226
+ if _should_skip_module(module_name):
227
+ continue # pragma: no cover
228
+
229
+ _import_and_map_module(module_name, tools_map)
230
+ except Exception: # pylint: disable=broad-except
231
+ # Broad catch for walk_packages failure
232
+ pass
233
+
234
+
235
+ def _get_all_aip_agents_tools() -> dict[str, type]:
236
+ """Discover and map all tools in aip_agents.tools (once per session)."""
237
+ global _DISCOVERED_TOOLS # pylint: disable=global-statement
238
+ if _DISCOVERED_TOOLS is None:
239
+ _DISCOVERED_TOOLS = {}
240
+ try:
241
+ package = importlib.import_module(_AIP_AGENTS_TOOLS)
242
+ if hasattr(package, "__path__"):
243
+ _walk_and_map_package(package, _DISCOVERED_TOOLS)
244
+ except (ImportError, AttributeError):
245
+ pass
246
+ return _DISCOVERED_TOOLS
247
+
248
+
249
+ @lru_cache(maxsize=128)
250
+ def find_aip_agents_tool_class(name: str) -> type | None:
251
+ """Find and return a native tool class by tool name.
252
+
253
+ Searches aip_agents.tools submodules for BaseTool subclasses
254
+ with matching 'name' attribute. Uses caching to improve performance.
255
+
256
+ Note:
257
+ Results are discovered once per session and cached. If tools are
258
+ dynamically added to the path after the first call, they may not
259
+ be discovered until the session restarts.
260
+
261
+ Args:
262
+ name (str): The tool name to search for (e.g., "google_serper").
263
+
264
+ Returns:
265
+ type|None: The discovered tool class, or None if not found.
266
+
267
+ Examples:
268
+ >>> find_aip_agents_tool_class("google_serper")
269
+ <class 'aip_agents.tools.web_search.serper_tool.GoogleSerperTool'>
270
+
271
+ >>> find_aip_agents_tool_class("nonexistent")
272
+ None
273
+ """
274
+ return _get_all_aip_agents_tools().get(name)
275
+
276
+
277
+ def clear_discovery_cache() -> None:
278
+ """Clear the tool discovery cache (internal use for testing)."""
279
+ global _DISCOVERED_TOOLS # pylint: disable=global-statement
280
+ _DISCOVERED_TOOLS = None
281
+ find_aip_agents_tool_class.cache_clear()
282
+
283
+
284
+ def is_tool_plugin_decorator(decorator: ast.expr) -> bool:
285
+ """Check if an AST decorator node is @tool_plugin.
286
+
287
+ Shared by:
288
+ - ToolBundler._has_tool_plugin_decorator() (for bundling)
289
+ - ImportResolver._is_tool_plugin_decorator() (for import resolution)
290
+
291
+ Args:
292
+ decorator: AST decorator expression node to check.
293
+
294
+ Returns:
295
+ True if decorator is @tool_plugin.
296
+ """
297
+ if isinstance(decorator, ast.Name) and decorator.id == "tool_plugin":
298
+ return True
299
+ if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Name) and decorator.func.id == "tool_plugin":
300
+ return True
33
301
  return False
@@ -0,0 +1,140 @@
1
+ """Helpers for local tool output storage setup.
2
+
3
+ This module bridges agent_config.tool_output_sharing to ToolOutputManager
4
+ for local execution without modifying aip-agents.
5
+
6
+ Authors:
7
+ Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from typing import Any
14
+
15
+ from gllm_core.utils import LoggerManager
16
+
17
+ logger = LoggerManager().get_logger(__name__)
18
+
19
+
20
+ def build_tool_output_manager(agent_name: str, agent_config: dict[str, Any]) -> Any | None:
21
+ """Build a ToolOutputManager for local tool output sharing.
22
+
23
+ Args:
24
+ agent_name: Name of the agent whose tool outputs will be stored.
25
+ agent_config: Agent configuration that may enable tool output sharing and contain task_id.
26
+
27
+ Returns:
28
+ A ToolOutputManager instance when tool output sharing is enabled and
29
+ dependencies are available, otherwise ``None``.
30
+ """
31
+ tool_output_sharing_enabled = agent_config.get("tool_output_sharing", False)
32
+ if not tool_output_sharing_enabled:
33
+ return None
34
+
35
+ try:
36
+ from aip_agents.storage.clients.minio_client import MinioConfig, MinioObjectStorage # noqa: PLC0415
37
+ from aip_agents.storage.providers.memory import InMemoryStorageProvider # noqa: PLC0415
38
+ from aip_agents.storage.providers.object_storage import ObjectStorageProvider # noqa: PLC0415
39
+ from aip_agents.utils.langgraph.tool_output_management import ( # noqa: PLC0415
40
+ ToolOutputConfig,
41
+ ToolOutputManager,
42
+ )
43
+ except ImportError:
44
+ logger.warning("Tool output sharing requested but aip-agents is unavailable; skipping.")
45
+ return None
46
+
47
+ task_id = agent_config.get("task_id")
48
+
49
+ storage_provider = _build_tool_output_storage_provider(
50
+ agent_name=agent_name,
51
+ task_id=task_id,
52
+ minio_config_cls=MinioConfig,
53
+ minio_client_cls=MinioObjectStorage,
54
+ object_storage_provider_cls=ObjectStorageProvider,
55
+ memory_storage_provider_cls=InMemoryStorageProvider,
56
+ )
57
+ tool_output_config = _build_tool_output_config(storage_provider, ToolOutputConfig)
58
+ return ToolOutputManager(tool_output_config)
59
+
60
+
61
+ def _build_tool_output_storage_provider(
62
+ agent_name: str,
63
+ task_id: str | None,
64
+ minio_config_cls: Any,
65
+ minio_client_cls: Any,
66
+ object_storage_provider_cls: Any,
67
+ memory_storage_provider_cls: Any,
68
+ ) -> Any:
69
+ """Create a storage provider for tool output sharing.
70
+
71
+ Args:
72
+ agent_name: Name of the agent whose tool outputs are stored.
73
+ task_id: Optional task identifier for coordination context.
74
+ minio_config_cls: Class exposing a ``from_env`` constructor for MinIO config.
75
+ minio_client_cls: MinIO client class used to talk to the object store.
76
+ object_storage_provider_cls: Storage provider wrapping the MinIO client.
77
+ memory_storage_provider_cls: In-memory provider used as a fallback.
78
+
79
+ Returns:
80
+ An instance of ``object_storage_provider_cls`` when MinIO initialization
81
+ succeeds, otherwise an instance of ``memory_storage_provider_cls``.
82
+ """
83
+ try:
84
+ config_obj = minio_config_cls.from_env()
85
+ minio_client = minio_client_cls(config=config_obj)
86
+ prefix = _build_tool_output_prefix(agent_name, task_id)
87
+ return object_storage_provider_cls(client=minio_client, prefix=prefix, use_json=False)
88
+ except Exception as exc:
89
+ logger.warning("Failed to initialize MinIO for tool outputs: %s. Using in-memory storage.", exc)
90
+ return memory_storage_provider_cls()
91
+
92
+
93
+ def _build_tool_output_prefix(agent_name: str, task_id: str | None) -> str:
94
+ """Build object storage prefix for tool outputs in local mode.
95
+
96
+ Args:
97
+ agent_name: Name of the agent whose outputs are stored.
98
+ task_id: Optional task identifier for coordination context.
99
+
100
+ Returns:
101
+ Object storage key prefix dedicated to the provided agent.
102
+ """
103
+ if task_id:
104
+ return f"tool-outputs/tasks/{task_id}/agents/{agent_name}/"
105
+ return f"tool-outputs/agents/{agent_name}/"
106
+
107
+
108
+ def _build_tool_output_config(storage_provider: Any, config_cls: Any) -> Any:
109
+ """Build ToolOutputConfig using env vars, with safe defaults.
110
+
111
+ Args:
112
+ storage_provider: Provider that will persist tool outputs.
113
+ config_cls: Tool output configuration class to instantiate.
114
+
115
+ Returns:
116
+ A configured ``config_cls`` instance ready for ToolOutputManager use.
117
+ """
118
+
119
+ def safe_int_conversion(env_var: str, default: str) -> int:
120
+ """Convert an environment variable to int with a fallback default.
121
+
122
+ Args:
123
+ env_var: Environment variable name to read.
124
+ default: Default string value used when parsing fails.
125
+
126
+ Returns:
127
+ Integer representation of the environment variable or the default.
128
+ """
129
+ try:
130
+ return int(os.getenv(env_var, default))
131
+ except (ValueError, TypeError):
132
+ logger.warning("Invalid value for %s, using default: %s", env_var, default)
133
+ return int(default)
134
+
135
+ return config_cls(
136
+ max_stored_outputs=safe_int_conversion("TOOL_OUTPUT_MAX_STORED", "200"),
137
+ max_age_minutes=safe_int_conversion("TOOL_OUTPUT_MAX_AGE_MINUTES", str(24 * 60)),
138
+ cleanup_interval=safe_int_conversion("TOOL_OUTPUT_CLEANUP_INTERVAL", "50"),
139
+ storage_provider=storage_provider,
140
+ )
@@ -1,43 +1,45 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: glaip-sdk
3
- Version: 0.6.5b6
4
- Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
3
+ Version: 0.7.12
4
+ Summary: Python SDK and CLI for GL AIP (GDP Labs AI Agent Package) - Build, run, and manage AI agents
5
+ Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
5
6
  License: MIT
6
- Author: Raymond Christopher
7
- Author-email: raymond.christopher@gdplabs.id
8
- Requires-Python: >=3.11,<3.13
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Provides-Extra: dev
14
- Requires-Dist: aip-agents-binary (>=0.5.0)
15
- Requires-Dist: click (>=8.2.0,<8.3.0)
16
- Requires-Dist: gllm-core-binary (>=0.1.0)
17
- Requires-Dist: gllm-tools-binary (>=0.1.3)
18
- Requires-Dist: httpx (>=0.28.1)
19
- Requires-Dist: langchain-core (>=0.3.0)
20
- Requires-Dist: packaging (>=23.2)
21
- Requires-Dist: pre-commit (>=4.3.0) ; extra == "dev"
22
- Requires-Dist: pydantic (>=2.0.0)
23
- Requires-Dist: pytest (>=7.0.0) ; extra == "dev"
24
- Requires-Dist: pytest-asyncio (>=0.23.6) ; extra == "dev"
25
- Requires-Dist: pytest-cov (>=4.0.0) ; extra == "dev"
26
- Requires-Dist: pytest-dotenv (>=0.5.2) ; extra == "dev"
27
- Requires-Dist: pytest-timeout (>=2.3.1) ; extra == "dev"
28
- Requires-Dist: pytest-xdist (>=3.8.0) ; extra == "dev"
29
- Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
30
- Requires-Dist: pyyaml (>=6.0.0)
31
- Requires-Dist: questionary (>=2.1.0,<3.0.0)
32
- Requires-Dist: readchar (>=4.2.1,<5.0.0)
33
- Requires-Dist: rich (>=13.0.0)
34
- Requires-Dist: ruff (>=0.14.0) ; extra == "dev"
35
- Requires-Dist: textual (>=0.52.0)
7
+ Requires-Python: <3.13,>=3.11
36
8
  Description-Content-Type: text/markdown
9
+ Requires-Dist: httpx>=0.28.1
10
+ Requires-Dist: pydantic>=2.0.0
11
+ Requires-Dist: pyyaml>=6.0.0
12
+ Requires-Dist: python-dotenv<2.0.0,>=1.1.1
13
+ Requires-Dist: readchar<5.0.0,>=4.2.1
14
+ Requires-Dist: questionary<3.0.0,>=2.1.0
15
+ Requires-Dist: click<8.3.0,>=8.2.0
16
+ Requires-Dist: rich>=13.0.0
17
+ Requires-Dist: packaging>=23.2
18
+ Requires-Dist: textual>=0.52.0
19
+ Requires-Dist: gllm-core-binary>=0.1.0
20
+ Requires-Dist: langchain-core>=0.3.0
21
+ Requires-Dist: gllm-tools-binary>=0.1.3
22
+ Provides-Extra: local
23
+ Requires-Dist: aip-agents-binary[local]>=0.5.23; (python_version >= "3.11" and python_version < "3.13") and extra == "local"
24
+ Provides-Extra: memory
25
+ Requires-Dist: aip-agents-binary[memory]>=0.5.23; (python_version >= "3.11" and python_version < "3.13") and extra == "memory"
26
+ Provides-Extra: privacy
27
+ Requires-Dist: aip-agents-binary[privacy]>=0.5.23; (python_version >= "3.11" and python_version < "3.13") and extra == "privacy"
28
+ Provides-Extra: guardrails
29
+ Requires-Dist: aip-agents-binary[guardrails]>=0.5.23; (python_version >= "3.11" and python_version < "3.13") and extra == "guardrails"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
+ Requires-Dist: pytest-dotenv>=0.5.2; extra == "dev"
34
+ Requires-Dist: pre-commit>=4.3.0; extra == "dev"
35
+ Requires-Dist: pytest-xdist>=3.8.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.23.6; extra == "dev"
37
+ Requires-Dist: pytest-timeout>=2.3.1; extra == "dev"
38
+ Requires-Dist: ruff>=0.14.0; extra == "dev"
37
39
 
38
40
  # GL AIP — GDP Labs AI Agents Package
39
41
 
40
- [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
42
+ [![Python 3.11-3.12](https://img.shields.io/badge/python-3.11--3.12-blue.svg)](https://www.python.org/downloads/)
41
43
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
42
44
 
43
45
  GL stands for **GDP Labs**—GL AIP is our AI Agents Package for building, running, and operating agents.
@@ -48,15 +50,26 @@ GL stands for **GDP Labs**—GL AIP is our AI Agents Package for building, runni
48
50
 
49
51
  ### Installation
50
52
 
53
+ Installing `glaip-sdk` provides both the **Python SDK** and the **`aip` CLI command** in a single package.
54
+
51
55
  ```bash
52
56
  # Using pip (recommended)
53
57
  pip install --upgrade glaip-sdk
54
58
 
55
59
  # Using uv (fast alternative)
56
60
  uv tool install glaip-sdk
61
+
62
+ # Using pipx (CLI-focused, isolated environment)
63
+ pipx install glaip-sdk
57
64
  ```
58
65
 
59
- **Requirements**: Python 3.10+
66
+ **Requirements**: Python 3.11 or 3.12
67
+
68
+ **Updating**: The `aip` CLI automatically detects your installation method and uses the correct update command:
69
+
70
+ - If installed via `pip`: Uses `pip install --upgrade glaip-sdk`
71
+ - If installed via `uv tool install`: Uses `uv tool install --upgrade glaip-sdk`
72
+ - You can also update manually using the same command you used to install
60
73
 
61
74
  ## 🐍 Hello World - Python SDK
62
75
 
@@ -133,7 +146,7 @@ print("--- Stream complete ---")
133
146
 
134
147
  🎉 **SDK Success!** You're now ready to build AI-powered applications with Python.
135
148
 
136
- ---
149
+ ______________________________________________________________________
137
150
 
138
151
  ## 💻 Hello World - CLI
139
152
 
@@ -226,4 +239,3 @@ The script:
226
239
  - Resets module metadata afterwards so your environment remains untouched.
227
240
 
228
241
  You should see the Rich banner re-render with the mocked version (and optional marker) at the end of the run.
229
-