glaip-sdk 0.0.0b99__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 +52 -0
- glaip_sdk/_version.py +81 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1227 -0
- glaip_sdk/branding.py +211 -0
- glaip_sdk/cli/__init__.py +9 -0
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +78 -0
- glaip_sdk/cli/auth.py +705 -0
- glaip_sdk/cli/commands/__init__.py +5 -0
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +895 -0
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +67 -0
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +192 -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 +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +355 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +112 -0
- glaip_sdk/cli/main.py +686 -0
- 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 +68 -0
- glaip_sdk/cli/rich_helpers.py +27 -0
- glaip_sdk/cli/slash/__init__.py +15 -0
- glaip_sdk/cli/slash/accounts_controller.py +580 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +285 -0
- glaip_sdk/cli/slash/prompt.py +256 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +1724 -0
- glaip_sdk/cli/slash/tui/__init__.py +34 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +147 -0
- glaip_sdk/cli/slash/tui/context.py +59 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +86 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +123 -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 +374 -0
- glaip_sdk/cli/update_notifier.py +369 -0
- glaip_sdk/cli/validators.py +238 -0
- glaip_sdk/client/__init__.py +12 -0
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +1353 -0
- glaip_sdk/client/base.py +502 -0
- glaip_sdk/client/main.py +253 -0
- glaip_sdk/client/mcps.py +401 -0
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/payloads/agent/requests.py +495 -0
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +747 -0
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +690 -0
- glaip_sdk/client/validators.py +198 -0
- glaip_sdk/config/constants.py +52 -0
- glaip_sdk/exceptions.py +113 -0
- glaip_sdk/hitl/__init__.py +15 -0
- glaip_sdk/hitl/local.py +151 -0
- 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 +107 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/schedule.py +224 -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 +393 -0
- glaip_sdk/rich_components.py +125 -0
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +112 -0
- glaip_sdk/runner/langgraph.py +870 -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/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +466 -0
- glaip_sdk/utils/__init__.py +86 -0
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +194 -0
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +486 -0
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +135 -0
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +61 -0
- glaip_sdk/utils/import_export.py +168 -0
- glaip_sdk/utils/import_resolver.py +530 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -0
- glaip_sdk/utils/rendering/formatting.py +264 -0
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/layout/panels.py +156 -0
- 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 +85 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +55 -0
- glaip_sdk/utils/rendering/renderer/base.py +1082 -0
- glaip_sdk/utils/rendering/renderer/config.py +27 -0
- glaip_sdk/utils/rendering/renderer/console.py +55 -0
- glaip_sdk/utils/rendering/renderer/debug.py +178 -0
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +202 -0
- 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 +195 -0
- glaip_sdk/utils/run_renderer.py +41 -0
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +424 -0
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +264 -0
- glaip_sdk-0.0.0b99.dist-info/METADATA +239 -0
- glaip_sdk-0.0.0b99.dist-info/RECORD +207 -0
- glaip_sdk-0.0.0b99.dist-info/WHEEL +5 -0
- glaip_sdk-0.0.0b99.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.0.0b99.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""Tool registry for glaip_sdk.
|
|
2
|
+
|
|
3
|
+
This module provides the ToolRegistry that caches deployed tools
|
|
4
|
+
to avoid redundant API calls when deploying agents with tools.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from glaip_sdk.registry.base import BaseRegistry
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from glaip_sdk.tools import Tool
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ToolRegistry(BaseRegistry["Tool"]):
|
|
24
|
+
"""Registry for tools.
|
|
25
|
+
|
|
26
|
+
Resolves tool references to glaip_sdk.models.Tool objects.
|
|
27
|
+
Caches results to avoid redundant API calls and duplicate uploads.
|
|
28
|
+
|
|
29
|
+
Handles:
|
|
30
|
+
- Tool classes (LangChain BaseTool subclasses) → upload, cache, return Tool
|
|
31
|
+
- glaip_sdk.models.Tool → return as-is (uses tool.id)
|
|
32
|
+
- String names → lookup on platform, cache, return Tool
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
_cache: Internal cache mapping names to Tool objects.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> registry = get_tool_registry()
|
|
39
|
+
>>> tool = registry.resolve(WebSearchTool)
|
|
40
|
+
>>> print(tool.id)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def _get_name_from_model_fields(self, ref: type) -> str | None:
|
|
44
|
+
"""Extract name from Pydantic model_fields if available."""
|
|
45
|
+
model_fields = getattr(ref, "model_fields", {})
|
|
46
|
+
if "name" not in model_fields:
|
|
47
|
+
return None
|
|
48
|
+
field_info = model_fields["name"]
|
|
49
|
+
default = getattr(field_info, "default", None)
|
|
50
|
+
return default if isinstance(default, str) else None
|
|
51
|
+
|
|
52
|
+
def _get_string_attr(self, obj: Any, attr: str) -> str | None:
|
|
53
|
+
"""Get attribute if it's a string, otherwise None."""
|
|
54
|
+
value = getattr(obj, attr, None)
|
|
55
|
+
return value if isinstance(value, str) else None
|
|
56
|
+
|
|
57
|
+
def _extract_name_from_instance(self, ref: Any) -> str | None:
|
|
58
|
+
"""Extract name from a non-type instance.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
ref: The instance to extract name from.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The extracted name, or None if not found.
|
|
65
|
+
"""
|
|
66
|
+
if isinstance(ref, type):
|
|
67
|
+
return None
|
|
68
|
+
return self._get_string_attr(ref, "name")
|
|
69
|
+
|
|
70
|
+
def _extract_name_from_class(self, ref: Any) -> str | None:
|
|
71
|
+
"""Extract name from a class.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
ref: The class to extract name from.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The extracted name, or None if not found.
|
|
78
|
+
"""
|
|
79
|
+
if not isinstance(ref, type):
|
|
80
|
+
return None
|
|
81
|
+
return self._get_string_attr(ref, "name") or self._get_name_from_model_fields(ref)
|
|
82
|
+
|
|
83
|
+
def _extract_name(self, ref: Any) -> str:
|
|
84
|
+
"""Extract tool name from a reference.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
ref: A tool class, instance, dict, or string name.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The extracted tool name.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If name cannot be extracted from the reference.
|
|
94
|
+
"""
|
|
95
|
+
# Lazy import to avoid circular dependency
|
|
96
|
+
from glaip_sdk.tools.base import Tool # noqa: PLC0415
|
|
97
|
+
|
|
98
|
+
if isinstance(ref, str):
|
|
99
|
+
return ref
|
|
100
|
+
|
|
101
|
+
# Tool instance (from Tool.from_langchain() or Tool.from_native())
|
|
102
|
+
if isinstance(ref, Tool):
|
|
103
|
+
return ref.get_name()
|
|
104
|
+
|
|
105
|
+
# Dict from API response - extract name or id
|
|
106
|
+
if isinstance(ref, dict):
|
|
107
|
+
return ref.get("name") or ref.get("id") or ""
|
|
108
|
+
|
|
109
|
+
# Tool instance (not a class) with name attribute
|
|
110
|
+
name = self._extract_name_from_instance(ref)
|
|
111
|
+
if name:
|
|
112
|
+
return name
|
|
113
|
+
|
|
114
|
+
# Tool class - try direct attribute first, then model_fields
|
|
115
|
+
name = self._extract_name_from_class(ref)
|
|
116
|
+
if name:
|
|
117
|
+
return name
|
|
118
|
+
|
|
119
|
+
raise ValueError(f"Cannot extract name from: {ref}")
|
|
120
|
+
|
|
121
|
+
def _cache_tool(self, tool: Tool, name: str) -> None:
|
|
122
|
+
"""Cache a tool by name and ID if available.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
tool: The tool to cache.
|
|
126
|
+
name: The tool name.
|
|
127
|
+
"""
|
|
128
|
+
self._cache[name] = tool
|
|
129
|
+
if hasattr(tool, "id") and tool.id:
|
|
130
|
+
self._cache[tool.id] = tool
|
|
131
|
+
|
|
132
|
+
def _resolve_tool_instance(self, ref: Any, name: str) -> Tool | None:
|
|
133
|
+
"""Resolve a ToolClass instance.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
ref: The ToolClass instance to resolve.
|
|
137
|
+
name: The extracted tool name.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The resolved tool, or None if not a ToolClass instance.
|
|
141
|
+
"""
|
|
142
|
+
# Lazy imports to avoid circular dependency
|
|
143
|
+
from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
|
|
144
|
+
from glaip_sdk.tools.base import ToolType # noqa: PLC0415
|
|
145
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
146
|
+
from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
|
|
147
|
+
|
|
148
|
+
# Use try/except to handle mocked Tool class in tests
|
|
149
|
+
try:
|
|
150
|
+
is_tool_instance = isinstance(ref, ToolClass)
|
|
151
|
+
except TypeError:
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
if not is_tool_instance:
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
# If Tool has an ID, it's already deployed - return as-is
|
|
158
|
+
if ref.id is not None:
|
|
159
|
+
logger.debug("Caching already deployed tool: %s", name)
|
|
160
|
+
self._cache_tool(ref, name)
|
|
161
|
+
return ref
|
|
162
|
+
|
|
163
|
+
# Tool.from_native() - look up on platform
|
|
164
|
+
if ref.tool_type == ToolType.NATIVE:
|
|
165
|
+
logger.info("Looking up native tool: %s", name)
|
|
166
|
+
tool = find_tool(name)
|
|
167
|
+
if tool:
|
|
168
|
+
self._cache[name] = tool
|
|
169
|
+
return tool
|
|
170
|
+
raise ValueError(f"Native tool not found on platform: {name}")
|
|
171
|
+
|
|
172
|
+
# Tool.from_langchain() - upload the tool_class
|
|
173
|
+
if ref.tool_class is not None:
|
|
174
|
+
logger.info("Uploading custom tool: %s", name)
|
|
175
|
+
tool = update_or_create_tool(ref.tool_class)
|
|
176
|
+
self._cache_tool(tool, name)
|
|
177
|
+
return tool
|
|
178
|
+
|
|
179
|
+
# Unresolvable Tool instance - neither native nor has tool_class
|
|
180
|
+
raise ValueError(
|
|
181
|
+
f"Cannot resolve Tool instance: {ref}. "
|
|
182
|
+
f"Tool has no id, is not NATIVE type, and has no tool_class. "
|
|
183
|
+
f"Ensure Tool is created via Tool.from_native() or Tool.from_langchain()."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def _resolve_deployed_tool(self, ref: Any, name: str) -> Tool | None:
|
|
187
|
+
"""Resolve an already deployed tool (has id/name attributes).
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
ref: The tool reference to resolve.
|
|
191
|
+
name: The extracted tool name.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The resolved tool, or None if not a deployed tool.
|
|
195
|
+
"""
|
|
196
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
197
|
+
|
|
198
|
+
# Already deployed tool (not a ToolClass, but has id/name)
|
|
199
|
+
# This handles API response objects and backward compatibility
|
|
200
|
+
if not (hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type)):
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
if ref.id is not None:
|
|
204
|
+
logger.debug("Caching already deployed tool: %s", name)
|
|
205
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
206
|
+
self._cache_tool(ref, name)
|
|
207
|
+
return ref
|
|
208
|
+
|
|
209
|
+
# Tool without ID (backward compatibility) - look up on platform
|
|
210
|
+
logger.info("Looking up native tool: %s", name)
|
|
211
|
+
tool = find_tool(name)
|
|
212
|
+
if tool:
|
|
213
|
+
# Use _cache_tool to cache by both name and ID if available
|
|
214
|
+
self._cache_tool(tool, name)
|
|
215
|
+
return tool
|
|
216
|
+
raise ValueError(f"Native tool not found on platform: {name}")
|
|
217
|
+
|
|
218
|
+
def _resolve_custom_tool(self, ref: Any, name: str) -> Tool | None:
|
|
219
|
+
"""Resolve a custom tool class.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
ref: The tool reference to resolve.
|
|
223
|
+
name: The extracted tool name.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The resolved tool, or None if not a custom tool.
|
|
227
|
+
"""
|
|
228
|
+
from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
|
|
229
|
+
|
|
230
|
+
if not self._is_custom_tool(ref):
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
logger.info("Uploading custom tool: %s", name)
|
|
234
|
+
tool = update_or_create_tool(ref)
|
|
235
|
+
self._cache_tool(tool, name)
|
|
236
|
+
return tool
|
|
237
|
+
|
|
238
|
+
def _resolve_dict_tool(self, ref: Any, name: str) -> Tool | None:
|
|
239
|
+
"""Resolve a tool from a dict (API response).
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
ref: The dict to resolve.
|
|
243
|
+
name: The extracted tool name.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
The resolved tool, or None if not a dict.
|
|
247
|
+
"""
|
|
248
|
+
from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
|
|
249
|
+
|
|
250
|
+
if not isinstance(ref, dict):
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
tool_id = ref.get("id")
|
|
254
|
+
if tool_id:
|
|
255
|
+
tool = ToolClass(id=tool_id, name=ref.get("name", ""))
|
|
256
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
257
|
+
self._cache_tool(tool, name)
|
|
258
|
+
return tool
|
|
259
|
+
raise ValueError(f"Tool dict missing 'id': {ref}")
|
|
260
|
+
|
|
261
|
+
def _resolve_string_tool(self, ref: Any, name: str) -> Tool | None:
|
|
262
|
+
"""Resolve a tool from a string name.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
ref: The string to resolve.
|
|
266
|
+
name: The extracted tool name.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
The resolved tool, or None if not a string.
|
|
270
|
+
"""
|
|
271
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
272
|
+
|
|
273
|
+
if not isinstance(ref, str):
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
logger.info("Looking up tool by name: %s", name)
|
|
277
|
+
tool = find_tool(name)
|
|
278
|
+
if tool:
|
|
279
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
280
|
+
self._cache_tool(tool, name)
|
|
281
|
+
return tool
|
|
282
|
+
raise ValueError(f"Tool not found on platform: {name}")
|
|
283
|
+
|
|
284
|
+
def _resolve_and_cache(self, ref: Any, name: str) -> Tool:
|
|
285
|
+
"""Resolve tool reference - upload if class, find if string/native.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
ref: The tool reference to resolve.
|
|
289
|
+
name: The extracted tool name.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The resolved glaip_sdk.models.Tool object.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ValueError: If the tool cannot be resolved.
|
|
296
|
+
"""
|
|
297
|
+
# Try each resolution strategy in order
|
|
298
|
+
resolvers = [
|
|
299
|
+
self._resolve_tool_instance,
|
|
300
|
+
self._resolve_deployed_tool,
|
|
301
|
+
self._resolve_custom_tool,
|
|
302
|
+
self._resolve_dict_tool,
|
|
303
|
+
self._resolve_string_tool,
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
for resolver in resolvers:
|
|
307
|
+
result = resolver(ref, name)
|
|
308
|
+
if result is not None:
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
raise ValueError(f"Could not resolve tool reference: {ref}")
|
|
312
|
+
|
|
313
|
+
def _is_custom_tool(self, ref: Any) -> bool:
|
|
314
|
+
"""Check if reference is a custom tool class/instance.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
ref: The reference to check.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
True if ref is a custom tool that needs uploading.
|
|
321
|
+
"""
|
|
322
|
+
try:
|
|
323
|
+
from glaip_sdk.utils.tool_detection import ( # noqa: PLC0415
|
|
324
|
+
is_langchain_tool,
|
|
325
|
+
)
|
|
326
|
+
except ImportError:
|
|
327
|
+
return False
|
|
328
|
+
|
|
329
|
+
return is_langchain_tool(ref)
|
|
330
|
+
|
|
331
|
+
def resolve(self, ref: Any) -> Tool:
|
|
332
|
+
"""Resolve a tool reference to a platform Tool object.
|
|
333
|
+
|
|
334
|
+
Overrides base resolve to handle SDK tools differently.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
ref: The tool reference to resolve.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
The resolved glaip_sdk.models.Tool object.
|
|
341
|
+
"""
|
|
342
|
+
# Check if it's a Tool instance (not a class)
|
|
343
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
|
|
344
|
+
# If Tool has an ID, it's already deployed - return as-is
|
|
345
|
+
if ref.id is not None:
|
|
346
|
+
name = self._extract_name(ref)
|
|
347
|
+
if name not in self._cache:
|
|
348
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
349
|
+
self._cache_tool(ref, name)
|
|
350
|
+
return ref
|
|
351
|
+
|
|
352
|
+
# Tool without ID (e.g., from Tool.from_native()) - needs platform lookup
|
|
353
|
+
# Fall through to normal resolution
|
|
354
|
+
|
|
355
|
+
return super().resolve(ref)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class _ToolRegistrySingleton:
|
|
359
|
+
"""Singleton holder for ToolRegistry to avoid global statement."""
|
|
360
|
+
|
|
361
|
+
_instance: ToolRegistry | None = None
|
|
362
|
+
|
|
363
|
+
@classmethod
|
|
364
|
+
def get_instance(cls) -> ToolRegistry:
|
|
365
|
+
"""Get or create the singleton instance.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
The global ToolRegistry instance.
|
|
369
|
+
"""
|
|
370
|
+
if cls._instance is None:
|
|
371
|
+
cls._instance = ToolRegistry()
|
|
372
|
+
return cls._instance
|
|
373
|
+
|
|
374
|
+
@classmethod
|
|
375
|
+
def reset(cls) -> None:
|
|
376
|
+
"""Reset the singleton instance (for testing)."""
|
|
377
|
+
cls._instance = None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def get_tool_registry() -> ToolRegistry:
|
|
381
|
+
"""Get the singleton ToolRegistry instance.
|
|
382
|
+
|
|
383
|
+
Returns a global ToolRegistry that caches tools across the session.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
The global ToolRegistry instance.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
>>> from glaip_sdk.registry import get_tool_registry
|
|
390
|
+
>>> registry = get_tool_registry()
|
|
391
|
+
>>> tool = registry.resolve("web_search")
|
|
392
|
+
"""
|
|
393
|
+
return _ToolRegistrySingleton.get_instance()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Custom Rich components with copy-friendly defaults.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from rich import box
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AIPPanel(Panel):
|
|
16
|
+
"""Rich Panel configured without vertical borders by default."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, *args, **kwargs):
|
|
19
|
+
"""Initialize AIPPanel with default settings for horizontal borders and padding.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
*args: Positional arguments passed to Panel
|
|
23
|
+
**kwargs: Keyword arguments passed to Panel
|
|
24
|
+
"""
|
|
25
|
+
kwargs.setdefault("box", box.HORIZONTALS)
|
|
26
|
+
kwargs.setdefault("padding", (0, 1))
|
|
27
|
+
super().__init__(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AIPTable(Table):
|
|
31
|
+
"""Rich Table configured without vertical borders by default."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, *args, **kwargs):
|
|
34
|
+
"""Initialize AIPTable with default settings for horizontal borders and no edge padding.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
*args: Positional arguments passed to Table
|
|
38
|
+
**kwargs: Keyword arguments passed to Table
|
|
39
|
+
"""
|
|
40
|
+
kwargs.setdefault("box", box.HORIZONTALS)
|
|
41
|
+
kwargs.setdefault("show_edge", False)
|
|
42
|
+
kwargs.setdefault("pad_edge", False)
|
|
43
|
+
super().__init__(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AIPGrid(Table):
|
|
47
|
+
"""Table-based grid with GL AIP defaults for layout blocks."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
expand: bool = True,
|
|
53
|
+
padding: tuple[int, int] = (0, 1),
|
|
54
|
+
collapse_padding: bool = True,
|
|
55
|
+
):
|
|
56
|
+
"""Initialize AIPGrid with zero-edge borders and optional expansion.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
expand: Whether the grid should expand to fill available width.
|
|
60
|
+
padding: Cell padding for the grid (row, column).
|
|
61
|
+
collapse_padding: Collapse padding between renderables.
|
|
62
|
+
"""
|
|
63
|
+
super().__init__(
|
|
64
|
+
show_header=False,
|
|
65
|
+
show_edge=False,
|
|
66
|
+
pad_edge=False,
|
|
67
|
+
box=None,
|
|
68
|
+
expand=expand,
|
|
69
|
+
padding=padding,
|
|
70
|
+
collapse_padding=collapse_padding,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RemoteRunsTable(AIPTable):
|
|
75
|
+
"""Rich Table for displaying remote agent runs with pagination support."""
|
|
76
|
+
|
|
77
|
+
def __init__(self, *args, **kwargs):
|
|
78
|
+
"""Initialize RemoteRunsTable with columns for run display.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
*args: Positional arguments passed to AIPTable
|
|
82
|
+
**kwargs: Keyword arguments passed to AIPTable
|
|
83
|
+
"""
|
|
84
|
+
kwargs.setdefault("row_styles", ("dim", "none"))
|
|
85
|
+
kwargs.setdefault("show_header", True)
|
|
86
|
+
super().__init__(*args, **kwargs)
|
|
87
|
+
# Add columns for run display
|
|
88
|
+
self.add_column("", width=2, no_wrap=True) # Selection gutter
|
|
89
|
+
self.add_column("Run UUID", style="cyan", width=36, no_wrap=True)
|
|
90
|
+
self.add_column("Type", style="yellow", width=8, no_wrap=True)
|
|
91
|
+
self.add_column("Status", style="magenta", width=12, no_wrap=True)
|
|
92
|
+
self.add_column("Started (UTC)", style="dim", width=20, no_wrap=True)
|
|
93
|
+
self.add_column("Completed (UTC)", style="dim", width=20, no_wrap=True)
|
|
94
|
+
self.add_column("Duration", style="green", width=10, no_wrap=True)
|
|
95
|
+
self.add_column("Input Preview", style="white", width=40, overflow="ellipsis")
|
|
96
|
+
|
|
97
|
+
def add_run_row(
|
|
98
|
+
self,
|
|
99
|
+
run_uuid: str,
|
|
100
|
+
run_type: str,
|
|
101
|
+
status: str,
|
|
102
|
+
started: str,
|
|
103
|
+
completed: str,
|
|
104
|
+
duration: str,
|
|
105
|
+
preview: str,
|
|
106
|
+
*,
|
|
107
|
+
selected: bool = False,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Append a run row with optional selection styling."""
|
|
110
|
+
gutter = Text("› ", style="bold bright_cyan") if selected else Text(" ")
|
|
111
|
+
row_style = "reverse" if selected else None
|
|
112
|
+
self.add_row(
|
|
113
|
+
gutter,
|
|
114
|
+
run_uuid,
|
|
115
|
+
run_type,
|
|
116
|
+
status,
|
|
117
|
+
started,
|
|
118
|
+
completed,
|
|
119
|
+
duration,
|
|
120
|
+
preview,
|
|
121
|
+
style=row_style,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
__all__ = ["AIPPanel", "AIPTable", "AIPGrid", "RemoteRunsTable"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Local agent execution runners.
|
|
2
|
+
|
|
3
|
+
This module provides runners for executing glaip-sdk agents locally
|
|
4
|
+
without requiring the AIP backend server. The primary runner is
|
|
5
|
+
LangGraphRunner which uses the aip-agents library.
|
|
6
|
+
|
|
7
|
+
To use local execution, install with the [local] extra:
|
|
8
|
+
pip install "glaip-sdk[local]"
|
|
9
|
+
|
|
10
|
+
Authors:
|
|
11
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from glaip_sdk.runner import get_default_runner
|
|
15
|
+
>>> from glaip_sdk.agents import Agent
|
|
16
|
+
>>>
|
|
17
|
+
>>> agent = Agent(name="my-agent", instruction="You are helpful.")
|
|
18
|
+
>>> runner = get_default_runner()
|
|
19
|
+
>>> result = runner.run(agent, "Hello!")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from glaip_sdk.runner.deps import (
|
|
23
|
+
LOCAL_RUNTIME_AVAILABLE,
|
|
24
|
+
check_local_runtime_available,
|
|
25
|
+
get_local_runtime_missing_message,
|
|
26
|
+
)
|
|
27
|
+
from glaip_sdk.runner.langgraph import LangGraphRunner
|
|
28
|
+
|
|
29
|
+
# Default runner instance
|
|
30
|
+
_default_runner: LangGraphRunner | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_default_runner() -> LangGraphRunner:
|
|
34
|
+
"""Get the default runner instance for local agent execution.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The default LangGraphRunner instance.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
RuntimeError: If local runtime dependencies are not available.
|
|
41
|
+
"""
|
|
42
|
+
global _default_runner
|
|
43
|
+
|
|
44
|
+
if not check_local_runtime_available():
|
|
45
|
+
raise RuntimeError(get_local_runtime_missing_message())
|
|
46
|
+
|
|
47
|
+
if _default_runner is None:
|
|
48
|
+
_default_runner = LangGraphRunner()
|
|
49
|
+
|
|
50
|
+
return _default_runner
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"LOCAL_RUNTIME_AVAILABLE",
|
|
55
|
+
"LangGraphRunner",
|
|
56
|
+
"check_local_runtime_available",
|
|
57
|
+
"get_default_runner",
|
|
58
|
+
"get_local_runtime_missing_message",
|
|
59
|
+
]
|
glaip_sdk/runner/base.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Abstract base class for agent execution runners.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from glaip_sdk.agents.base import Agent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseRunner(ABC):
|
|
17
|
+
"""Abstract base class for agent execution runners.
|
|
18
|
+
|
|
19
|
+
Runners are responsible for executing glaip-sdk Agent instances
|
|
20
|
+
and returning results. Different runner implementations may use
|
|
21
|
+
different execution backends (LangGraph, Google ADK, etc.).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def run(
|
|
26
|
+
self,
|
|
27
|
+
agent: Agent,
|
|
28
|
+
message: str,
|
|
29
|
+
verbose: bool = False,
|
|
30
|
+
runtime_config: dict[str, Any] | None = None,
|
|
31
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
32
|
+
**kwargs: Any,
|
|
33
|
+
) -> str:
|
|
34
|
+
"""Execute agent synchronously and return final response text.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
agent: The glaip_sdk Agent to execute.
|
|
38
|
+
message: The user message to send to the agent.
|
|
39
|
+
verbose: If True, emit debug trace output during execution.
|
|
40
|
+
Defaults to False.
|
|
41
|
+
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
42
|
+
Defaults to None.
|
|
43
|
+
chat_history: Optional list of prior conversation messages.
|
|
44
|
+
Defaults to None.
|
|
45
|
+
**kwargs: Additional keyword arguments passed to the backend.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The final response text from the agent.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
RuntimeError: If execution fails.
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
async def arun(
|
|
57
|
+
self,
|
|
58
|
+
agent: Agent,
|
|
59
|
+
message: str,
|
|
60
|
+
verbose: bool = False,
|
|
61
|
+
runtime_config: dict[str, Any] | None = None,
|
|
62
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
) -> str:
|
|
65
|
+
"""Execute agent asynchronously and return final response text.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
agent: The glaip_sdk Agent to execute.
|
|
69
|
+
message: The user message to send to the agent.
|
|
70
|
+
verbose: If True, emit debug trace output during execution.
|
|
71
|
+
Defaults to False.
|
|
72
|
+
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
73
|
+
Defaults to None.
|
|
74
|
+
chat_history: Optional list of prior conversation messages.
|
|
75
|
+
Defaults to None.
|
|
76
|
+
**kwargs: Additional keyword arguments passed to the backend.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The final response text from the agent.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
RuntimeError: If execution fails.
|
|
83
|
+
"""
|
|
84
|
+
...
|