glaip-sdk 0.6.5b6__py3-none-any.whl → 0.7.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glaip_sdk/__init__.py +42 -5
- glaip_sdk/agents/base.py +217 -42
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/account_store.py +15 -0
- glaip_sdk/cli/auth.py +14 -8
- glaip_sdk/cli/commands/accounts.py +1 -1
- 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 +15 -12
- glaip_sdk/cli/commands/configure.py +2 -3
- 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 +2 -4
- 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.py → transcripts_original.py} +2 -1
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +1 -0
- glaip_sdk/cli/core/output.py +12 -7
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +127 -39
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +112 -32
- glaip_sdk/cli/slash/agent_session.py +5 -2
- glaip_sdk/cli/slash/prompt.py +11 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +369 -23
- glaip_sdk/cli/slash/tui/__init__.py +26 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +79 -5
- glaip_sdk/cli/slash/tui/accounts_app.py +1027 -88
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +87 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +160 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +119 -12
- glaip_sdk/cli/slash/tui/terminal.py +407 -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 +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +374 -0
- glaip_sdk/cli/transcript/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +5 -3
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +215 -7
- glaip_sdk/cli/validators.py +1 -1
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agents.py +50 -8
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -1
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +414 -3
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +57 -26
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/models/__init__.py +17 -0
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/registry/tool.py +273 -59
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +5 -8
- glaip_sdk/runner/langgraph.py +318 -42
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +104 -5
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +72 -7
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/base.py +67 -14
- glaip_sdk/utils/__init__.py +1 -0
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +43 -11
- glaip_sdk/utils/rendering/renderer/base.py +58 -0
- glaip_sdk/utils/runtime_config.py +15 -12
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/METADATA +49 -37
- glaip_sdk-0.7.12.dist-info/RECORD +219 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.12.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.12.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk-0.6.5b6.dist-info/RECORD +0 -159
- glaip_sdk-0.6.5b6.dist-info/entry_points.txt +0 -3
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Guardrail serialization logic.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to serialize GuardrailManager and its engines
|
|
4
|
+
into the JSON format expected by the GL AIP backend. This keeps the serialization
|
|
5
|
+
logic within the SDK rather than polluting the core aip-agents logic.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from glaip_sdk.guardrails import (
|
|
17
|
+
GuardrailManager,
|
|
18
|
+
NemoGuardrailEngine,
|
|
19
|
+
PhraseMatcherEngine,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _serialize_phrase_matcher(engine: PhraseMatcherEngine) -> dict[str, Any]:
|
|
24
|
+
"""Serialize a PhraseMatcherEngine configuration."""
|
|
25
|
+
config: dict[str, Any] = {}
|
|
26
|
+
|
|
27
|
+
# Extract config from BaseGuardrailEngineConfig
|
|
28
|
+
if hasattr(engine, "config") and engine.config:
|
|
29
|
+
config.update(engine.config.model_dump())
|
|
30
|
+
|
|
31
|
+
# Extract specific fields
|
|
32
|
+
if hasattr(engine, "banned_phrases"):
|
|
33
|
+
config["banned_phrases"] = engine.banned_phrases
|
|
34
|
+
|
|
35
|
+
return config
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _serialize_nemo(engine: NemoGuardrailEngine) -> dict[str, Any]:
|
|
39
|
+
"""Serialize a NemoGuardrailEngine configuration."""
|
|
40
|
+
config: dict[str, Any] = {}
|
|
41
|
+
|
|
42
|
+
# Extract config from BaseGuardrailEngineConfig
|
|
43
|
+
if hasattr(engine, "config") and engine.config:
|
|
44
|
+
config.update(engine.config.model_dump())
|
|
45
|
+
|
|
46
|
+
# Extract specific fields
|
|
47
|
+
nemo_fields = [
|
|
48
|
+
"topic_safety_mode",
|
|
49
|
+
"allowed_topics",
|
|
50
|
+
"denied_topics",
|
|
51
|
+
"include_core_restrictions",
|
|
52
|
+
"core_restriction_categories",
|
|
53
|
+
"config_dict",
|
|
54
|
+
"denial_phrases",
|
|
55
|
+
]
|
|
56
|
+
for field in nemo_fields:
|
|
57
|
+
if hasattr(engine, field):
|
|
58
|
+
val = getattr(engine, field)
|
|
59
|
+
if val is not None:
|
|
60
|
+
config[field] = val
|
|
61
|
+
|
|
62
|
+
return config
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def serialize_guardrail_manager(manager: GuardrailManager) -> dict[str, Any]:
|
|
66
|
+
"""Serialize a GuardrailManager into the backend JSON format.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
manager: The GuardrailManager instance to serialize.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A dictionary matching the agent_config.guardrails schema.
|
|
73
|
+
"""
|
|
74
|
+
from glaip_sdk.guardrails import NemoGuardrailEngine, PhraseMatcherEngine # noqa: PLC0415
|
|
75
|
+
|
|
76
|
+
engines_config = []
|
|
77
|
+
|
|
78
|
+
if hasattr(manager, "engines"):
|
|
79
|
+
for engine in manager.engines:
|
|
80
|
+
if isinstance(engine, PhraseMatcherEngine):
|
|
81
|
+
engines_config.append({"type": "phrase_matcher", "config": _serialize_phrase_matcher(engine)})
|
|
82
|
+
elif isinstance(engine, NemoGuardrailEngine):
|
|
83
|
+
engines_config.append({"type": "nemo", "config": _serialize_nemo(engine)})
|
|
84
|
+
else:
|
|
85
|
+
# Fallback for unknown engines
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
enabled = getattr(manager, "enabled", True)
|
|
89
|
+
return {"enabled": enabled, "engines": engines_config}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
For remote execution, use RemoteHITLHandler to handle HITL events programmatically.
|
|
10
|
+
|
|
11
|
+
Authors:
|
|
12
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
13
|
+
GLAIP SDK Team
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
# These don't require aip_agents, so import them directly
|
|
19
|
+
from glaip_sdk.hitl.base import HITLCallback, HITLDecision, HITLRequest, HITLResponse
|
|
20
|
+
from glaip_sdk.hitl.callback import PauseResumeCallback
|
|
21
|
+
from glaip_sdk.hitl.remote import RemoteHITLHandler
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from glaip_sdk.hitl.local import LocalPromptHandler
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"LocalPromptHandler",
|
|
28
|
+
"PauseResumeCallback",
|
|
29
|
+
"HITLCallback",
|
|
30
|
+
"HITLDecision",
|
|
31
|
+
"HITLRequest",
|
|
32
|
+
"HITLResponse",
|
|
33
|
+
"RemoteHITLHandler",
|
|
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}")
|
glaip_sdk/hitl/base.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Base types for HITL approval handling.
|
|
3
|
+
|
|
4
|
+
Authors:
|
|
5
|
+
GLAIP SDK Team
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Protocol, runtime_checkable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HITLDecision(str, Enum):
|
|
14
|
+
"""HITL decision types."""
|
|
15
|
+
|
|
16
|
+
APPROVED = "approved"
|
|
17
|
+
REJECTED = "rejected"
|
|
18
|
+
SKIPPED = "skipped"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class HITLRequest:
|
|
23
|
+
"""HITL approval request from SSE stream."""
|
|
24
|
+
|
|
25
|
+
request_id: str
|
|
26
|
+
tool_name: str
|
|
27
|
+
tool_args: dict[str, Any]
|
|
28
|
+
timeout_at: str # ISO 8601, authoritative deadline
|
|
29
|
+
timeout_seconds: int # Informational, fallback only
|
|
30
|
+
|
|
31
|
+
# Raw metadata for advanced use cases
|
|
32
|
+
hitl_metadata: dict[str, Any]
|
|
33
|
+
tool_metadata: dict[str, Any]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class HITLResponse:
|
|
38
|
+
"""HITL decision response."""
|
|
39
|
+
|
|
40
|
+
decision: HITLDecision
|
|
41
|
+
operator_input: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@runtime_checkable
|
|
45
|
+
class HITLCallback(Protocol):
|
|
46
|
+
"""Protocol for HITL approval callbacks.
|
|
47
|
+
|
|
48
|
+
Callbacks should complete within the computed callback timeout.
|
|
49
|
+
Callbacks should handle exceptions internally or let them propagate.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __call__(self, request: HITLRequest) -> HITLResponse:
|
|
53
|
+
"""Handle HITL approval request.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
request: HITL request with tool info and metadata
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
HITLResponse with decision and optional operator input
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
Any exception will be caught, logged, and treated as REJECTED.
|
|
63
|
+
"""
|
|
64
|
+
...
|
|
@@ -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
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
__all__ = ["LocalPromptHandler"]
|