glaip-sdk 0.1.3__py3-none-any.whl → 0.6.19__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 +44 -4
- glaip_sdk/_version.py +9 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1196 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/auth.py +254 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +213 -73
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +729 -113
- glaip_sdk/cli/commands/mcps.py +241 -72
- glaip_sdk/cli/commands/models.py +11 -5
- glaip_sdk/cli/commands/tools.py +49 -57
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/config.py +48 -4
- 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 +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +35 -19
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +241 -121
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/parsers/__init__.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 +62 -21
- glaip_sdk/cli/slash/prompt.py +21 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +771 -140
- 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 +255 -44
- glaip_sdk/cli/transcript/capture.py +27 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -499
- glaip_sdk/cli/update_notifier.py +14 -5
- glaip_sdk/cli/utils.py +243 -1252
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +45 -9
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +291 -35
- glaip_sdk/client/base.py +1 -0
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +122 -12
- glaip_sdk/client/run_rendering.py +466 -89
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +155 -10
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/hitl/__init__.py +15 -0
- glaip_sdk/hitl/local.py +151 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- 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 +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/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/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +39 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +12 -7
- 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 +5 -30
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +1 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
- glaip_sdk/utils/rendering/renderer/base.py +275 -1476
- glaip_sdk/utils/rendering/renderer/debug.py +26 -20
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +4 -12
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -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/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- 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 +25 -13
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +18 -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 +16 -24
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.19.dist-info}/METADATA +56 -21
- glaip_sdk-0.6.19.dist-info/RECORD +163 -0
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.19.dist-info}/WHEEL +2 -1
- glaip_sdk-0.6.19.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.6.19.dist-info/top_level.txt +1 -0
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.3.dist-info/RECORD +0 -83
- glaip_sdk-0.1.3.dist-info/entry_points.txt +0 -3
glaip_sdk/models/tool.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Tool response model for AIP SDK.
|
|
2
|
+
|
|
3
|
+
This module contains the Pydantic model for Tool API responses.
|
|
4
|
+
This is a pure data model with no runtime behavior.
|
|
5
|
+
|
|
6
|
+
For the runtime Tool class with update/delete methods, use glaip_sdk.tools.Tool.
|
|
7
|
+
|
|
8
|
+
Authors:
|
|
9
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
10
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolResponse(BaseModel):
|
|
17
|
+
"""Pydantic model for Tool API responses.
|
|
18
|
+
|
|
19
|
+
This is a pure data model for deserializing API responses.
|
|
20
|
+
It does NOT have runtime methods (update, delete, get_script).
|
|
21
|
+
|
|
22
|
+
For the runtime Tool class, use glaip_sdk.tools.Tool.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
name: str
|
|
27
|
+
tool_type: str | None = None
|
|
28
|
+
description: str | None = None
|
|
29
|
+
framework: str | None = None
|
|
30
|
+
version: str | None = None
|
|
31
|
+
tool_script: str | None = None
|
|
32
|
+
tool_file: str | None = None
|
|
33
|
+
tags: str | list[str] | None = None
|
|
@@ -4,16 +4,4 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
AgentImportOperation,
|
|
9
|
-
ImportFieldPlan,
|
|
10
|
-
get_import_field_plan,
|
|
11
|
-
list_server_only_fields,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
"AgentImportOperation",
|
|
16
|
-
"ImportFieldPlan",
|
|
17
|
-
"get_import_field_plan",
|
|
18
|
-
"list_server_only_fields",
|
|
19
|
-
]
|
|
7
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Registry package for GL AIP platform.
|
|
2
|
+
|
|
3
|
+
This package provides registries that cache platform objects to avoid
|
|
4
|
+
redundant API calls when deploying multi-agent systems.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from glaip_sdk.registry import get_agent_registry, get_tool_registry
|
|
8
|
+
>>> agent_registry = get_agent_registry()
|
|
9
|
+
>>> tool_registry = get_tool_registry()
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
from glaip_sdk.registry.base import BaseRegistry
|
|
18
|
+
|
|
19
|
+
# Lazy imports to avoid circular dependencies
|
|
20
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
21
|
+
from glaip_sdk.registry.agent import AgentRegistry, get_agent_registry
|
|
22
|
+
from glaip_sdk.registry.mcp import MCPRegistry, get_mcp_registry
|
|
23
|
+
from glaip_sdk.registry.tool import ToolRegistry, get_tool_registry
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"BaseRegistry",
|
|
27
|
+
"AgentRegistry",
|
|
28
|
+
"get_agent_registry",
|
|
29
|
+
"ToolRegistry",
|
|
30
|
+
"get_tool_registry",
|
|
31
|
+
"MCPRegistry",
|
|
32
|
+
"get_mcp_registry",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def __getattr__(name: str) -> type:
|
|
37
|
+
"""Lazy import to avoid circular dependencies."""
|
|
38
|
+
_agent_module = "glaip_sdk.registry.agent"
|
|
39
|
+
_tool_module = "glaip_sdk.registry.tool"
|
|
40
|
+
_mcp_module = "glaip_sdk.registry.mcp"
|
|
41
|
+
|
|
42
|
+
lazy_imports = {
|
|
43
|
+
"AgentRegistry": _agent_module,
|
|
44
|
+
"get_agent_registry": _agent_module,
|
|
45
|
+
"ToolRegistry": _tool_module,
|
|
46
|
+
"get_tool_registry": _tool_module,
|
|
47
|
+
"MCPRegistry": _mcp_module,
|
|
48
|
+
"get_mcp_registry": _mcp_module,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if name in lazy_imports:
|
|
52
|
+
module = importlib.import_module(lazy_imports[name])
|
|
53
|
+
return getattr(module, name)
|
|
54
|
+
|
|
55
|
+
raise AttributeError(f"module 'glaip_sdk.registry' has no attribute '{name}'")
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Agent registry for glaip_sdk.
|
|
2
|
+
|
|
3
|
+
This module provides the AgentRegistry that caches deployed agents
|
|
4
|
+
to avoid redundant API calls when deploying multi-agent systems.
|
|
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.agents import Agent
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AgentRegistry(BaseRegistry["Agent"]):
|
|
24
|
+
"""Registry for agents.
|
|
25
|
+
|
|
26
|
+
Resolves agent references to glaip_sdk.models.Agent objects.
|
|
27
|
+
Caches results to avoid redundant API calls and duplicate deployments.
|
|
28
|
+
|
|
29
|
+
Handles:
|
|
30
|
+
- glaip_sdk.agents.Agent classes → deploy, cache, return Agent
|
|
31
|
+
- glaip_sdk.agents.Agent instances → deploy, cache, return Agent
|
|
32
|
+
- glaip_sdk.models.Agent → return as-is (uses agent.id)
|
|
33
|
+
- String names → lookup on platform, cache, return Agent
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
_cache: Internal cache mapping names to Agent objects.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
>>> registry = get_agent_registry()
|
|
40
|
+
>>> agent = registry.resolve(GreeterAgent) # Returns deployed Agent
|
|
41
|
+
>>> print(agent.id) # "uuid-123"
|
|
42
|
+
>>> print(agent.name) # "greeter_agent"
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def _extract_name(self, ref: Any) -> str:
|
|
46
|
+
"""Extract agent name from a reference.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
ref: An agent class, instance, or string name.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The extracted agent name.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: If name cannot be extracted from the reference.
|
|
56
|
+
"""
|
|
57
|
+
# Lazy import to avoid circular dependency
|
|
58
|
+
from glaip_sdk.agents.base import Agent # noqa: PLC0415
|
|
59
|
+
|
|
60
|
+
# Agent class
|
|
61
|
+
if isinstance(ref, type) and issubclass(ref, Agent):
|
|
62
|
+
return ref().name
|
|
63
|
+
|
|
64
|
+
# Agent instance
|
|
65
|
+
if isinstance(ref, Agent):
|
|
66
|
+
return ref.name
|
|
67
|
+
|
|
68
|
+
# Already deployed agent (glaip_sdk.models.Agent)
|
|
69
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
|
|
70
|
+
return ref.name
|
|
71
|
+
|
|
72
|
+
# String name
|
|
73
|
+
if isinstance(ref, str):
|
|
74
|
+
return ref
|
|
75
|
+
|
|
76
|
+
raise ValueError(f"Cannot extract name from: {ref}")
|
|
77
|
+
|
|
78
|
+
def _resolve_and_cache(self, ref: Any, name: str) -> Agent:
|
|
79
|
+
"""Resolve agent reference - deploy if class/instance, find if string.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
ref: The agent reference to resolve.
|
|
83
|
+
name: The extracted agent name.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The resolved glaip_sdk.models.Agent object.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If the agent cannot be resolved.
|
|
90
|
+
"""
|
|
91
|
+
# Lazy imports to avoid circular dependency
|
|
92
|
+
from glaip_sdk.agents.base import Agent # noqa: PLC0415
|
|
93
|
+
from glaip_sdk.utils.discovery import find_agent # noqa: PLC0415
|
|
94
|
+
|
|
95
|
+
# Agent class
|
|
96
|
+
if isinstance(ref, type) and issubclass(ref, Agent):
|
|
97
|
+
logger.info("Deploying Agent class: %s", name)
|
|
98
|
+
deployed = ref().deploy()
|
|
99
|
+
self._cache[name] = deployed
|
|
100
|
+
return deployed
|
|
101
|
+
|
|
102
|
+
# Agent instance
|
|
103
|
+
if isinstance(ref, Agent):
|
|
104
|
+
logger.info("Deploying Agent instance: %s", name)
|
|
105
|
+
deployed = ref.deploy()
|
|
106
|
+
self._cache[name] = deployed
|
|
107
|
+
return deployed
|
|
108
|
+
|
|
109
|
+
# Already deployed agent (glaip_sdk.models.Agent) - just cache and return
|
|
110
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
|
|
111
|
+
logger.debug("Caching already deployed agent: %s", name)
|
|
112
|
+
self._cache[name] = ref
|
|
113
|
+
return ref
|
|
114
|
+
|
|
115
|
+
# String name - look up on platform
|
|
116
|
+
if isinstance(ref, str):
|
|
117
|
+
logger.info("Looking up agent by name: %s", name)
|
|
118
|
+
agent = find_agent(name)
|
|
119
|
+
if agent:
|
|
120
|
+
self._cache[name] = agent
|
|
121
|
+
return agent
|
|
122
|
+
raise ValueError(f"Agent not found on platform: {name}")
|
|
123
|
+
|
|
124
|
+
raise ValueError(f"Could not resolve agent reference: {ref}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class _AgentRegistrySingleton:
|
|
128
|
+
"""Singleton holder for AgentRegistry to avoid global statement."""
|
|
129
|
+
|
|
130
|
+
_instance: AgentRegistry | None = None
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def get_instance(cls) -> AgentRegistry:
|
|
134
|
+
"""Get or create the singleton instance.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The global AgentRegistry instance.
|
|
138
|
+
"""
|
|
139
|
+
if cls._instance is None:
|
|
140
|
+
cls._instance = AgentRegistry()
|
|
141
|
+
return cls._instance
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def reset(cls) -> None:
|
|
145
|
+
"""Reset the singleton instance (for testing)."""
|
|
146
|
+
cls._instance = None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_agent_registry() -> AgentRegistry:
|
|
150
|
+
"""Get the singleton AgentRegistry instance.
|
|
151
|
+
|
|
152
|
+
Returns a global AgentRegistry that caches agents across the session.
|
|
153
|
+
Use this function to get the registry instead of creating instances directly.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
The global AgentRegistry instance.
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> from glaip_sdk.registry import get_agent_registry
|
|
160
|
+
>>> registry = get_agent_registry()
|
|
161
|
+
>>> agent = registry.resolve("weather_agent")
|
|
162
|
+
>>> print(agent.name)
|
|
163
|
+
"""
|
|
164
|
+
return _AgentRegistrySingleton.get_instance()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Abstract base registry for caching platform objects.
|
|
2
|
+
|
|
3
|
+
This module provides the BaseRegistry abstract class that serves as the
|
|
4
|
+
foundation for type-specific registries (AgentRegistry, ToolRegistry, MCPRegistry).
|
|
5
|
+
|
|
6
|
+
The registry pattern provides:
|
|
7
|
+
- In-memory caching to avoid redundant API calls
|
|
8
|
+
- Transparent resolution of various reference types
|
|
9
|
+
- Simple invalidation and cache management
|
|
10
|
+
|
|
11
|
+
Authors:
|
|
12
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Any, Generic, TypeVar
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BaseRegistry(ABC, Generic[T]):
|
|
28
|
+
"""Abstract base registry for caching platform objects.
|
|
29
|
+
|
|
30
|
+
Provides a caching layer between local code and the AIP platform.
|
|
31
|
+
Subclasses implement type-specific resolution logic.
|
|
32
|
+
|
|
33
|
+
The registry follows a simple flow:
|
|
34
|
+
1. Check if reference is already a platform object → return as-is
|
|
35
|
+
2. Extract name from reference
|
|
36
|
+
3. Check cache → return if found
|
|
37
|
+
4. Resolve via subclass logic → cache and return
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
_cache: Internal cache mapping names to objects.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> class MyRegistry(BaseRegistry):
|
|
44
|
+
... def _extract_name(self, ref: Any) -> str:
|
|
45
|
+
... return ref.name if hasattr(ref, 'name') else str(ref)
|
|
46
|
+
...
|
|
47
|
+
... def _resolve_and_cache(self, ref: Any, name: str) -> MyType:
|
|
48
|
+
... obj = fetch_from_platform(name)
|
|
49
|
+
... self._cache[name] = obj
|
|
50
|
+
... return obj
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self) -> None:
|
|
54
|
+
"""Initialize the registry with an empty cache."""
|
|
55
|
+
self._cache: dict[str, T] = {}
|
|
56
|
+
|
|
57
|
+
def resolve(self, ref: Any) -> T:
|
|
58
|
+
"""Resolve a reference to a platform object.
|
|
59
|
+
|
|
60
|
+
This is the main entry point for the registry. It handles:
|
|
61
|
+
- Cached references (returned from cache)
|
|
62
|
+
- New references (resolved via subclass, then cached)
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
ref: A reference to resolve. Can be a class, string name,
|
|
66
|
+
or platform object depending on the registry type.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
The resolved platform object.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If the reference cannot be resolved.
|
|
73
|
+
"""
|
|
74
|
+
name = self._extract_name(ref)
|
|
75
|
+
|
|
76
|
+
if name in self._cache:
|
|
77
|
+
logger.debug("Cache hit: %s", name)
|
|
78
|
+
return self._cache[name]
|
|
79
|
+
|
|
80
|
+
return self._resolve_and_cache(ref, name)
|
|
81
|
+
|
|
82
|
+
def get(self, name: str) -> T | None:
|
|
83
|
+
"""Get a cached object by name.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
name: The name of the object to retrieve.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The cached object, or None if not found.
|
|
90
|
+
"""
|
|
91
|
+
return self._cache.get(name)
|
|
92
|
+
|
|
93
|
+
def invalidate(self, name: str) -> None:
|
|
94
|
+
"""Remove an object from the cache.
|
|
95
|
+
|
|
96
|
+
Use this to force a re-fetch on the next resolve call.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
name: The name of the object to invalidate.
|
|
100
|
+
"""
|
|
101
|
+
self._cache.pop(name, None)
|
|
102
|
+
logger.debug("Invalidated cache entry: %s", name)
|
|
103
|
+
|
|
104
|
+
def clear(self) -> None:
|
|
105
|
+
"""Clear all cached entries."""
|
|
106
|
+
self._cache.clear()
|
|
107
|
+
logger.debug("Cleared registry cache")
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def _extract_name(self, ref: Any) -> str:
|
|
111
|
+
"""Extract the name from a reference.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
ref: The reference to extract a name from.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
The extracted name string.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: If name cannot be extracted.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def _resolve_and_cache(self, ref: Any, name: str) -> T:
|
|
125
|
+
"""Resolve the reference and cache the result.
|
|
126
|
+
|
|
127
|
+
Subclasses implement type-specific resolution logic here.
|
|
128
|
+
This method MUST cache the result in self._cache[name].
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
ref: The reference to resolve.
|
|
132
|
+
name: The extracted name for caching.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The resolved platform object.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If resolution fails.
|
|
139
|
+
"""
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""MCP registry for glaip_sdk.
|
|
2
|
+
|
|
3
|
+
This module provides the MCPRegistry that caches MCPs (Model Context Protocols)
|
|
4
|
+
to avoid redundant API calls when deploying agents with MCPs.
|
|
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
|
+
from glaip_sdk.utils.resource_refs import is_uuid
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from glaip_sdk.mcps import MCP
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MCPRegistry(BaseRegistry["MCP"]):
|
|
25
|
+
"""Registry for MCPs (Model Context Protocols).
|
|
26
|
+
|
|
27
|
+
Resolves MCP references to glaip_sdk.models.MCP objects.
|
|
28
|
+
Caches results to avoid redundant API calls.
|
|
29
|
+
|
|
30
|
+
Handles:
|
|
31
|
+
- glaip_sdk.models.MCP → return as-is (uses mcp.id)
|
|
32
|
+
- String names/IDs → lookup on platform, cache, return MCP
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
_cache: Internal cache mapping names to MCP objects.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> registry = get_mcp_registry()
|
|
39
|
+
>>> mcp = registry.resolve("arxiv-mcp")
|
|
40
|
+
>>> print(mcp.id)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def _extract_name(self, ref: Any) -> str:
|
|
44
|
+
"""Extract MCP name from a reference.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
ref: An MCP object, dict, or string name.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The extracted MCP name.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If name cannot be extracted from the reference.
|
|
54
|
+
"""
|
|
55
|
+
# String name
|
|
56
|
+
if isinstance(ref, str):
|
|
57
|
+
return ref
|
|
58
|
+
|
|
59
|
+
# Dict from API response - extract name or id
|
|
60
|
+
if isinstance(ref, dict):
|
|
61
|
+
return ref.get("name") or ref.get("id") or ""
|
|
62
|
+
|
|
63
|
+
# Already resolved MCP (glaip_sdk.models.MCP)
|
|
64
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
|
|
65
|
+
return ref.name or ref.id
|
|
66
|
+
|
|
67
|
+
raise ValueError(f"Cannot extract name from: {ref}")
|
|
68
|
+
|
|
69
|
+
def _resolve_and_cache(self, ref: Any, name: str) -> MCP:
|
|
70
|
+
"""Resolve MCP reference - find by name/ID or create if needed.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
ref: The MCP reference to resolve.
|
|
74
|
+
name: The extracted MCP name.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The resolved glaip_sdk.models.MCP object.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If the MCP cannot be resolved.
|
|
81
|
+
"""
|
|
82
|
+
# MCP object (check if already has ID)
|
|
83
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
|
|
84
|
+
if ref.id is not None:
|
|
85
|
+
# Already resolved MCP with ID - just cache and return
|
|
86
|
+
logger.debug("Caching already resolved MCP: %s", name)
|
|
87
|
+
self._cache[name] = ref
|
|
88
|
+
return ref
|
|
89
|
+
|
|
90
|
+
# MCP without ID - need to look up or create
|
|
91
|
+
return self._lookup_or_create_mcp(ref, name)
|
|
92
|
+
|
|
93
|
+
# Dict from API response - use ID directly if available
|
|
94
|
+
if isinstance(ref, dict):
|
|
95
|
+
mcp_id = ref.get("id")
|
|
96
|
+
if mcp_id:
|
|
97
|
+
from glaip_sdk.mcps.base import MCP # noqa: PLC0415
|
|
98
|
+
|
|
99
|
+
mcp = MCP(id=mcp_id, name=ref.get("name", ""))
|
|
100
|
+
self._cache[name] = mcp
|
|
101
|
+
return mcp
|
|
102
|
+
raise ValueError(f"MCP dict missing 'id': {ref}")
|
|
103
|
+
|
|
104
|
+
# String name - look up on platform
|
|
105
|
+
if isinstance(ref, str):
|
|
106
|
+
return self._lookup_mcp_by_name(name)
|
|
107
|
+
|
|
108
|
+
raise ValueError(f"Could not resolve MCP reference: {ref}")
|
|
109
|
+
|
|
110
|
+
def _lookup_or_create_mcp(self, ref: Any, name: str) -> MCP:
|
|
111
|
+
"""Look up or create an MCP from a reference.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
ref: The MCP reference with config details.
|
|
115
|
+
name: The extracted MCP name.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The resolved or created glaip_sdk.models.MCP object.
|
|
119
|
+
"""
|
|
120
|
+
# Check if this MCP is lookup-only (e.g., from MCP.from_native)
|
|
121
|
+
if getattr(ref, "_lookup_only", False):
|
|
122
|
+
return self._lookup_native_mcp(name)
|
|
123
|
+
|
|
124
|
+
return self._upsert_mcp_from_ref(ref, name)
|
|
125
|
+
|
|
126
|
+
def _lookup_native_mcp(self, name: str) -> MCP:
|
|
127
|
+
"""Look up a native MCP that must exist on the platform.
|
|
128
|
+
|
|
129
|
+
Used for MCP.from_native() references that should not be created.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
name: The MCP name to look up.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The found MCP.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If MCP not found or multiple found.
|
|
139
|
+
"""
|
|
140
|
+
from glaip_sdk.utils.client import get_client # noqa: PLC0415
|
|
141
|
+
|
|
142
|
+
client = get_client()
|
|
143
|
+
logger.info("Looking up native MCP: %s", name)
|
|
144
|
+
|
|
145
|
+
results = client.find_mcps(name)
|
|
146
|
+
exact_matches = [mcp for mcp in results if getattr(mcp, "name", None) == name]
|
|
147
|
+
if len(exact_matches) == 1:
|
|
148
|
+
mcp = exact_matches[0]
|
|
149
|
+
self._cache[name] = mcp
|
|
150
|
+
return mcp
|
|
151
|
+
if len(exact_matches) > 1:
|
|
152
|
+
raise ValueError(f"Multiple MCPs found with name '{name}'")
|
|
153
|
+
raise ValueError(f"MCP not found on platform: {name}")
|
|
154
|
+
|
|
155
|
+
def _upsert_mcp_from_ref(self, ref: Any, name: str) -> MCP:
|
|
156
|
+
"""Create or update an MCP from a reference with config.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
ref: The MCP reference with config details.
|
|
160
|
+
name: The extracted MCP name.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
The created or updated MCP.
|
|
164
|
+
"""
|
|
165
|
+
from glaip_sdk.utils.client import get_client # noqa: PLC0415
|
|
166
|
+
|
|
167
|
+
client = get_client()
|
|
168
|
+
logger.info("Upserting MCP: %s", name)
|
|
169
|
+
|
|
170
|
+
mcp = client.mcps.upsert_mcp(
|
|
171
|
+
name,
|
|
172
|
+
description=getattr(ref, "description", None),
|
|
173
|
+
config=getattr(ref, "config", None),
|
|
174
|
+
transport=getattr(ref, "transport", None),
|
|
175
|
+
metadata=getattr(ref, "metadata", None),
|
|
176
|
+
authentication=getattr(ref, "authentication", None),
|
|
177
|
+
)
|
|
178
|
+
self._cache[name] = mcp
|
|
179
|
+
return mcp
|
|
180
|
+
|
|
181
|
+
def _lookup_mcp_by_name(self, name: str) -> MCP:
|
|
182
|
+
"""Look up MCP by name or ID on the platform.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
name: The MCP name or ID to look up.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The resolved glaip_sdk.models.MCP object.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
ValueError: If the MCP cannot be found.
|
|
192
|
+
"""
|
|
193
|
+
# Lazy imports to avoid circular dependency
|
|
194
|
+
from glaip_sdk.utils.client import get_client # noqa: PLC0415
|
|
195
|
+
|
|
196
|
+
client = get_client()
|
|
197
|
+
logger.info("Looking up MCP by name: %s", name)
|
|
198
|
+
|
|
199
|
+
# Check if it's a valid UUID
|
|
200
|
+
if is_uuid(name):
|
|
201
|
+
mcp = client.get_mcp_by_id(name)
|
|
202
|
+
if mcp:
|
|
203
|
+
self._cache[name] = mcp
|
|
204
|
+
return mcp
|
|
205
|
+
else:
|
|
206
|
+
results = client.find_mcps(name)
|
|
207
|
+
exact_matches = [mcp for mcp in results if getattr(mcp, "name", None) == name]
|
|
208
|
+
if len(exact_matches) == 1:
|
|
209
|
+
mcp = exact_matches[0]
|
|
210
|
+
self._cache[name] = mcp
|
|
211
|
+
return mcp
|
|
212
|
+
if len(exact_matches) > 1:
|
|
213
|
+
raise ValueError(f"Multiple MCPs found with name '{name}'")
|
|
214
|
+
|
|
215
|
+
raise ValueError(f"MCP not found on platform: {name}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class _MCPRegistrySingleton:
|
|
219
|
+
"""Singleton holder for MCPRegistry to avoid global statement."""
|
|
220
|
+
|
|
221
|
+
_instance: MCPRegistry | None = None
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def get_instance(cls) -> MCPRegistry:
|
|
225
|
+
"""Get or create the singleton instance.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
The global MCPRegistry instance.
|
|
229
|
+
"""
|
|
230
|
+
if cls._instance is None:
|
|
231
|
+
cls._instance = MCPRegistry()
|
|
232
|
+
return cls._instance
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def reset(cls) -> None:
|
|
236
|
+
"""Reset the singleton instance (for testing)."""
|
|
237
|
+
cls._instance = None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_mcp_registry() -> MCPRegistry:
|
|
241
|
+
"""Get the singleton MCPRegistry instance.
|
|
242
|
+
|
|
243
|
+
Returns a global MCPRegistry that caches MCPs across the session.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
The global MCPRegistry instance.
|
|
247
|
+
|
|
248
|
+
Example:
|
|
249
|
+
>>> from glaip_sdk.registry import get_mcp_registry
|
|
250
|
+
>>> registry = get_mcp_registry()
|
|
251
|
+
>>> mcp = registry.resolve("arxiv-mcp")
|
|
252
|
+
"""
|
|
253
|
+
return _MCPRegistrySingleton.get_instance()
|