glaip-sdk 0.6.15b3__py3-none-any.whl → 0.6.17__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.
@@ -0,0 +1,15 @@
1
+ """Human-in-the-Loop (HITL) utilities for glaip-sdk.
2
+
3
+ This package provides utilities for HITL approval workflows in both local
4
+ and remote agent execution modes.
5
+
6
+ For local development, LocalPromptHandler is automatically injected when
7
+ agent_config.hitl_enabled is True. No manual setup required.
8
+
9
+ Authors:
10
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
11
+ """
12
+
13
+ from glaip_sdk.hitl.local import LocalPromptHandler, PauseResumeCallback
14
+
15
+ __all__ = ["LocalPromptHandler", "PauseResumeCallback"]
@@ -0,0 +1,151 @@
1
+ """Local HITL prompt handler with interactive console support.
2
+
3
+ Author:
4
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
5
+ """
6
+
7
+ import os
8
+ from typing import Any
9
+
10
+ try:
11
+ from aip_agents.agent.hitl.prompt.base import BasePromptHandler
12
+ from aip_agents.schema.hitl import ApprovalDecision, ApprovalDecisionType, ApprovalRequest
13
+ except ImportError as e:
14
+ raise ImportError("aip_agents is required for local HITL. Install with: pip install 'glaip-sdk[local]'") from e
15
+
16
+ from rich.console import Console
17
+ from rich.prompt import Prompt
18
+
19
+
20
+ class LocalPromptHandler(BasePromptHandler):
21
+ """Local HITL prompt handler with interactive console prompts.
22
+
23
+ Experimental local HITL implementation with known limitations:
24
+ - Timeouts are not enforced (interactive prompts wait indefinitely)
25
+ - Relies on private renderer methods for pause/resume
26
+ - Only supports interactive terminal environments
27
+
28
+ The key insight from Rich documentation is that Live must be stopped before
29
+ using Prompt/input(), otherwise the input won't render properly.
30
+
31
+ Environment variables:
32
+ GLAIP_HITL_AUTO_APPROVE: Set to "true" (case-insensitive) to auto-approve
33
+ all requests without user interaction. Useful for integration tests and CI.
34
+ """
35
+
36
+ def __init__(self, *, pause_resume_callback: Any | None = None) -> None:
37
+ """Initialize the prompt handler.
38
+
39
+ Args:
40
+ pause_resume_callback: Optional callable with pause() and resume() methods
41
+ to control the live renderer during prompts. This is needed because
42
+ Rich Live interferes with Prompt/input().
43
+ """
44
+ super().__init__()
45
+ self._pause_resume = pause_resume_callback
46
+ self._console = Console()
47
+
48
+ async def prompt_for_decision(
49
+ self,
50
+ request: ApprovalRequest,
51
+ timeout_seconds: int,
52
+ context_keys: list[str] | None = None,
53
+ ) -> ApprovalDecision:
54
+ """Prompt for approval decision with live renderer pause/resume.
55
+
56
+ Supports auto-approval via GLAIP_HITL_AUTO_APPROVE environment variable
57
+ for integration testing and CI environments. Set to "true" (case-insensitive) to enable.
58
+ """
59
+ _ = (timeout_seconds, context_keys) # Suppress unused parameter warnings.
60
+
61
+ # Check for auto-approve mode (for integration tests/CI)
62
+ auto_approve = os.getenv("GLAIP_HITL_AUTO_APPROVE", "").lower() == "true"
63
+
64
+ if auto_approve:
65
+ # Auto-approve without user interaction
66
+ return ApprovalDecision(
67
+ request_id=request.request_id,
68
+ decision=ApprovalDecisionType.APPROVED,
69
+ operator_input="auto-approved",
70
+ )
71
+
72
+ # Pause the live renderer if callback is available
73
+ if self._pause_resume:
74
+ self._pause_resume.pause()
75
+
76
+ try:
77
+ # POC/MVP: Show what we're approving (still auto-approve for now)
78
+ self._print_request_info(request)
79
+
80
+ # POC/MVP: For testing, we can do actual input here
81
+ # Uncomment to enable real prompting:
82
+ response = Prompt.ask(
83
+ "\n[yellow]Approve this tool call?[/yellow] [dim](y/n/s)[/dim]",
84
+ console=self._console,
85
+ default="y",
86
+ )
87
+ response = response.lower().strip()
88
+
89
+ if response in ("y", "yes"):
90
+ decision = ApprovalDecisionType.APPROVED
91
+ elif response in ("n", "no"):
92
+ decision = ApprovalDecisionType.REJECTED
93
+ else:
94
+ decision = ApprovalDecisionType.SKIPPED
95
+
96
+ return ApprovalDecision(
97
+ request_id=request.request_id,
98
+ decision=decision,
99
+ operator_input=response if decision != ApprovalDecisionType.SKIPPED else None,
100
+ )
101
+ finally:
102
+ # Always resume the live renderer
103
+ if self._pause_resume:
104
+ self._pause_resume.resume()
105
+
106
+ def _print_request_info(self, request: ApprovalRequest) -> None:
107
+ """Print the approval request information."""
108
+ self._console.print()
109
+ self._console.rule("[yellow]HITL Approval Request[/yellow]", style="yellow")
110
+
111
+ tool_name = request.tool_name or "unknown"
112
+ self._console.print(f"[cyan]Tool:[/cyan] {tool_name}")
113
+
114
+ if hasattr(request, "arguments_preview") and request.arguments_preview:
115
+ self._console.print(f"[cyan]Arguments:[/cyan] {request.arguments_preview}")
116
+
117
+ if request.context:
118
+ self._console.print(f"[dim]Context: {request.context}[/dim]")
119
+
120
+
121
+ class PauseResumeCallback:
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"]
@@ -25,6 +25,9 @@ from typing import TYPE_CHECKING, Any
25
25
 
