glaip-sdk 0.7.3__py3-none-any.whl → 0.7.4__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/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +6 -0
- glaip_sdk/hitl/__init__.py +21 -1
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +1 -31
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/langgraph.py +4 -2
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +3 -9
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +21 -0
- {glaip_sdk-0.7.3.dist-info → glaip_sdk-0.7.4.dist-info}/METADATA +1 -1
- {glaip_sdk-0.7.3.dist-info → glaip_sdk-0.7.4.dist-info}/RECORD +17 -14
- glaip_sdk-0.7.4.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.3.dist-info/entry_points.txt +0 -2
- {glaip_sdk-0.7.3.dist-info → glaip_sdk-0.7.4.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.7.3.dist-info → glaip_sdk-0.7.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Entry point wrapper for early logging configuration.
|
|
2
|
+
|
|
3
|
+
This must be imported BEFORE glaip_sdk.cli.main to catch import-time warnings.
|
|
4
|
+
|
|
5
|
+
Authors:
|
|
6
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
# Configure logging BEFORE importing anything else
|
|
12
|
+
from glaip_sdk.runner.logging_config import setup_cli_logging
|
|
13
|
+
|
|
14
|
+
setup_cli_logging()
|
|
15
|
+
|
|
16
|
+
# Now import and run CLI
|
|
17
|
+
from glaip_sdk.cli import main # noqa: E402
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
sys.exit(main()) # pylint: disable=no-value-for-parameter
|
glaip_sdk/cli/main.py
CHANGED
|
@@ -73,6 +73,12 @@ def _resolve_client_class() -> type[Any]:
|
|
|
73
73
|
|
|
74
74
|
def _suppress_chatty_loggers() -> None:
|
|
75
75
|
"""Silence noisy SDK/httpx logs for CLI output."""
|
|
76
|
+
# Ensure CLI logging is configured (idempotent)
|
|
77
|
+
from glaip_sdk.runner.logging_config import setup_cli_logging # noqa: PLC0415
|
|
78
|
+
|
|
79
|
+
setup_cli_logging()
|
|
80
|
+
|
|
81
|
+
# Also suppress SDK-specific loggers
|
|
76
82
|
noisy_loggers = [
|
|
77
83
|
"glaip_sdk.client",
|
|
78
84
|
"httpx",
|
glaip_sdk/hitl/__init__.py
CHANGED
|
@@ -13,10 +13,16 @@ Authors:
|
|
|
13
13
|
GLAIP SDK Team
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
# These don't require aip_agents, so import them directly
|
|
16
19
|
from glaip_sdk.hitl.base import HITLCallback, HITLDecision, HITLRequest, HITLResponse
|
|
17
|
-
from glaip_sdk.hitl.
|
|
20
|
+
from glaip_sdk.hitl.callback import PauseResumeCallback
|
|
18
21
|
from glaip_sdk.hitl.remote import RemoteHITLHandler
|
|
19
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from glaip_sdk.hitl.local import LocalPromptHandler
|
|
25
|
+
|
|
20
26
|
__all__ = [
|
|
21
27
|
"LocalPromptHandler",
|
|
22
28
|
"PauseResumeCallback",
|
|
@@ -26,3 +32,17 @@ __all__ = [
|
|
|
26
32
|
"HITLResponse",
|
|
27
33
|
"RemoteHITLHandler",
|
|
28
34
|
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def __getattr__(name: str) -> Any: # noqa: ANN401
|
|
38
|
+
"""Lazy import for LocalPromptHandler.
|
|
39
|
+
|
|
40
|
+
This defers the import of aip_agents until LocalPromptHandler is actually accessed,
|
|
41
|
+
preventing ImportError when aip-agents is not installed but HITL is not being used.
|
|
42
|
+
"""
|
|
43
|
+
if name == "LocalPromptHandler":
|
|
44
|
+
from glaip_sdk.hitl.local import LocalPromptHandler # noqa: PLC0415
|
|
45
|
+
|
|
46
|
+
globals()["LocalPromptHandler"] = LocalPromptHandler
|
|
47
|
+
return LocalPromptHandler
|
|
48
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Pause/resume callback for HITL renderer control.
|
|
2
|
+
|
|
3
|
+
This module provides PauseResumeCallback which allows HITL prompt handlers
|
|
4
|
+
to control the live renderer without directly coupling to the renderer implementation.
|
|
5
|
+
|
|
6
|
+
Author:
|
|
7
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PauseResumeCallback:
|
|
14
|
+
"""Simple callback object for pausing/resuming the live renderer.
|
|
15
|
+
|
|
16
|
+
This allows the LocalPromptHandler to control the renderer without
|
|
17
|
+
directly coupling to the renderer implementation.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
"""Initialize the callback."""
|
|
22
|
+
self._renderer: Any | None = None
|
|
23
|
+
|
|
24
|
+
def set_renderer(self, renderer: Any) -> None:
|
|
25
|
+
"""Set the renderer instance.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
renderer: RichStreamRenderer instance with pause_live() and resume_live() methods.
|
|
29
|
+
"""
|
|
30
|
+
self._renderer = renderer
|
|
31
|
+
|
|
32
|
+
def pause(self) -> None:
|
|
33
|
+
"""Pause the live renderer before prompting."""
|
|
34
|
+
if self._renderer and hasattr(self._renderer, "_shutdown_live"):
|
|
35
|
+
self._renderer._shutdown_live()
|
|
36
|
+
|
|
37
|
+
def resume(self) -> None:
|
|
38
|
+
"""Resume the live renderer after prompting."""
|
|
39
|
+
if self._renderer and hasattr(self._renderer, "_ensure_live"):
|
|
40
|
+
self._renderer._ensure_live()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["PauseResumeCallback"]
|
glaip_sdk/hitl/local.py
CHANGED
|
@@ -118,34 +118,4 @@ class LocalPromptHandler(BasePromptHandler):
|
|
|
118
118
|
self._console.print(f"[dim]Context: {request.context}[/dim]")
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
"""Simple callback object for pausing/resuming the live renderer.
|
|
123
|
-
|
|
124
|
-
This allows the LocalPromptHandler to control the renderer without
|
|
125
|
-
directly coupling to the renderer implementation.
|
|
126
|
-
"""
|
|
127
|
-
|
|
128
|
-
def __init__(self) -> None:
|
|
129
|
-
"""Initialize the callback."""
|
|
130
|
-
self._renderer: Any | None = None
|
|
131
|
-
|
|
132
|
-
def set_renderer(self, renderer: Any) -> None:
|
|
133
|
-
"""Set the renderer instance.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
renderer: RichStreamRenderer instance with pause_live() and resume_live() methods.
|
|
137
|
-
"""
|
|
138
|
-
self._renderer = renderer
|
|
139
|
-
|
|
140
|
-
def pause(self) -> None:
|
|
141
|
-
"""Pause the live renderer before prompting."""
|
|
142
|
-
if self._renderer and hasattr(self._renderer, "_shutdown_live"):
|
|
143
|
-
self._renderer._shutdown_live()
|
|
144
|
-
|
|
145
|
-
def resume(self) -> None:
|
|
146
|
-
"""Resume the live renderer after prompting."""
|
|
147
|
-
if self._renderer and hasattr(self._renderer, "_ensure_live"):
|
|
148
|
-
self._renderer._ensure_live()
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
__all__ = ["LocalPromptHandler", "PauseResumeCallback"]
|
|
121
|
+
__all__ = ["LocalPromptHandler"]
|
glaip_sdk/runner/__init__.py
CHANGED
|
@@ -19,18 +19,19 @@ Example:
|
|
|
19
19
|
>>> result = runner.run(agent, "Hello!")
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
22
24
|
from glaip_sdk.runner.deps import (
|
|
23
25
|
LOCAL_RUNTIME_AVAILABLE,
|
|
24
26
|
check_local_runtime_available,
|
|
25
27
|
get_local_runtime_missing_message,
|
|
26
28
|
)
|
|
27
|
-
from glaip_sdk.runner.langgraph import LangGraphRunner
|
|
28
29
|
|
|
29
30
|
# Default runner instance
|
|
30
|
-
_default_runner:
|
|
31
|
+
_default_runner: Any | None = None
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
def get_default_runner() ->
|
|
34
|
+
def get_default_runner() -> Any:
|
|
34
35
|
"""Get the default runner instance for local agent execution.
|
|
35
36
|
|
|
36
37
|
Returns:
|
|
@@ -45,11 +46,17 @@ def get_default_runner() -> LangGraphRunner:
|
|
|
45
46
|
raise RuntimeError(get_local_runtime_missing_message())
|
|
46
47
|
|
|
47
48
|
if _default_runner is None:
|
|
49
|
+
# Lazy import to avoid requiring aip-agents when runner is not used
|
|
50
|
+
from glaip_sdk.runner.langgraph import LangGraphRunner # noqa: PLC0415
|
|
51
|
+
|
|
48
52
|
_default_runner = LangGraphRunner()
|
|
49
53
|
|
|
50
54
|
return _default_runner
|
|
51
55
|
|
|
52
56
|
|
|
57
|
+
if TYPE_CHECKING:
|
|
58
|
+
from glaip_sdk.runner.langgraph import LangGraphRunner
|
|
59
|
+
|
|
53
60
|
__all__ = [
|
|
54
61
|
"LOCAL_RUNTIME_AVAILABLE",
|
|
55
62
|
"LangGraphRunner",
|
|
@@ -57,3 +64,13 @@ __all__ = [
|
|
|
57
64
|
"get_default_runner",
|
|
58
65
|
"get_local_runtime_missing_message",
|
|
59
66
|
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def __getattr__(name: str) -> Any:
|
|
70
|
+
"""Lazy import for LangGraphRunner to avoid requiring aip-agents when not used."""
|
|
71
|
+
if name == "LangGraphRunner":
|
|
72
|
+
from glaip_sdk.runner.langgraph import LangGraphRunner # noqa: PLC0415
|
|
73
|
+
|
|
74
|
+
globals()["LangGraphRunner"] = LangGraphRunner
|
|
75
|
+
return LangGraphRunner
|
|
76
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
glaip_sdk/runner/langgraph.py
CHANGED
|
@@ -23,11 +23,10 @@ import logging
|
|
|
23
23
|
from dataclasses import dataclass
|
|
24
24
|
from typing import TYPE_CHECKING, Any
|
|
25
25
|
|
|
26
|
-
from aip_agents.agent.hitl.manager import ApprovalManager # noqa: PLC0415
|
|
27
26
|
from gllm_core.utils import LoggerManager
|
|
28
27
|
|
|
29
28
|
from glaip_sdk.client.run_rendering import AgentRunRenderingManager
|
|
30
|
-
from glaip_sdk.hitl import
|
|
29
|
+
from glaip_sdk.hitl import PauseResumeCallback
|
|
31
30
|
from glaip_sdk.runner.base import BaseRunner
|
|
32
31
|
from glaip_sdk.runner.deps import (
|
|
33
32
|
check_local_runtime_available,
|
|
@@ -434,6 +433,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
434
433
|
hitl_enabled = merged_agent_config.get("hitl_enabled", False)
|
|
435
434
|
if hitl_enabled:
|
|
436
435
|
try:
|
|
436
|
+
from aip_agents.agent.hitl.manager import ApprovalManager # noqa: PLC0415
|
|
437
|
+
from glaip_sdk.hitl import LocalPromptHandler # noqa: PLC0415
|
|
438
|
+
|
|
437
439
|
local_agent.hitl_manager = ApprovalManager(
|
|
438
440
|
prompt_handler=LocalPromptHandler(pause_resume_callback=pause_resume_callback)
|
|
439
441
|
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Logging configuration for CLI to suppress noisy dependency warnings.
|
|
2
|
+
|
|
3
|
+
This module provides centralized logging suppression for optional dependencies
|
|
4
|
+
that emit noisy warnings during CLI usage. Warnings are suppressed by default
|
|
5
|
+
but can be shown using GLAIP_LOG_LEVEL=DEBUG.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import warnings
|
|
14
|
+
|
|
15
|
+
NOISY_LOGGERS = ["transformers", "gllm_privacy", "google.cloud.aiplatform"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NameFilter(logging.Filter):
|
|
19
|
+
"""Filter logs by logger name prefix."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, prefixes: list[str]) -> None:
|
|
22
|
+
"""Initialize filter with logger name prefixes to suppress.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
prefixes: List of logger name prefixes to filter out.
|
|
26
|
+
"""
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.prefixes = prefixes
|
|
29
|
+
|
|
30
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
31
|
+
"""Filter log records by name prefix.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
record: Log record to filter.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
False if record should be suppressed, True otherwise.
|
|
38
|
+
"""
|
|
39
|
+
return not any(record.name.startswith(p) for p in self.prefixes)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def setup_cli_logging() -> None:
|
|
43
|
+
"""Suppress INFO from noisy third-party libraries.
|
|
44
|
+
|
|
45
|
+
Use GLAIP_LOG_LEVEL=DEBUG to see all warnings.
|
|
46
|
+
This function is idempotent - calling it multiple times is safe.
|
|
47
|
+
"""
|
|
48
|
+
# Check env level FIRST before any suppression
|
|
49
|
+
env_level = os.getenv("GLAIP_LOG_LEVEL", "").upper()
|
|
50
|
+
is_debug = env_level == "DEBUG"
|
|
51
|
+
|
|
52
|
+
if is_debug:
|
|
53
|
+
# Debug mode: show everything, no suppression
|
|
54
|
+
if env_level and hasattr(logging, env_level):
|
|
55
|
+
logging.basicConfig(level=getattr(logging, env_level))
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Default mode: suppress noisy warnings
|
|
59
|
+
if env_level and hasattr(logging, env_level):
|
|
60
|
+
logging.basicConfig(level=getattr(logging, env_level))
|
|
61
|
+
|
|
62
|
+
# Add handler filter to suppress by name prefix (handles child loggers)
|
|
63
|
+
# Check if filter already exists to ensure idempotency
|
|
64
|
+
root_logger = logging.getLogger()
|
|
65
|
+
has_name_filter = any(isinstance(f, NameFilter) for h in root_logger.handlers for f in h.filters)
|
|
66
|
+
|
|
67
|
+
if not has_name_filter:
|
|
68
|
+
handler = logging.StreamHandler()
|
|
69
|
+
handler.addFilter(NameFilter(NOISY_LOGGERS))
|
|
70
|
+
root_logger.addHandler(handler)
|
|
71
|
+
|
|
72
|
+
# Suppress FutureWarning for GCS (idempotent - multiple calls are safe)
|
|
73
|
+
warnings.filterwarnings(
|
|
74
|
+
"ignore",
|
|
75
|
+
category=FutureWarning,
|
|
76
|
+
message=r".*google-cloud-storage.*",
|
|
77
|
+
)
|
glaip_sdk/utils/bundler.py
CHANGED
|
@@ -14,6 +14,7 @@ import inspect
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
from glaip_sdk.utils.import_resolver import ImportResolver
|
|
17
|
+
from glaip_sdk.utils.tool_detection import is_tool_plugin_decorator
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class ToolBundler:
|
|
@@ -50,9 +51,14 @@ class ToolBundler:
|
|
|
50
51
|
self.tool_dir = self.tool_file.parent
|
|
51
52
|
self._import_resolver = ImportResolver(self.tool_dir)
|
|
52
53
|
|
|
53
|
-
def bundle(self) -> str:
|
|
54
|
+
def bundle(self, add_tool_plugin_decorator: bool = True) -> str:
|
|
54
55
|
"""Bundle tool source code with inlined local imports.
|
|
55
56
|
|
|
57
|
+
Args:
|
|
58
|
+
add_tool_plugin_decorator: If True, add @tool_plugin decorator to BaseTool classes.
|
|
59
|
+
Set to False for newer servers (0.1.85+) where decorator is optional.
|
|
60
|
+
Defaults to True for backward compatibility with older servers.
|
|
61
|
+
|
|
56
62
|
Returns:
|
|
57
63
|
Bundled source code with all local dependencies inlined.
|
|
58
64
|
"""
|
|
@@ -62,6 +68,16 @@ class ToolBundler:
|
|
|
62
68
|
tree = ast.parse(full_source)
|
|
63
69
|
local_imports, external_imports = self._import_resolver.categorize_imports(tree)
|
|
64
70
|
|
|
71
|
+
# NOTE: The @tool_plugin decorator is REQUIRED by older servers (< 0.1.85) for remote execution.
|
|
72
|
+
# Newer servers (0.1.85+) make the decorator optional.
|
|
73
|
+
# The server validates uploaded tool code and will reject tools without the decorator
|
|
74
|
+
# with error: "No classes found with @tool_plugin decorator".
|
|
75
|
+
# See: docs/resources/reference/schemas/tools.md - "Plugin Requirements"
|
|
76
|
+
# TESTED: Commenting out this decorator addition causes HTTP 400 ValidationError from older servers.
|
|
77
|
+
# We try without decorator first (for new servers), then retry with decorator if validation fails.
|
|
78
|
+
if add_tool_plugin_decorator:
|
|
79
|
+
self._add_tool_plugin_decorator(tree)
|
|
80
|
+
|
|
65
81
|
# Extract main code nodes (excluding imports, docstrings, glaip_sdk.Tool subclasses)
|
|
66
82
|
main_code_nodes = self._extract_main_code_nodes(tree)
|
|
67
83
|
|
|
@@ -71,6 +87,13 @@ class ToolBundler:
|
|
|
71
87
|
# Merge all external imports
|
|
72
88
|
all_external_imports = external_imports + inlined_external_imports
|
|
73
89
|
|
|
90
|
+
# NOTE: The gllm_plugin.tools import is REQUIRED when decorator is added.
|
|
91
|
+
# Without this import, the decorator will cause a NameError when the server executes the code.
|
|
92
|
+
# TESTED: Commenting out this import causes NameError when server tries to use the decorator.
|
|
93
|
+
# This import is added automatically during bundling so source files can remain clean.
|
|
94
|
+
if add_tool_plugin_decorator:
|
|
95
|
+
self._ensure_tool_plugin_import(all_external_imports)
|
|
96
|
+
|
|
74
97
|
# Build bundled code
|
|
75
98
|
bundled_code = ["# Bundled tool with inlined local imports\n"]
|
|
76
99
|
bundled_code.extend(self._import_resolver.format_external_imports(all_external_imports))
|
|
@@ -109,6 +132,103 @@ class ToolBundler:
|
|
|
109
132
|
main_code_nodes.append(ast.unparse(node))
|
|
110
133
|
return main_code_nodes
|
|
111
134
|
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _add_tool_plugin_decorator(tree: ast.AST) -> None:
|
|
137
|
+
"""Add @tool_plugin decorator to BaseTool classes that don't have it.
|
|
138
|
+
|
|
139
|
+
This allows tools to be clean (without decorator) for local use,
|
|
140
|
+
while the decorator is automatically added during bundling for remote execution.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
tree: AST tree to modify in-place.
|
|
144
|
+
"""
|
|
145
|
+
for node in ast.walk(tree):
|
|
146
|
+
if not isinstance(node, ast.ClassDef):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if not ToolBundler._inherits_from_base_tool(node):
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
if ToolBundler._has_tool_plugin_decorator(node):
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
decorator_call = ToolBundler._create_tool_plugin_decorator()
|
|
156
|
+
node.decorator_list.insert(0, decorator_call)
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _inherits_from_base_tool(class_node: ast.ClassDef) -> bool:
|
|
160
|
+
"""Check if a class inherits from BaseTool.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
class_node: AST ClassDef node to check.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
True if class inherits from BaseTool.
|
|
167
|
+
"""
|
|
168
|
+
for base in class_node.bases:
|
|
169
|
+
if isinstance(base, ast.Name) and base.id == "BaseTool":
|
|
170
|
+
return True
|
|
171
|
+
if isinstance(base, ast.Attribute) and base.attr == "BaseTool":
|
|
172
|
+
# Handle nested attributes like langchain_core.tools.BaseTool
|
|
173
|
+
# Check if the value chain leads to langchain_core
|
|
174
|
+
value = base.value
|
|
175
|
+
while isinstance(value, ast.Attribute):
|
|
176
|
+
value = value.value
|
|
177
|
+
if isinstance(value, ast.Name) and value.id == "langchain_core":
|
|
178
|
+
return True
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _has_tool_plugin_decorator(class_node: ast.ClassDef) -> bool:
|
|
183
|
+
"""Check if a class already has the @tool_plugin decorator.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
class_node: AST ClassDef node to check.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
True if decorator already exists.
|
|
190
|
+
"""
|
|
191
|
+
for decorator in class_node.decorator_list:
|
|
192
|
+
if is_tool_plugin_decorator(decorator):
|
|
193
|
+
return True
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _create_tool_plugin_decorator() -> ast.Call:
|
|
198
|
+
"""Create a @tool_plugin decorator AST node.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
AST Call node representing @tool_plugin(version="1.0.0").
|
|
202
|
+
"""
|
|
203
|
+
return ast.Call(
|
|
204
|
+
func=ast.Name(id="tool_plugin", ctx=ast.Load()),
|
|
205
|
+
args=[],
|
|
206
|
+
keywords=[ast.keyword(arg="version", value=ast.Constant(value="1.0.0"))],
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def _ensure_tool_plugin_import(external_imports: list) -> None:
|
|
211
|
+
"""Ensure gllm_plugin.tools import is present in external imports.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
external_imports: List of external import nodes (modified in-place).
|
|
215
|
+
"""
|
|
216
|
+
# Check if import already exists
|
|
217
|
+
for import_node in external_imports:
|
|
218
|
+
if isinstance(import_node, ast.ImportFrom) and import_node.module == "gllm_plugin.tools":
|
|
219
|
+
# Check if tool_plugin is in the names
|
|
220
|
+
for alias in import_node.names:
|
|
221
|
+
if alias.name == "tool_plugin":
|
|
222
|
+
return # Import already present
|
|
223
|
+
|
|
224
|
+
# Add the import
|
|
225
|
+
import_node = ast.ImportFrom(
|
|
226
|
+
module="gllm_plugin.tools",
|
|
227
|
+
names=[ast.alias(name="tool_plugin")],
|
|
228
|
+
level=0,
|
|
229
|
+
)
|
|
230
|
+
external_imports.append(import_node)
|
|
231
|
+
|
|
112
232
|
@staticmethod
|
|
113
233
|
def _is_sdk_tool_subclass(node: ast.ClassDef) -> bool:
|
|
114
234
|
"""Check if AST class definition inherits from Tool.
|
|
@@ -135,7 +255,7 @@ class ToolBundler:
|
|
|
135
255
|
return False
|
|
136
256
|
|
|
137
257
|
@classmethod
|
|
138
|
-
def bundle_from_source(cls, file_path: Path) -> tuple[str, str, str]:
|
|
258
|
+
def bundle_from_source(cls, file_path: Path, add_tool_plugin_decorator: bool = True) -> tuple[str, str, str]:
|
|
139
259
|
"""Extract tool info directly from source file without importing.
|
|
140
260
|
|
|
141
261
|
This is used as a fallback when the tool class cannot be imported
|
|
@@ -143,6 +263,9 @@ class ToolBundler:
|
|
|
143
263
|
|
|
144
264
|
Args:
|
|
145
265
|
file_path: Path to the tool source file.
|
|
266
|
+
add_tool_plugin_decorator: If True, add @tool_plugin decorator to BaseTool classes.
|
|
267
|
+
Set to False for newer servers (0.1.85+) where decorator is optional.
|
|
268
|
+
Defaults to True for backward compatibility with older servers.
|
|
146
269
|
|
|
147
270
|
Returns:
|
|
148
271
|
Tuple of (name, description, bundled_source_code).
|
|
@@ -160,6 +283,12 @@ class ToolBundler:
|
|
|
160
283
|
tool_dir = file_path.parent
|
|
161
284
|
import_resolver = ImportResolver(tool_dir)
|
|
162
285
|
|
|
286
|
+
# NOTE: The @tool_plugin decorator is REQUIRED by older servers (< 0.1.85) for remote execution.
|
|
287
|
+
# Newer servers (0.1.85+) make the decorator optional.
|
|
288
|
+
# See bundle() method for detailed explanation.
|
|
289
|
+
if add_tool_plugin_decorator:
|
|
290
|
+
cls._add_tool_plugin_decorator(tree)
|
|
291
|
+
|
|
163
292
|
# Find tool name and description from class definitions
|
|
164
293
|
tool_name, tool_description = cls._extract_tool_metadata(tree, file_path.stem)
|
|
165
294
|
|
|
@@ -180,6 +309,13 @@ class ToolBundler:
|
|
|
180
309
|
|
|
181
310
|
# Build bundled code
|
|
182
311
|
all_external_imports = external_imports + inlined_external_imports
|
|
312
|
+
|
|
313
|
+
# NOTE: The gllm_plugin.tools import is REQUIRED when decorator is added.
|
|
314
|
+
# See bundle() method for detailed explanation.
|
|
315
|
+
# TESTED: Commenting out this import causes NameError when server tries to use the decorator.
|
|
316
|
+
if add_tool_plugin_decorator:
|
|
317
|
+
cls._ensure_tool_plugin_import(all_external_imports)
|
|
318
|
+
|
|
183
319
|
bundled_code = ["# Bundled tool with inlined local imports\n"]
|
|
184
320
|
bundled_code.extend(import_resolver.format_external_imports(all_external_imports))
|
|
185
321
|
|
|
@@ -13,6 +13,8 @@ import ast
|
|
|
13
13
|
import importlib
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
+
from glaip_sdk.utils.tool_detection import is_tool_plugin_decorator
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
class ImportResolver:
|
|
18
20
|
"""Resolves and categorizes Python imports for tool bundling.
|
|
@@ -482,15 +484,7 @@ class ImportResolver:
|
|
|
482
484
|
Returns:
|
|
483
485
|
True if decorator is @tool_plugin.
|
|
484
486
|
"""
|
|
485
|
-
|
|
486
|
-
return True
|
|
487
|
-
if (
|
|
488
|
-
isinstance(decorator, ast.Call)
|
|
489
|
-
and isinstance(decorator.func, ast.Name)
|
|
490
|
-
and decorator.func.id == "tool_plugin"
|
|
491
|
-
):
|
|
492
|
-
return True
|
|
493
|
-
return False
|
|
487
|
+
return is_tool_plugin_decorator(decorator)
|
|
494
488
|
|
|
495
489
|
@staticmethod
|
|
496
490
|
def _filter_bases(bases: list) -> list:
|
glaip_sdk/utils/sync.py
CHANGED
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
from typing import TYPE_CHECKING, Any
|
|
17
17
|
|
|
18
|
+
from glaip_sdk.exceptions import ValidationError
|
|
18
19
|
from glaip_sdk.utils.bundler import ToolBundler
|
|
19
20
|
from glaip_sdk.utils.import_resolver import load_class
|
|
20
21
|
from gllm_core.utils import LoggerManager
|
|
@@ -94,19 +95,38 @@ def update_or_create_tool(tool_ref: Any) -> Tool:
|
|
|
94
95
|
tool_name = _extract_tool_name(tool_class)
|
|
95
96
|
tool_description = _extract_tool_description(tool_class)
|
|
96
97
|
|
|
97
|
-
# Bundle source code
|
|
98
|
+
# Bundle source code - try without decorator first (for newer servers 0.1.85+)
|
|
99
|
+
# If validation fails, retry with decorator for older servers (< 0.1.85)
|
|
98
100
|
bundler = ToolBundler(tool_class)
|
|
99
|
-
bundled_source = bundler.bundle()
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
try:
|
|
103
|
+
# Try without decorator first (for newer servers where it's optional)
|
|
104
|
+
bundled_source = bundler.bundle(add_tool_plugin_decorator=False)
|
|
105
|
+
logger.info("Tool info: name='%s', description='%s...'", tool_name, tool_description[:50])
|
|
106
|
+
logger.info("Bundled source code (without decorator): %d characters", len(bundled_source))
|
|
107
|
+
|
|
108
|
+
# Attempt upload without decorator
|
|
109
|
+
return client.tools.upsert_tool(
|
|
110
|
+
tool_name,
|
|
111
|
+
code=bundled_source,
|
|
112
|
+
description=tool_description,
|
|
113
|
+
)
|
|
114
|
+
except ValidationError as e:
|
|
115
|
+
# Check if error is about missing @tool_plugin decorator
|
|
116
|
+
error_message = str(e).lower()
|
|
117
|
+
if "@tool_plugin decorator" in error_message or "no classes found" in error_message:
|
|
118
|
+
# Retry with decorator for older servers (< 0.1.85)
|
|
119
|
+
logger.info("Server requires @tool_plugin decorator, retrying with decorator added")
|
|
120
|
+
bundled_source = bundler.bundle(add_tool_plugin_decorator=True)
|
|
121
|
+
logger.info("Bundled source code (with decorator): %d characters", len(bundled_source))
|
|
122
|
+
|
|
123
|
+
return client.tools.upsert_tool(
|
|
124
|
+
tool_name,
|
|
125
|
+
code=bundled_source,
|
|
126
|
+
description=tool_description,
|
|
127
|
+
)
|
|
128
|
+
# Re-raise if it's a different validation error
|
|
129
|
+
raise
|
|
110
130
|
|
|
111
131
|
|
|
112
132
|
def update_or_create_agent(agent_config: dict[str, Any]) -> Agent:
|
|
@@ -4,6 +4,7 @@ Authors:
|
|
|
4
4
|
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import ast
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
|
|
@@ -31,3 +32,23 @@ def is_langchain_tool(ref: Any) -> bool:
|
|
|
31
32
|
pass
|
|
32
33
|
|
|
33
34
|
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def is_tool_plugin_decorator(decorator: ast.expr) -> bool:
|
|
38
|
+
"""Check if an AST decorator node is @tool_plugin.
|
|
39
|
+
|
|
40
|
+
Shared by:
|
|
41
|
+
- ToolBundler._has_tool_plugin_decorator() (for bundling)
|
|
42
|
+
- ImportResolver._is_tool_plugin_decorator() (for import resolution)
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
decorator: AST decorator expression node to check.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
True if decorator is @tool_plugin.
|
|
49
|
+
"""
|
|
50
|
+
if isinstance(decorator, ast.Name) and decorator.id == "tool_plugin":
|
|
51
|
+
return True
|
|
52
|
+
if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Name) and decorator.func.id == "tool_plugin":
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
@@ -14,9 +14,10 @@ glaip_sdk/cli/config.py,sha256=s0_xBB1e5YE4I_Wc4q-ayY3dwsBU1JrHAF-8ySlim7Y,3040
|
|
|
14
14
|
glaip_sdk/cli/constants.py,sha256=zqcVtzfj6huW97gbCmhkFqntge1H-c1vnkGqTazADgU,895
|
|
15
15
|
glaip_sdk/cli/context.py,sha256=--Y5vc6lgoAV7cRoUAr9UxSQaLmkMg29FolA7EwoRqM,3803
|
|
16
16
|
glaip_sdk/cli/display.py,sha256=ojgWdGeD5KUnGOmWNqqK4JP-1EaWHWX--DWze3BmIz0,12137
|
|
17
|
+
glaip_sdk/cli/entrypoint.py,sha256=ODrNZT1c7mFtNuXn4CrJgs06-xIhrqUMi1rKzkYJ21c,516
|
|
17
18
|
glaip_sdk/cli/hints.py,sha256=ca4krG103IS43s5BSLr0-N7uRMpte1_LY4nAXVvgDxo,1596
|
|
18
19
|
glaip_sdk/cli/io.py,sha256=ChP6CRKbtuENsNomNEaMDfPDU0iqO-WuVvl4_y7F2io,3871
|
|
19
|
-
glaip_sdk/cli/main.py,sha256=
|
|
20
|
+
glaip_sdk/cli/main.py,sha256=bi_SBrRWWMcdbl28zbNMrzp8i5SKCKTb2CWN-hLIx94,24680
|
|
20
21
|
glaip_sdk/cli/masking.py,sha256=2lrXQ-pfL7N-vNEQRT1s4Xq3JPDPDT8RC61OdaTtkkc,4060
|
|
21
22
|
glaip_sdk/cli/mcp_validators.py,sha256=cwbz7p_p7_9xVuuF96OBQOdmEgo5UObU6iWWQ2X03PI,10047
|
|
22
23
|
glaip_sdk/cli/pager.py,sha256=TmiMDNpUMuZju7QJ6A_ITqIoEf8Dhv8U6mTXx2Fga1k,7935
|
|
@@ -116,9 +117,10 @@ glaip_sdk/client/payloads/agent/__init__.py,sha256=gItEH2zt2secVq6n60oGA-ztdE5mc
|
|
|
116
117
|
glaip_sdk/client/payloads/agent/requests.py,sha256=5FuGEuypaEXlWBhB07JrDca_ecLg4bvo8mjyFBxAV9U,17139
|
|
117
118
|
glaip_sdk/client/payloads/agent/responses.py,sha256=1eRMI4JAIGqTB5zY_7D9ILQDRHPXR06U7JqHSmRp3Qs,1243
|
|
118
119
|
glaip_sdk/config/constants.py,sha256=Y03c6op0e7K0jTQ8bmWXhWAqsnjWxkAhWniq8Z0iEKY,1081
|
|
119
|
-
glaip_sdk/hitl/__init__.py,sha256=
|
|
120
|
+
glaip_sdk/hitl/__init__.py,sha256=hi_SwW1oBimNnSFPo9Yc-mZWVPzpytlnDWNq2h1_fPo,1572
|
|
120
121
|
glaip_sdk/hitl/base.py,sha256=EUN2igzydlYZ6_qmHU46Gyk3Bk9uyalZkCJ06XMRKJ8,1484
|
|
121
|
-
glaip_sdk/hitl/
|
|
122
|
+
glaip_sdk/hitl/callback.py,sha256=icKxxa_f8lxFQuXrZVoTt6baWivFL4a4YioWG_U_8k8,1336
|
|
123
|
+
glaip_sdk/hitl/local.py,sha256=7Qf-O62YcVXpOHdckm1-g4wwvHQCvwg4D1ikK-xwgqA,4642
|
|
122
124
|
glaip_sdk/hitl/remote.py,sha256=cdO-wWwRGdyb0HYNMwIvHfvKwOqhqp-l7efnaC9b85M,18914
|
|
123
125
|
glaip_sdk/mcps/__init__.py,sha256=4jYrt8K__oxrxexHRcmnRBXt-W_tbJN61H9Kf2lVh4Q,551
|
|
124
126
|
glaip_sdk/mcps/base.py,sha256=jWwHjDF67_mtDGRp9p5SolANjVeB8jt1PSwPBtX876M,11654
|
|
@@ -136,10 +138,11 @@ glaip_sdk/registry/agent.py,sha256=F0axW4BIUODqnttIOzxnoS5AqQkLZ1i48FTeZNnYkhA,5
|
|
|
136
138
|
glaip_sdk/registry/base.py,sha256=0x2ZBhiERGUcf9mQeWlksSYs5TxDG6FxBYQToYZa5D4,4143
|
|
137
139
|
glaip_sdk/registry/mcp.py,sha256=kNJmiijIbZL9Btx5o2tFtbaT-WG6O4Xf_nl3wz356Ow,7978
|
|
138
140
|
glaip_sdk/registry/tool.py,sha256=QnbAlk09lYvEb9PEdCsvpg4CGxlLbvvFWBS8WkM1ZoM,12955
|
|
139
|
-
glaip_sdk/runner/__init__.py,sha256=
|
|
141
|
+
glaip_sdk/runner/__init__.py,sha256=orJ3nLR9P-n1qMaAMWZ_xRS4368YnDpdltg-bX5BlUk,2210
|
|
140
142
|
glaip_sdk/runner/base.py,sha256=KIjcSAyDCP9_mn2H4rXR5gu1FZlwD9pe0gkTBmr6Yi4,2663
|
|
141
143
|
glaip_sdk/runner/deps.py,sha256=Du3hr2R5RHOYCRAv7RVmx661x-ayVXIeZ8JD7ODirTA,3884
|
|
142
|
-
glaip_sdk/runner/langgraph.py,sha256
|
|
144
|
+
glaip_sdk/runner/langgraph.py,sha256=HWzEkmkQqvAOKat3lENVf0zDaypj7HMeny5thDyprWY,33031
|
|
145
|
+
glaip_sdk/runner/logging_config.py,sha256=OrQgW23t42qQRqEXKH8U4bFg4JG5EEkUJTlbvtU65iE,2528
|
|
143
146
|
glaip_sdk/runner/mcp_adapter/__init__.py,sha256=Rdttfg3N6kg3-DaTCKqaGXKByZyBt0Mwf6FV8s_5kI8,462
|
|
144
147
|
glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py,sha256=ic56fKgb3zgVZZQm3ClWUZi7pE1t4EVq8mOg6AM6hdA,1374
|
|
145
148
|
glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py,sha256=b58GuadPz7q7aXoJyTYs0eeJ_oqp-wLR1tcr_5cbV1s,9723
|
|
@@ -153,7 +156,7 @@ glaip_sdk/tools/__init__.py,sha256=rhGzEqQFCzeMrxmikBuNrMz4PyYczwic28boDKVmoHs,5
|
|
|
153
156
|
glaip_sdk/tools/base.py,sha256=hkz2NZFHW1PqsRiXh3kKTVLIjMPR274Uwml944vH5tg,16325
|
|
154
157
|
glaip_sdk/utils/__init__.py,sha256=ntohV7cxlY2Yksi2nFuFm_Mg2XVJbBbSJVRej7Mi9YE,2770
|
|
155
158
|
glaip_sdk/utils/agent_config.py,sha256=RhcHsSOVwOaSC2ggnPuHn36Aa0keGJhs8KGb2InvzRk,7262
|
|
156
|
-
glaip_sdk/utils/bundler.py,sha256=
|
|
159
|
+
glaip_sdk/utils/bundler.py,sha256=fLumFj1MqqqGA1Mwn05v_cEKPALv3rIPEMvaURpxZ80,15171
|
|
157
160
|
glaip_sdk/utils/client.py,sha256=otPUOIDvLCCsvFBNR8YMZFtRrORggmvvlFjl3YeeTqQ,3121
|
|
158
161
|
glaip_sdk/utils/client_utils.py,sha256=hzHxxNuM37mK4HhgIdS0qg4AqjAA5ai2irPO6Nr1Uzo,15350
|
|
159
162
|
glaip_sdk/utils/datetime_helpers.py,sha256=QLknNLEAY56628-MTRKnCXAffATkF33erOqBubKmU98,1544
|
|
@@ -162,14 +165,14 @@ glaip_sdk/utils/display.py,sha256=zu3SYqxj9hPyEN8G1vIXv_yXBkV8jLLCXEg2rs8NlzM,44
|
|
|
162
165
|
glaip_sdk/utils/export.py,sha256=1NxxE3wGsA1auzecG5oJw5ELB4VmPljoeIkGhrGOh1I,5006
|
|
163
166
|
glaip_sdk/utils/general.py,sha256=3HSVIopUsIymPaim-kP2lqLX75TkkdIVLe6g3UKabZ0,1507
|
|
164
167
|
glaip_sdk/utils/import_export.py,sha256=RCvoydm_6_L7_J1igcE6IYDunqgS5mQUbWT4VGrytMw,5510
|
|
165
|
-
glaip_sdk/utils/import_resolver.py,sha256=
|
|
168
|
+
glaip_sdk/utils/import_resolver.py,sha256=X2qUV4_XmwStccGjnQ0YcxXAFyxZzwaKpfxjAW4Ev2o,17159
|
|
166
169
|
glaip_sdk/utils/instructions.py,sha256=MTk93lsq3I8aRnvnRMSXXNMzcpnaIM_Pm3Aiiiq3GBc,2997
|
|
167
170
|
glaip_sdk/utils/resource_refs.py,sha256=vF34kyAtFBLnaKnQVrsr2st1JiSxVbIZ4yq0DelJvCI,5966
|
|
168
171
|
glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
|
|
169
172
|
glaip_sdk/utils/runtime_config.py,sha256=Gl9-CQ4lYZ39vRSgtdfcSU3CXshVDDuTOdSzjvsCgG0,14070
|
|
170
173
|
glaip_sdk/utils/serialization.py,sha256=z-qpvWLSBrGK3wbUclcA1UIKLXJedTnMSwPdq-FF4lo,13308
|
|
171
|
-
glaip_sdk/utils/sync.py,sha256=
|
|
172
|
-
glaip_sdk/utils/tool_detection.py,sha256=
|
|
174
|
+
glaip_sdk/utils/sync.py,sha256=71egWp5qm_8tYpWZyGazvnP4NnyW16rcmzjGVicmQEE,6043
|
|
175
|
+
glaip_sdk/utils/tool_detection.py,sha256=6dHp0naLnvY6jwy_38k4kyTgQnizgcsq9hpeLSjAmcc,1471
|
|
173
176
|
glaip_sdk/utils/tool_storage_provider.py,sha256=lampwUeWu4Uy8nBG7C4ZT-M6AHoWZS0m67HdLx21VDg,5396
|
|
174
177
|
glaip_sdk/utils/validation.py,sha256=hB_k3lvHdIFUiSwHStrC0Eqnhx0OG2UvwqASeem0HuQ,6859
|
|
175
178
|
glaip_sdk/utils/a2a/__init__.py,sha256=_X8AvDOsHeppo5n7rP5TeisVxlAdkZDTFReBk_9lmxo,876
|
|
@@ -203,8 +206,8 @@ glaip_sdk/utils/rendering/steps/format.py,sha256=Chnq7OBaj8XMeBntSBxrX5zSmrYeGcO
|
|
|
203
206
|
glaip_sdk/utils/rendering/steps/manager.py,sha256=BiBmTeQMQhjRMykgICXsXNYh1hGsss-fH9BIGVMWFi0,13194
|
|
204
207
|
glaip_sdk/utils/rendering/viewer/__init__.py,sha256=XrxmE2cMAozqrzo1jtDFm8HqNtvDcYi2mAhXLXn5CjI,457
|
|
205
208
|
glaip_sdk/utils/rendering/viewer/presenter.py,sha256=mlLMTjnyeyPVtsyrAbz1BJu9lFGQSlS-voZ-_Cuugv0,5725
|
|
206
|
-
glaip_sdk-0.7.
|
|
207
|
-
glaip_sdk-0.7.
|
|
208
|
-
glaip_sdk-0.7.
|
|
209
|
-
glaip_sdk-0.7.
|
|
210
|
-
glaip_sdk-0.7.
|
|
209
|
+
glaip_sdk-0.7.4.dist-info/METADATA,sha256=r36rVJdxsiV-fUHo3SRNf_pakOeHayQy1KyL3ZCqN-M,8365
|
|
210
|
+
glaip_sdk-0.7.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
211
|
+
glaip_sdk-0.7.4.dist-info/entry_points.txt,sha256=NkhO6FfgX9Zrjn63GuKphf-dLw7KNJvucAcXc7P3aMk,54
|
|
212
|
+
glaip_sdk-0.7.4.dist-info/top_level.txt,sha256=td7yXttiYX2s94-4wFhv-5KdT0rSZ-pnJRSire341hw,10
|
|
213
|
+
glaip_sdk-0.7.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|