glaip-sdk 0.7.9__py3-none-any.whl → 0.7.11__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/agents/base.py +61 -10
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/slash/remote_runs_controller.py +2 -0
- glaip_sdk/cli/slash/session.py +331 -30
- glaip_sdk/cli/slash/tui/accounts.tcss +72 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +827 -101
- glaip_sdk/cli/slash/tui/clipboard.py +56 -8
- glaip_sdk/cli/slash/tui/context.py +5 -2
- 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 +8 -3
- glaip_sdk/cli/slash/tui/toast.py +270 -19
- glaip_sdk/client/run_rendering.py +76 -29
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/runner/langgraph.py +1 -0
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/METADATA +3 -1
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/RECORD +24 -19
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.7.9.dist-info → glaip_sdk-0.7.11.dist-info}/top_level.txt +0 -0
glaip_sdk/agents/base.py
CHANGED
|
@@ -54,6 +54,7 @@ from glaip_sdk.utils.resource_refs import is_uuid
|
|
|
54
54
|
|
|
55
55
|
if TYPE_CHECKING:
|
|
56
56
|
from glaip_sdk.client.schedules import AgentScheduleManager
|
|
57
|
+
from glaip_sdk.guardrails import GuardrailManager
|
|
57
58
|
from glaip_sdk.models import AgentResponse
|
|
58
59
|
from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
|
|
59
60
|
|
|
@@ -130,6 +131,7 @@ class Agent:
|
|
|
130
131
|
agents: list | None = None,
|
|
131
132
|
mcps: list | None = None,
|
|
132
133
|
model: str | None = _UNSET, # type: ignore[assignment]
|
|
134
|
+
guardrail: GuardrailManager | None = None,
|
|
133
135
|
_client: Any = None,
|
|
134
136
|
**kwargs: Any,
|
|
135
137
|
) -> None:
|
|
@@ -147,7 +149,9 @@ class Agent:
|
|
|
147
149
|
agents: List of sub-agents (Agent classes, instances, or strings).
|
|
148
150
|
mcps: List of MCPs.
|
|
149
151
|
model: Model identifier.
|
|
152
|
+
guardrail: The guardrail manager for content safety.
|
|
150
153
|
_client: Internal client reference (set automatically).
|
|
154
|
+
|
|
151
155
|
**kwargs: Additional configuration parameters:
|
|
152
156
|
- timeout: Execution timeout in seconds.
|
|
153
157
|
- metadata: Optional metadata dictionary.
|
|
@@ -173,6 +177,7 @@ class Agent:
|
|
|
173
177
|
self._agents = agents
|
|
174
178
|
self._mcps = mcps
|
|
175
179
|
self._model = model
|
|
180
|
+
self._guardrail = guardrail
|
|
176
181
|
self._language_model_id: str | None = None
|
|
177
182
|
# Extract parameters from kwargs with _UNSET defaults
|
|
178
183
|
self._timeout = kwargs.pop("timeout", Agent._UNSET) # type: ignore[assignment]
|
|
@@ -452,6 +457,11 @@ class Agent:
|
|
|
452
457
|
return self._mcp_configs
|
|
453
458
|
return None
|
|
454
459
|
|
|
460
|
+
@property
|
|
461
|
+
def guardrail(self) -> GuardrailManager | None:
|
|
462
|
+
"""The guardrail manager for content safety."""
|
|
463
|
+
return self._guardrail
|
|
464
|
+
|
|
455
465
|
@property
|
|
456
466
|
def a2a_profile(self) -> dict[str, Any] | None:
|
|
457
467
|
"""A2A (Agent-to-Agent) profile configuration.
|
|
@@ -547,11 +557,20 @@ class Agent:
|
|
|
547
557
|
|
|
548
558
|
# Handle agent_config with timeout
|
|
549
559
|
# The timeout property is a convenience that maps to agent_config.execution_timeout
|
|
550
|
-
|
|
560
|
+
raw_config = self.agent_config if self.agent_config is not self._UNSET else {}
|
|
561
|
+
agent_config = dict(raw_config) if raw_config else {}
|
|
562
|
+
|
|
551
563
|
if self.timeout and "execution_timeout" not in agent_config:
|
|
552
564
|
agent_config["execution_timeout"] = self.timeout
|
|
553
|
-
|
|
554
|
-
|
|
565
|
+
|
|
566
|
+
if self.guardrail:
|
|
567
|
+
from glaip_sdk.guardrails.serializer import ( # noqa: PLC0415
|
|
568
|
+
serialize_guardrail_manager,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
agent_config["guardrails"] = serialize_guardrail_manager(self.guardrail)
|
|
572
|
+
|
|
573
|
+
config["agent_config"] = agent_config
|
|
555
574
|
|
|
556
575
|
# Handle tool_configs - resolve tool names/classes to IDs
|
|
557
576
|
if self.tool_configs:
|
|
@@ -583,11 +602,20 @@ class Agent:
|
|
|
583
602
|
|
|
584
603
|
Returns:
|
|
585
604
|
List of resolved MCP IDs for the API payload.
|
|
605
|
+
|
|
606
|
+
Raises:
|
|
607
|
+
ValueError: If an MCP fails to resolve to a valid ID.
|
|
586
608
|
"""
|
|
587
609
|
if not self.mcps:
|
|
588
610
|
return []
|
|
589
611
|
|
|
590
|
-
|
|
612
|
+
resolved_ids: list[str] = []
|
|
613
|
+
for mcp_ref in self.mcps:
|
|
614
|
+
mcp = registry.resolve(mcp_ref)
|
|
615
|
+
if not mcp.id:
|
|
616
|
+
raise ValueError(f"Failed to resolve ID for MCP: {mcp_ref}")
|
|
617
|
+
resolved_ids.append(mcp.id)
|
|
618
|
+
return resolved_ids
|
|
591
619
|
|
|
592
620
|
def _resolve_tools(self, registry: ToolRegistry) -> list[str]:
|
|
593
621
|
"""Resolve tool references to IDs using ToolRegistry.
|
|
@@ -601,12 +629,20 @@ class Agent:
|
|
|
601
629
|
|
|
602
630
|
Returns:
|
|
603
631
|
List of resolved tool IDs for the API payload.
|
|
632
|
+
|
|
633
|
+
Raises:
|
|
634
|
+
ValueError: If a tool fails to resolve to a valid ID.
|
|
604
635
|
"""
|
|
605
636
|
if not self.tools:
|
|
606
637
|
return []
|
|
607
638
|
|
|
608
|
-
|
|
609
|
-
|
|
639
|
+
resolved_ids: list[str] = []
|
|
640
|
+
for tool_ref in self.tools:
|
|
641
|
+
tool = registry.resolve(tool_ref)
|
|
642
|
+
if not tool.id:
|
|
643
|
+
raise ValueError(f"Failed to resolve ID for tool: {tool_ref}")
|
|
644
|
+
resolved_ids.append(tool.id)
|
|
645
|
+
return resolved_ids
|
|
610
646
|
|
|
611
647
|
def _resolve_tool_configs(self, registry: ToolRegistry) -> dict[str, Any]:
|
|
612
648
|
"""Resolve tool_configs keys from tool names/classes to tool IDs.
|
|
@@ -649,6 +685,8 @@ class Agent:
|
|
|
649
685
|
try:
|
|
650
686
|
# Resolve key (tool name/class) to Tool object, get ID
|
|
651
687
|
tool = registry.resolve(key)
|
|
688
|
+
if not tool.id:
|
|
689
|
+
raise ValueError(f"Resolved tool has no ID: {key}")
|
|
652
690
|
resolved[tool.id] = config
|
|
653
691
|
except (ValueError, KeyError) as e:
|
|
654
692
|
raise ValueError(f"Failed to resolve tool config key: {key}") from e
|
|
@@ -684,6 +722,8 @@ class Agent:
|
|
|
684
722
|
resolved_id = key
|
|
685
723
|
else:
|
|
686
724
|
mcp = registry.resolve(key)
|
|
725
|
+
if not mcp.id:
|
|
726
|
+
raise ValueError(f"Resolved MCP has no ID: {key}")
|
|
687
727
|
resolved_id = mcp.id
|
|
688
728
|
|
|
689
729
|
if resolved_id in resolved:
|
|
@@ -699,7 +739,7 @@ class Agent:
|
|
|
699
739
|
|
|
700
740
|
return resolved
|
|
701
741
|
|
|
702
|
-
def _resolve_agents(self, registry: AgentRegistry) -> list:
|
|
742
|
+
def _resolve_agents(self, registry: AgentRegistry) -> list[str]:
|
|
703
743
|
"""Resolve sub-agent references using AgentRegistry.
|
|
704
744
|
|
|
705
745
|
Uses the global AgentRegistry to cache Agent objects across deployments.
|
|
@@ -711,12 +751,20 @@ class Agent:
|
|
|
711
751
|
|
|
712
752
|
Returns:
|
|
713
753
|
List of resolved agent IDs for the API payload.
|
|
754
|
+
|
|
755
|
+
Raises:
|
|
756
|
+
ValueError: If an agent fails to resolve to a valid ID.
|
|
714
757
|
"""
|
|
715
758
|
if not self.agents:
|
|
716
759
|
return []
|
|
717
760
|
|
|
718
|
-
|
|
719
|
-
|
|
761
|
+
resolved_ids: list[str] = []
|
|
762
|
+
for agent_ref in self.agents:
|
|
763
|
+
agent = registry.resolve(agent_ref)
|
|
764
|
+
if not agent.id:
|
|
765
|
+
raise ValueError(f"Failed to resolve ID for agent: {agent_ref}")
|
|
766
|
+
resolved_ids.append(agent.id)
|
|
767
|
+
return resolved_ids
|
|
720
768
|
|
|
721
769
|
def _create_or_update_agent(
|
|
722
770
|
self,
|
|
@@ -807,6 +855,8 @@ class Agent:
|
|
|
807
855
|
Returns:
|
|
808
856
|
Dictionary containing Agent attributes.
|
|
809
857
|
"""
|
|
858
|
+
config = self._build_config(get_tool_registry(), get_mcp_registry())
|
|
859
|
+
|
|
810
860
|
data = {
|
|
811
861
|
"id": self._id,
|
|
812
862
|
"name": self.name,
|
|
@@ -820,10 +870,11 @@ class Agent:
|
|
|
820
870
|
"mcps": self.mcps,
|
|
821
871
|
"timeout": self.timeout,
|
|
822
872
|
"metadata": self.metadata,
|
|
823
|
-
"agent_config":
|
|
873
|
+
"agent_config": config.get("agent_config"),
|
|
824
874
|
"tool_configs": self.tool_configs,
|
|
825
875
|
"mcp_configs": self.mcp_configs,
|
|
826
876
|
"a2a_profile": self.a2a_profile,
|
|
877
|
+
"guardrail": self.guardrail,
|
|
827
878
|
"created_at": self._created_at,
|
|
828
879
|
"updated_at": self._updated_at,
|
|
829
880
|
}
|
glaip_sdk/branding.py
CHANGED
|
@@ -17,6 +17,7 @@ import platform
|
|
|
17
17
|
import sys
|
|
18
18
|
|
|
19
19
|
from rich.console import Console
|
|
20
|
+
from rich.text import Text
|
|
20
21
|
|
|
21
22
|
from glaip_sdk._version import __version__ as SDK_VERSION
|
|
22
23
|
from glaip_sdk.rich_components import AIPPanel
|
|
@@ -110,9 +111,13 @@ GDP Labs AI Agents Package
|
|
|
110
111
|
return SDK_VERSION
|
|
111
112
|
|
|
112
113
|
@staticmethod
|
|
113
|
-
def _make_console() -> Console:
|
|
114
|
+
def _make_console(force_terminal: bool | None = None, *, soft_wrap: bool = True) -> Console:
|
|
114
115
|
"""Create a Rich Console instance respecting NO_COLOR environment variables.
|
|
115
116
|
|
|
117
|
+
Args:
|
|
118
|
+
force_terminal: Override terminal detection when True/False.
|
|
119
|
+
soft_wrap: Whether to enable soft wrapping in the console.
|
|
120
|
+
|
|
116
121
|
Returns:
|
|
117
122
|
Console instance with color system configured based on environment.
|
|
118
123
|
"""
|
|
@@ -124,7 +129,12 @@ GDP Labs AI Agents Package
|
|
|
124
129
|
else:
|
|
125
130
|
color_system = "auto"
|
|
126
131
|
no_color = False
|
|
127
|
-
return Console(
|
|
132
|
+
return Console(
|
|
133
|
+
color_system=color_system,
|
|
134
|
+
no_color=no_color,
|
|
135
|
+
soft_wrap=soft_wrap,
|
|
136
|
+
force_terminal=force_terminal,
|
|
137
|
+
)
|
|
128
138
|
|
|
129
139
|
# ---- public API -----------------------------------------------------------
|
|
130
140
|
def get_welcome_banner(self) -> str:
|
|
@@ -209,3 +219,104 @@ GDP Labs AI Agents Package
|
|
|
209
219
|
AIPBranding instance
|
|
210
220
|
"""
|
|
211
221
|
return cls(version=sdk_version, package_name=package_name)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class LogoAnimator:
|
|
225
|
+
"""Animated logo with pulse effect for CLI startup.
|
|
226
|
+
|
|
227
|
+
Provides a "Knight Rider" style light pulse animation that sweeps across
|
|
228
|
+
the GL AIP logo during initialization tasks. Respects NO_COLOR and non-TTY
|
|
229
|
+
environments with graceful degradation.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# Animation colors from GDP Labs brand palette
|
|
233
|
+
BASE_BLUE = SECONDARY_MEDIUM # "#005CB8" - Medium Blue
|
|
234
|
+
HIGHLIGHT = SECONDARY_LIGHT # "#40B4E5" - Light Blue
|
|
235
|
+
WHITE = "#FFFFFF" # Bright white center
|
|
236
|
+
|
|
237
|
+
def __init__(self, console: Console | None = None) -> None:
|
|
238
|
+
"""Initialize LogoAnimator.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
console: Optional console instance. If None, creates a default console.
|
|
242
|
+
"""
|
|
243
|
+
self.console = console or AIPBranding._make_console()
|
|
244
|
+
self.logo = AIPBranding.AIP_LOGO
|
|
245
|
+
self.lines = self.logo.split("\n")
|
|
246
|
+
self.max_width = max(len(line) for line in self.lines) if self.lines else 0
|
|
247
|
+
|
|
248
|
+
def generate_frame(self, step: int, status_text: str = "") -> Text:
|
|
249
|
+
"""Generate a single animation frame with logo pulse and status.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
step: Current animation step (position of the pulse).
|
|
253
|
+
status_text: Optional status text to display below the logo.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Text object with styled logo and status.
|
|
257
|
+
"""
|
|
258
|
+
text = Text()
|
|
259
|
+
|
|
260
|
+
for line in self.lines:
|
|
261
|
+
for x, char in enumerate(line):
|
|
262
|
+
distance = abs(x - step)
|
|
263
|
+
|
|
264
|
+
if distance == 0:
|
|
265
|
+
style = f"bold {self.WHITE}" # Bright white center
|
|
266
|
+
elif distance <= 3:
|
|
267
|
+
style = f"bold {self.HIGHLIGHT}" # Light blue glow
|
|
268
|
+
else:
|
|
269
|
+
style = self.BASE_BLUE # Base blue
|
|
270
|
+
|
|
271
|
+
text.append(char, style=style)
|
|
272
|
+
text.append("\n")
|
|
273
|
+
|
|
274
|
+
# Add status area below the logo
|
|
275
|
+
if status_text:
|
|
276
|
+
text.append(f"\n{status_text}\n")
|
|
277
|
+
|
|
278
|
+
return text
|
|
279
|
+
|
|
280
|
+
def should_animate(self) -> bool:
|
|
281
|
+
"""Check if animation should be used.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
True if animation should be used (interactive TTY with colors),
|
|
285
|
+
False otherwise (NO_COLOR set or non-TTY).
|
|
286
|
+
"""
|
|
287
|
+
# Check for NO_COLOR environment variables
|
|
288
|
+
no_color = os.getenv("NO_COLOR") is not None or os.getenv("AIP_NO_COLOR") is not None
|
|
289
|
+
if no_color:
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
# Check if console is a TTY
|
|
293
|
+
if not self.console.is_terminal:
|
|
294
|
+
return False
|
|
295
|
+
|
|
296
|
+
# Check if console explicitly disables colors
|
|
297
|
+
if self.console.no_color:
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
# If we get here, we have a TTY without NO_COLOR set
|
|
301
|
+
# Rich will handle color detection, so we can animate
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
def display_static_logo(self, status_text: str = "") -> None:
|
|
305
|
+
"""Display static logo without animation (for non-TTY or NO_COLOR).
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
status_text: Optional status text to display below the logo.
|
|
309
|
+
"""
|
|
310
|
+
self.console.print(self.static_frame(status_text))
|
|
311
|
+
|
|
312
|
+
def static_frame(self, status_text: str = "") -> Text:
|
|
313
|
+
"""Return a static logo frame for use in non-animated renders.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
status_text: Optional status text to display below the logo.
|
|
317
|
+
"""
|
|
318
|
+
logo_text = Text(self.logo, style=self.BASE_BLUE)
|
|
319
|
+
if status_text:
|
|
320
|
+
logo_text.append("\n")
|
|
321
|
+
logo_text.append(status_text)
|
|
322
|
+
return logo_text
|
|
@@ -294,12 +294,14 @@ class RemoteRunsController:
|
|
|
294
294
|
fetch_detail=fetch_detail,
|
|
295
295
|
export_run=export_run,
|
|
296
296
|
)
|
|
297
|
+
tui_ctx = getattr(self.session, "tui_ctx", None)
|
|
297
298
|
page, limit, cursor = run_remote_runs_textual(
|
|
298
299
|
runs_page,
|
|
299
300
|
state.get("cursor", 0),
|
|
300
301
|
callbacks,
|
|
301
302
|
agent_name=agent_name,
|
|
302
303
|
agent_id=agent_id,
|
|
304
|
+
ctx=tui_ctx,
|
|
303
305
|
)
|
|
304
306
|
state["page"] = page
|
|
305
307
|
state["limit"] = limit
|