26
26
  from gllm_core.utils import LoggerManager
27
27
 
28
+ from aip_agents.agent.hitl.manager import ApprovalManager # noqa: PLC0415
29
+
30
+ from glaip_sdk.hitl import LocalPromptHandler, PauseResumeCallback
28
31
  from glaip_sdk.runner.base import BaseRunner
29
32
  from glaip_sdk.runner.deps import (
30
33
  check_local_runtime_available,
@@ -251,8 +254,13 @@ class LangGraphRunner(BaseRunner):
251
254
  if swallow_aip_logs:
252
255
  _swallow_aip_logs()
253
256
 
257
+ # POC/MVP: Create pause/resume callback for interactive HITL input
258
+ pause_resume_callback = PauseResumeCallback()
259
+
254
260
  # Build the local LangGraphReactAgent from the glaip_sdk Agent
255
- local_agent = self.build_langgraph_agent(agent, runtime_config=runtime_config)
261
+ local_agent = self.build_langgraph_agent(
262
+ agent, runtime_config=runtime_config, pause_resume_callback=pause_resume_callback
263
+ )
256
264
 
257
265
  # Convert chat history to LangChain messages for the agent
258
266
  langchain_messages = _convert_chat_history_to_messages(chat_history)
@@ -267,6 +275,10 @@ class LangGraphRunner(BaseRunner):
267
275
  # Use shared render manager for unified processing
268
276
  render_manager = AgentRunRenderingManager(logger)
269
277
  renderer = render_manager.create_renderer(kwargs.get("renderer"), verbose=verbose)
278
+
279
+ # POC/MVP: Set renderer on callback so LocalPromptHandler can pause/resume Live
280
+ pause_resume_callback.set_renderer(renderer)
281
+
270
282
  meta = render_manager.build_initial_metadata(agent.name, message, kwargs)
271
283
  render_manager.start_renderer(renderer, meta)
272
284
 
@@ -305,6 +317,8 @@ class LangGraphRunner(BaseRunner):
305
317
  self,
306
318
  agent: Agent,
307
319
  runtime_config: dict[str, Any] | None = None,
320
+ *,
321
+ pause_resume_callback: Any | None = None,
308
322
  ) -> Any:
