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 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
- agent_config = dict(self.agent_config) if self.agent_config else {}
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
- if agent_config:
554
- config["agent_config"] = agent_config
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
- return [registry.resolve(mcp_ref).id for mcp_ref in self.mcps]
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
- # Resolve each tool reference to a Tool object, extract ID
609
- return [registry.resolve(tool_ref).id for tool_ref in self.tools]
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
- # Resolve each agent reference to a deployed Agent, extract ID
719
- return [registry.resolve(agent_ref).id for agent_ref in self.agents]
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": self.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(color_system=color_system, no_color=no_color, soft_wrap=True)
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