309
323
  """Build a LangGraphReactAgent from a glaip_sdk Agent definition.
310
324
 
@@ -312,6 +326,8 @@ class LangGraphRunner(BaseRunner):
312
326
  agent: The glaip_sdk Agent to convert.
313
327
  runtime_config: Optional runtime configuration with tool_configs,
314
328
  mcp_configs, agent_config, and agent-specific overrides.
329
+ pause_resume_callback: Optional callback used to pause/resume the renderer
330
+ during interactive HITL prompts.
315
331
 
316
332
  Returns:
317
333
  A configured LangGraphReactAgent instance.
@@ -365,6 +381,28 @@ class LangGraphRunner(BaseRunner):
365
381
  # Add MCP servers if configured
366
382
  self._add_mcp_servers(local_agent, agent, mcp_configs)
367
383
 
384
+ # Inject local HITL manager only if hitl_enabled is True (master switch).
385
+ # This matches remote behavior: hitl_enabled gates the HITL plumbing.
386
+ # Tool-level HITL configs are only enforced when hitl_enabled=True.
387
+ hitl_enabled = merged_agent_config.get("hitl_enabled", False)
388
+ if hitl_enabled:
389
+ try:
390
+ local_agent.hitl_manager = ApprovalManager(
391
+ prompt_handler=LocalPromptHandler(pause_resume_callback=pause_resume_callback)
392
+ )
393
+ # Store callback reference for setting renderer later
394
+ if pause_resume_callback:
395
+ local_agent._pause_resume_callback = pause_resume_callback
396
+ logger.debug("HITL manager injected for agent '%s' (hitl_enabled=True)", agent.name)
397
+ except ImportError as e:
398
+ # Missing dependencies - fail fast
399
+ raise ImportError("Local HITL requires aip_agents. Install with: pip install 'glaip-sdk[local]'") from e
400
+ except Exception as e:
401
+ # Other errors during HITL setup - fail fast
402
+ raise RuntimeError(f"Failed to initialize HITL manager for agent '{agent.name}'") from e
403
+ else:
404
+ logger.debug("HITL manager not injected for agent '%s' (hitl_enabled=False)", agent.name)
405
+
368
406
  logger.debug(
369
407
  "Built local LangGraphReactAgent for agent '%s' with %d tools, %d sub-agents, and %d MCPs",
370
408
  agent.name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glaip-sdk
3
- Version: 0.6.15b3
3
+ Version: 0.6.17
4
4
  Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
5
5
  Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
6
6
  License: MIT
@@ -19,12 +19,11 @@ Requires-Dist: textual>=0.52.0
19
19
  Requires-Dist: gllm-core-binary>=0.1.0
20
20
  Requires-Dist: langchain-core>=0.3.0
21
21
  Requires-Dist: gllm-tools-binary>=0.1.3
22
- Provides-Extra: local
23
- Requires-Dist: aip-agents-binary[local]>=0.5.11; extra == "local"
22
+ Requires-Dist: aip-agents-binary[local]>=0.5.13
24
23
  Provides-Extra: memory
25
- Requires-Dist: aip-agents-binary[memory]>=0.5.11; extra == "memory"
24
+ Requires-Dist: aip-agents-binary[memory]>=0.5.13; (python_version >= "3.11" and python_version < "3.13") and extra == "memory"
26
25
  Provides-Extra: privacy
27
- Requires-Dist: aip-agents-binary[privacy]>=0.5.11; extra == "privacy"
26
+ Requires-Dist: aip-agents-binary[privacy]>=0.5.13; (python_version >= "3.11" and python_version < "3.13") and extra == "privacy"
28
27
  Requires-Dist: en-core-web-sm; extra == "privacy"
29
28
  Provides-Extra: dev
30
29
  Requires-Dist: pytest>=7.0.0; extra == "dev"
@@ -38,7 +37,7 @@ Requires-Dist: ruff>=0.14.0; extra == "dev"
38
37
 
39
38
  # GL AIP — GDP Labs AI Agents Package
40
39
 
41
- [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
40
+ [![Python 3.11-3.12](https://img.shields.io/badge/python-3.11--3.12-blue.svg)](https://www.python.org/downloads/)
42
41
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
43
42
 
44
43
  GL stands for **GDP Labs**—GL AIP is our AI Agents Package for building, running, and operating agents.
@@ -57,7 +56,7 @@ pip install --upgrade glaip-sdk
57
56
  uv tool install glaip-sdk
58
57
  ```
59
58
 
60
- **Requirements**: Python 3.10+
59
+ **Requirements**: Python 3.11 or 3.12
61
60
 
62
61
  ## 🐍 Hello World - Python SDK
63
62
 
@@ -74,6 +74,8 @@ glaip_sdk/client/shared.py,sha256=esHlsR0LEfL-pFDaWebQjKKOLl09jsRY-2pllBUn4nU,52
74
74
  glaip_sdk/client/tools.py,sha256=kK0rBwX1e_5AlGQRjlO6rNz6gDlohhXWdlxN9AwotdE,22585
75
75
  glaip_sdk/client/validators.py,sha256=ioF9VCs-LG2yLkaRDd7Hff74lojDZZ0_Q3CiLbdm1RY,8381
76
76
  glaip_sdk/config/constants.py,sha256=Y03c6op0e7K0jTQ8bmWXhWAqsnjWxkAhWniq8Z0iEKY,1081
77
+ glaip_sdk/hitl/__init__.py,sha256=sg92Rpu8_vJIGi1ZEhx0-qWa1nGdvfrKyJAxtoDSKzo,494
78
+ glaip_sdk/hitl/local.py,sha256=rzmaRK15BxgRX7cmklUcGQUotMYg8x2Gd9BWf39k6hw,5661
77
79
  glaip_sdk/mcps/__init__.py,sha256=4jYrt8K__oxrxexHRcmnRBXt-W_tbJN61H9Kf2lVh4Q,551
78
80
  glaip_sdk/mcps/base.py,sha256=jWwHjDF67_mtDGRp9p5SolANjVeB8jt1PSwPBtX876M,11654
79
81
  glaip_sdk/models/__init__.py,sha256=-qO4Yr1-fkyaYC9RcT3nYhplDjoXATrIFZr4JrqflHI,2577
@@ -92,7 +94,7 @@ glaip_sdk/registry/tool.py,sha256=rxrVxnO_VwO6E5kccqxxEUC337J9qbKpje-Gwl5a3sY,76
92
94
  glaip_sdk/runner/__init__.py,sha256=8RrngoGfpF8x9X27RPdX4gJjch75ZvhtVt_6UV0ULLQ,1615
93
95
  glaip_sdk/runner/base.py,sha256=KIjcSAyDCP9_mn2H4rXR5gu1FZlwD9pe0gkTBmr6Yi4,2663
94
96
  glaip_sdk/runner/deps.py,sha256=Du3hr2R5RHOYCRAv7RVmx661x-ayVXIeZ8JD7ODirTA,3884
95
- glaip_sdk/runner/langgraph.py,sha256=97fCY4BwBj-APID3e4qQUUwXLEDbrxTyTqHhN2Z7RcA,28811
97
+ glaip_sdk/runner/langgraph.py,sha256=tcpbcve2K1IpjkJx00deP11WtCV6qny6QSgWgQH4YuA,30835
96
98
  glaip_sdk/runner/mcp_adapter/__init__.py,sha256=Rdttfg3N6kg3-DaTCKqaGXKByZyBt0Mwf6FV8s_5kI8,462
97
99
  glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py,sha256=ic56fKgb3zgVZZQm3ClWUZi7pE1t4EVq8mOg6AM6hdA,1374
98
100
  glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py,sha256=b58GuadPz7q7aXoJyTYs0eeJ_oqp-wLR1tcr_5cbV1s,9723
@@ -153,8 +155,8 @@ glaip_sdk/utils/rendering/steps/format.py,sha256=Chnq7OBaj8XMeBntSBxrX5zSmrYeGcO
153
155
  glaip_sdk/utils/rendering/steps/manager.py,sha256=BiBmTeQMQhjRMykgICXsXNYh1hGsss-fH9BIGVMWFi0,13194
154
156
  glaip_sdk/utils/rendering/viewer/__init__.py,sha256=XrxmE2cMAozqrzo1jtDFm8HqNtvDcYi2mAhXLXn5CjI,457
155
157
  glaip_sdk/utils/rendering/viewer/presenter.py,sha256=mlLMTjnyeyPVtsyrAbz1BJu9lFGQSlS-voZ-_Cuugv0,5725
156
- glaip_sdk-0.6.15b3.dist-info/METADATA,sha256=5XIDOUN2jiyyCCJxCx9MvraBvtqqkjeTwX3qxglauU4,7624
157
- glaip_sdk-0.6.15b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
158
- glaip_sdk-0.6.15b3.dist-info/entry_points.txt,sha256=65vNPUggyYnVGhuw7RhNJ8Fp2jygTcX0yxJBcBY3iLU,48
159
- glaip_sdk-0.6.15b3.dist-info/top_level.txt,sha256=td7yXttiYX2s94-4wFhv-5KdT0rSZ-pnJRSire341hw,10
160
- glaip_sdk-0.6.15b3.dist-info/RECORD,,
158
+ glaip_sdk-0.6.17.dist-info/METADATA,sha256=_pRNW4dous-_1TOJSdFtaVhSC5YLcxCSp3zBRDHPhvA,7716
159
+ glaip_sdk-0.6.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
160
+ glaip_sdk-0.6.17.dist-info/entry_points.txt,sha256=65vNPUggyYnVGhuw7RhNJ8Fp2jygTcX0yxJBcBY3iLU,48
161
+ glaip_sdk-0.6.17.dist-info/top_level.txt,sha256=td7yXttiYX2s94-4wFhv-5KdT0rSZ-pnJRSire341hw,10
162
+ glaip_sdk-0.6.17.dist-info/RECORD,,