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
glaip_sdk/__init__.py
CHANGED
|
@@ -4,12 +4,49 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import importlib
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
7
12
|
from glaip_sdk._version import __version__
|
|
8
|
-
from glaip_sdk.client import Client
|
|
9
|
-
from glaip_sdk.exceptions import AIPError
|
|
10
|
-
from glaip_sdk.agents import Agent
|
|
11
|
-
from glaip_sdk.tools import Tool
|
|
12
|
-
from glaip_sdk.mcps import MCP
|
|
13
13
|
|
|
14
|
+
if TYPE_CHECKING: # pragma: no cover - import only for type checking
|
|
15
|
+
from glaip_sdk.agents import Agent
|
|
16
|
+
from glaip_sdk.client import Client
|
|
17
|
+
from glaip_sdk.exceptions import AIPError
|
|
18
|
+
from glaip_sdk.mcps import MCP
|
|
19
|
+
from glaip_sdk.tools import Tool
|
|
14
20
|
|
|
15
21
|
__all__ = ["Client", "Agent", "Tool", "MCP", "AIPError", "__version__"]
|
|
22
|
+
|
|
23
|
+
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
24
|
+
"Client": ("glaip_sdk.client", "Client"),
|
|
25
|
+
"Agent": ("glaip_sdk.agents", "Agent"),
|
|
26
|
+
"Tool": ("glaip_sdk.tools", "Tool"),
|
|
27
|
+
"MCP": ("glaip_sdk.mcps", "MCP"),
|
|
28
|
+
"AIPError": ("glaip_sdk.exceptions", "AIPError"),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def __getattr__(name: str) -> Any:
|
|
33
|
+
"""Lazy attribute access for public SDK symbols to defer heavy imports."""
|
|
34
|
+
if name == "__version__":
|
|
35
|
+
# Import __version__ when accessed via __getattr__
|
|
36
|
+
# This ensures coverage even if __version__ was removed from __dict__ for testing
|
|
37
|
+
from glaip_sdk._version import __version__ as version # noqa: PLC0415
|
|
38
|
+
|
|
39
|
+
globals()["__version__"] = version
|
|
40
|
+
return version
|
|
41
|
+
if name in _LAZY_IMPORTS:
|
|
42
|
+
module_path, attr_name = _LAZY_IMPORTS[name]
|
|
43
|
+
module = importlib.import_module(module_path)
|
|
44
|
+
attr = getattr(module, attr_name)
|
|
45
|
+
globals()[name] = attr
|
|
46
|
+
return attr
|
|
47
|
+
raise AttributeError(f"module 'glaip_sdk' has no attribute {name!r}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def __dir__() -> list[str]:
|
|
51
|
+
"""Return module attributes for dir()."""
|
|
52
|
+
return sorted(__all__)
|
glaip_sdk/agents/base.py
CHANGED
|
@@ -50,16 +50,11 @@ from pathlib import Path
|
|
|
50
50
|
from typing import TYPE_CHECKING, Any
|
|
51
51
|
|
|
52
52
|
from glaip_sdk.registry import get_agent_registry, get_mcp_registry, get_tool_registry
|
|
53
|
-
from glaip_sdk.runner import get_default_runner
|
|
54
|
-
from glaip_sdk.runner.deps import (
|
|
55
|
-
check_local_runtime_available,
|
|
56
|
-
get_local_runtime_missing_message,
|
|
57
|
-
)
|
|
58
|
-
from glaip_sdk.utils.discovery import find_agent
|
|
59
53
|
from glaip_sdk.utils.resource_refs import is_uuid
|
|
60
|
-
from glaip_sdk.utils.runtime_config import normalize_runtime_config_keys
|
|
61
54
|
|
|
62
55
|
if TYPE_CHECKING:
|
|
56
|
+
from glaip_sdk.client.schedules import AgentScheduleManager
|
|
57
|
+
from glaip_sdk.guardrails import GuardrailManager
|
|
63
58
|
from glaip_sdk.models import AgentResponse
|
|
64
59
|
from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
|
|
65
60
|
|
|
@@ -136,6 +131,7 @@ class Agent:
|
|
|
136
131
|
agents: list | None = None,
|
|
137
132
|
mcps: list | None = None,
|
|
138
133
|
model: str | None = _UNSET, # type: ignore[assignment]
|
|
134
|
+
guardrail: GuardrailManager | None = None,
|
|
139
135
|
_client: Any = None,
|
|
140
136
|
**kwargs: Any,
|
|
141
137
|
) -> None:
|
|
@@ -153,7 +149,9 @@ class Agent:
|
|
|
153
149
|
agents: List of sub-agents (Agent classes, instances, or strings).
|
|
154
150
|
mcps: List of MCPs.
|
|
155
151
|
model: Model identifier.
|
|
152
|
+
guardrail: The guardrail manager for content safety.
|
|
156
153
|
_client: Internal client reference (set automatically).
|
|
154
|
+
|
|
157
155
|
**kwargs: Additional configuration parameters:
|
|
158
156
|
- timeout: Execution timeout in seconds.
|
|
159
157
|
- metadata: Optional metadata dictionary.
|
|
@@ -179,6 +177,7 @@ class Agent:
|
|
|
179
177
|
self._agents = agents
|
|
180
178
|
self._mcps = mcps
|
|
181
179
|
self._model = model
|
|
180
|
+
self._guardrail = guardrail
|
|
182
181
|
self._language_model_id: str | None = None
|
|
183
182
|
# Extract parameters from kwargs with _UNSET defaults
|
|
184
183
|
self._timeout = kwargs.pop("timeout", Agent._UNSET) # type: ignore[assignment]
|
|
@@ -458,6 +457,11 @@ class Agent:
|
|
|
458
457
|
return self._mcp_configs
|
|
459
458
|
return None
|
|
460
459
|
|
|
460
|
+
@property
|
|
461
|
+
def guardrail(self) -> GuardrailManager | None:
|
|
462
|
+
"""The guardrail manager for content safety."""
|
|
463
|
+
return self._guardrail
|
|
464
|
+
|
|
461
465
|
@property
|
|
462
466
|
def a2a_profile(self) -> dict[str, Any] | None:
|
|
463
467
|
"""A2A (Agent-to-Agent) profile configuration.
|
|
@@ -518,6 +522,8 @@ class Agent:
|
|
|
518
522
|
from glaip_sdk.utils.client import get_client # noqa: PLC0415
|
|
519
523
|
|
|
520
524
|
client = get_client()
|
|
525
|
+
from glaip_sdk.utils.discovery import find_agent # noqa: PLC0415
|
|
526
|
+
|
|
521
527
|
response = self._create_or_update_agent(config, client, find_agent)
|
|
522
528
|
|
|
523
529
|
# Update self with deployed info
|
|
@@ -551,11 +557,20 @@ class Agent:
|
|
|
551
557
|
|
|
552
558
|
# Handle agent_config with timeout
|
|
553
559
|
# The timeout property is a convenience that maps to agent_config.execution_timeout
|
|
554
|
-
|
|
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
|
+
|
|
555
563
|
if self.timeout and "execution_timeout" not in agent_config:
|
|
556
564
|
agent_config["execution_timeout"] = self.timeout
|
|
557
|
-
|
|
558
|
-
|
|
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
|
|
559
574
|
|
|
560
575
|
# Handle tool_configs - resolve tool names/classes to IDs
|
|
561
576
|
if self.tool_configs:
|
|
@@ -587,11 +602,20 @@ class Agent:
|
|
|
587
602
|
|
|
588
603
|
Returns:
|
|
589
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.
|
|
590
608
|
"""
|
|
591
609
|
if not self.mcps:
|
|
592
610
|
return []
|
|
593
611
|
|
|
594
|
-
|
|
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
|
|
595
619
|
|
|
596
620
|
def _resolve_tools(self, registry: ToolRegistry) -> list[str]:
|
|
597
621
|
"""Resolve tool references to IDs using ToolRegistry.
|
|
@@ -605,12 +629,20 @@ class Agent:
|
|
|
605
629
|
|
|
606
630
|
Returns:
|
|
607
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.
|
|
608
635
|
"""
|
|
609
636
|
if not self.tools:
|
|
610
637
|
return []
|
|
611
638
|
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
614
646
|
|
|
615
647
|
def _resolve_tool_configs(self, registry: ToolRegistry) -> dict[str, Any]:
|
|
616
648
|
"""Resolve tool_configs keys from tool names/classes to tool IDs.
|
|
@@ -653,6 +685,8 @@ class Agent:
|
|
|
653
685
|
try:
|
|
654
686
|
# Resolve key (tool name/class) to Tool object, get ID
|
|
655
687
|
tool = registry.resolve(key)
|
|
688
|
+
if not tool.id:
|
|
689
|
+
raise ValueError(f"Resolved tool has no ID: {key}")
|
|
656
690
|
resolved[tool.id] = config
|
|
657
691
|
except (ValueError, KeyError) as e:
|
|
658
692
|
raise ValueError(f"Failed to resolve tool config key: {key}") from e
|
|
@@ -688,6 +722,8 @@ class Agent:
|
|
|
688
722
|
resolved_id = key
|
|
689
723
|
else:
|
|
690
724
|
mcp = registry.resolve(key)
|
|
725
|
+
if not mcp.id:
|
|
726
|
+
raise ValueError(f"Resolved MCP has no ID: {key}")
|
|
691
727
|
resolved_id = mcp.id
|
|
692
728
|
|
|
693
729
|
if resolved_id in resolved:
|
|
@@ -703,7 +739,7 @@ class Agent:
|
|
|
703
739
|
|
|
704
740
|
return resolved
|
|
705
741
|
|
|
706
|
-
def _resolve_agents(self, registry: AgentRegistry) -> list:
|
|
742
|
+
def _resolve_agents(self, registry: AgentRegistry) -> list[str]:
|
|
707
743
|
"""Resolve sub-agent references using AgentRegistry.
|
|
708
744
|
|
|
709
745
|
Uses the global AgentRegistry to cache Agent objects across deployments.
|
|
@@ -715,12 +751,20 @@ class Agent:
|
|
|
715
751
|
|
|
716
752
|
Returns:
|
|
717
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.
|
|
718
757
|
"""
|
|
719
758
|
if not self.agents:
|
|
720
759
|
return []
|
|
721
760
|
|
|
722
|
-
|
|
723
|
-
|
|
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
|
|
724
768
|
|
|
725
769
|
def _create_or_update_agent(
|
|
726
770
|
self,
|
|
@@ -811,6 +855,8 @@ class Agent:
|
|
|
811
855
|
Returns:
|
|
812
856
|
Dictionary containing Agent attributes.
|
|
813
857
|
"""
|
|
858
|
+
config = self._build_config(get_tool_registry(), get_mcp_registry())
|
|
859
|
+
|
|
814
860
|
data = {
|
|
815
861
|
"id": self._id,
|
|
816
862
|
"name": self.name,
|
|
@@ -824,10 +870,11 @@ class Agent:
|
|
|
824
870
|
"mcps": self.mcps,
|
|
825
871
|
"timeout": self.timeout,
|
|
826
872
|
"metadata": self.metadata,
|
|
827
|
-
"agent_config":
|
|
873
|
+
"agent_config": config.get("agent_config"),
|
|
828
874
|
"tool_configs": self.tool_configs,
|
|
829
875
|
"mcp_configs": self.mcp_configs,
|
|
830
876
|
"a2a_profile": self.a2a_profile,
|
|
877
|
+
"guardrail": self.guardrail,
|
|
831
878
|
"created_at": self._created_at,
|
|
832
879
|
"updated_at": self._updated_at,
|
|
833
880
|
}
|
|
@@ -847,6 +894,36 @@ class Agent:
|
|
|
847
894
|
self._client = client
|
|
848
895
|
return self
|
|
849
896
|
|
|
897
|
+
@property
|
|
898
|
+
def schedule(self) -> AgentScheduleManager:
|
|
899
|
+
"""Get the schedule manager for this agent.
|
|
900
|
+
|
|
901
|
+
Provides a convenient interface for managing schedules scoped to this agent.
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
AgentScheduleManager for schedule operations
|
|
905
|
+
|
|
906
|
+
Raises:
|
|
907
|
+
ValueError: If agent is not deployed
|
|
908
|
+
RuntimeError: If agent is not bound to a client
|
|
909
|
+
|
|
910
|
+
Example:
|
|
911
|
+
>>> agent = client.get_agent_by_id("agent-id")
|
|
912
|
+
>>> schedules = agent.schedule.list()
|
|
913
|
+
>>> new_schedule = agent.schedule.create(
|
|
914
|
+
... input="Daily task",
|
|
915
|
+
... schedule="0 9 * * 1-5"
|
|
916
|
+
... )
|
|
917
|
+
"""
|
|
918
|
+
if not self.id:
|
|
919
|
+
raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
|
|
920
|
+
if not self._client:
|
|
921
|
+
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
922
|
+
|
|
923
|
+
from glaip_sdk.client.schedules import AgentScheduleManager # noqa: PLC0415
|
|
924
|
+
|
|
925
|
+
return AgentScheduleManager(self, self._client.schedules)
|
|
926
|
+
|
|
850
927
|
def _prepare_run_kwargs(
|
|
851
928
|
self,
|
|
852
929
|
message: str,
|
|
@@ -869,7 +946,7 @@ class Agent:
|
|
|
869
946
|
ValueError: If the agent hasn't been deployed yet.
|
|
870
947
|
RuntimeError: If client is not available.
|
|
871
948
|
"""
|
|
872
|
-
if not self.id:
|
|
949
|
+
if not self.id: # pragma: no cover - defensive: called only when self.id is truthy
|
|
873
950
|
raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
|
|
874
951
|
if not self._client:
|
|
875
952
|
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
@@ -883,6 +960,10 @@ class Agent:
|
|
|
883
960
|
}
|
|
884
961
|
|
|
885
962
|
if runtime_config is not None:
|
|
963
|
+
from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
|
|
964
|
+
normalize_runtime_config_keys,
|
|
965
|
+
)
|
|
966
|
+
|
|
886
967
|
call_kwargs["runtime_config"] = normalize_runtime_config_keys(
|
|
887
968
|
runtime_config,
|
|
888
969
|
tool_registry=get_tool_registry(),
|
|
@@ -893,10 +974,68 @@ class Agent:
|
|
|
893
974
|
call_kwargs.update(kwargs)
|
|
894
975
|
return agent_client, call_kwargs
|
|
895
976
|
|
|
977
|
+
def _get_local_runner_or_raise(self) -> Any:
|
|
978
|
+
"""Get the local runner if available, otherwise raise ValueError.
|
|
979
|
+
|
|
980
|
+
Returns:
|
|
981
|
+
The default local runner instance.
|
|
982
|
+
|
|
983
|
+
Raises:
|
|
984
|
+
ValueError: If local runtime is not available.
|
|
985
|
+
"""
|
|
986
|
+
from glaip_sdk.runner import get_default_runner # noqa: PLC0415
|
|
987
|
+
from glaip_sdk.runner.deps import ( # noqa: PLC0415
|
|
988
|
+
check_local_runtime_available,
|
|
989
|
+
get_local_runtime_missing_message,
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
if check_local_runtime_available():
|
|
993
|
+
return get_default_runner()
|
|
994
|
+
|
|
995
|
+
# If agent is not deployed, it *must* use local runtime
|
|
996
|
+
if not self.id:
|
|
997
|
+
raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
|
|
998
|
+
|
|
999
|
+
# If agent IS deployed but local execution was forced (local=True)
|
|
1000
|
+
raise ValueError(
|
|
1001
|
+
f"Local execution override was requested, but local runtime is missing.\n\n"
|
|
1002
|
+
f"{get_local_runtime_missing_message()}"
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
def _prepare_local_runner_kwargs(
|
|
1006
|
+
self,
|
|
1007
|
+
message: str,
|
|
1008
|
+
verbose: bool,
|
|
1009
|
+
runtime_config: dict[str, Any] | None,
|
|
1010
|
+
chat_history: list[dict[str, str]] | None,
|
|
1011
|
+
**kwargs: Any,
|
|
1012
|
+
) -> dict[str, Any]:
|
|
1013
|
+
"""Prepare kwargs for local runner execution.
|
|
1014
|
+
|
|
1015
|
+
Args:
|
|
1016
|
+
message: The message to send to the agent.
|
|
1017
|
+
verbose: If True, print streaming output to console.
|
|
1018
|
+
runtime_config: Optional runtime configuration.
|
|
1019
|
+
chat_history: Optional list of prior conversation messages.
|
|
1020
|
+
**kwargs: Additional arguments.
|
|
1021
|
+
|
|
1022
|
+
Returns:
|
|
1023
|
+
Dictionary of prepared kwargs for runner.run() or runner.arun().
|
|
1024
|
+
"""
|
|
1025
|
+
return {
|
|
1026
|
+
"agent": self,
|
|
1027
|
+
"message": message,
|
|
1028
|
+
"verbose": verbose,
|
|
1029
|
+
"runtime_config": runtime_config,
|
|
1030
|
+
"chat_history": chat_history,
|
|
1031
|
+
**kwargs,
|
|
1032
|
+
}
|
|
1033
|
+
|
|
896
1034
|
def run(
|
|
897
1035
|
self,
|
|
898
1036
|
message: str,
|
|
899
1037
|
verbose: bool = False,
|
|
1038
|
+
local: bool = False,
|
|
900
1039
|
runtime_config: dict[str, Any] | None = None,
|
|
901
1040
|
chat_history: list[dict[str, str]] | None = None,
|
|
902
1041
|
**kwargs: Any,
|
|
@@ -909,9 +1048,13 @@ class Agent:
|
|
|
909
1048
|
- **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
|
|
910
1049
|
execution happens locally via aip-agents (no server required).
|
|
911
1050
|
|
|
1051
|
+
You can force local execution for a deployed agent by passing `local=True`.
|
|
1052
|
+
|
|
912
1053
|
Args:
|
|
913
1054
|
message: The message to send to the agent.
|
|
914
1055
|
verbose: If True, print streaming output to console. Defaults to False.
|
|
1056
|
+
local: If True, force local execution even if the agent is deployed.
|
|
1057
|
+
Defaults to False.
|
|
915
1058
|
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
916
1059
|
Keys can be SDK objects, UUIDs, or names. Example:
|
|
917
1060
|
{
|
|
@@ -933,42 +1076,47 @@ class Agent:
|
|
|
933
1076
|
RuntimeError: If server-backed execution fails due to client issues.
|
|
934
1077
|
"""
|
|
935
1078
|
# Backend routing: deployed agents use server, undeployed use local (if available)
|
|
936
|
-
if self.id:
|
|
1079
|
+
if self.id and not local:
|
|
937
1080
|
# Server-backed execution path (agent is deployed)
|
|
938
1081
|
agent_client, call_kwargs = self._prepare_run_kwargs(
|
|
939
|
-
message,
|
|
1082
|
+
message,
|
|
1083
|
+
verbose,
|
|
1084
|
+
runtime_config or kwargs.get("runtime_config"),
|
|
1085
|
+
**kwargs,
|
|
940
1086
|
)
|
|
941
1087
|
if chat_history is not None:
|
|
942
1088
|
call_kwargs["chat_history"] = chat_history
|
|
943
1089
|
return agent_client.run_agent(**call_kwargs)
|
|
944
1090
|
|
|
945
|
-
# Local execution path (agent is not deployed)
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
agent=self,
|
|
950
|
-
message=message,
|
|
951
|
-
verbose=verbose,
|
|
952
|
-
runtime_config=runtime_config,
|
|
953
|
-
chat_history=chat_history,
|
|
954
|
-
**kwargs,
|
|
955
|
-
)
|
|
956
|
-
|
|
957
|
-
# Neither deployed nor local runtime available - provide actionable error
|
|
958
|
-
raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
|
|
1091
|
+
# Local execution path (agent is not deployed OR local=True)
|
|
1092
|
+
runner = self._get_local_runner_or_raise()
|
|
1093
|
+
local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
|
|
1094
|
+
return runner.run(**local_kwargs)
|
|
959
1095
|
|
|
960
1096
|
async def arun(
|
|
961
1097
|
self,
|
|
962
1098
|
message: str,
|
|
963
1099
|
verbose: bool = False,
|
|
1100
|
+
local: bool = False,
|
|
964
1101
|
runtime_config: dict[str, Any] | None = None,
|
|
1102
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
965
1103
|
**kwargs: Any,
|
|
966
1104
|
) -> AsyncGenerator[dict, None]:
|
|
967
1105
|
"""Run the agent asynchronously with streaming output.
|
|
968
1106
|
|
|
1107
|
+
Supports two execution modes:
|
|
1108
|
+
- **Server-backed**: When the agent is deployed (has an ID), execution
|
|
1109
|
+
happens via the AIP backend server with streaming.
|
|
1110
|
+
- **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
|
|
1111
|
+
execution happens locally via aip-agents (no server required).
|
|
1112
|
+
|
|
1113
|
+
You can force local execution for a deployed agent by passing `local=True`.
|
|
1114
|
+
|
|
969
1115
|
Args:
|
|
970
1116
|
message: The message to send to the agent.
|
|
971
|
-
verbose: If True, print streaming output to console.
|
|
1117
|
+
verbose: If True, print streaming output to console. Defaults to False.
|
|
1118
|
+
local: If True, force local execution even if the agent is deployed.
|
|
1119
|
+
Defaults to False.
|
|
972
1120
|
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
973
1121
|
Keys can be SDK objects, UUIDs, or names. Example:
|
|
974
1122
|
{
|
|
@@ -976,20 +1124,47 @@ class Agent:
|
|
|
976
1124
|
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
977
1125
|
"agent_config": {"planning": True},
|
|
978
1126
|
}
|
|
1127
|
+
Defaults to None.
|
|
1128
|
+
chat_history: Optional list of prior conversation messages for context.
|
|
1129
|
+
Each message is a dict with "role" and "content" keys.
|
|
1130
|
+
Defaults to None.
|
|
979
1131
|
**kwargs: Additional arguments to pass to the run API.
|
|
980
1132
|
|
|
981
1133
|
Yields:
|
|
982
1134
|
Streaming response chunks from the agent.
|
|
983
1135
|
|
|
984
1136
|
Raises:
|
|
985
|
-
ValueError: If the agent
|
|
986
|
-
RuntimeError: If
|
|
1137
|
+
ValueError: If the agent is not deployed and local runtime is not available.
|
|
1138
|
+
RuntimeError: If server-backed execution fails due to client issues.
|
|
987
1139
|
"""
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1140
|
+
# Backend routing: deployed agents use server, undeployed use local (if available)
|
|
1141
|
+
if self.id and not local:
|
|
1142
|
+
# Server-backed execution path (agent is deployed)
|
|
1143
|
+
agent_client, call_kwargs = self._prepare_run_kwargs(
|
|
1144
|
+
message,
|
|
1145
|
+
verbose,
|
|
1146
|
+
runtime_config or kwargs.get("runtime_config"),
|
|
1147
|
+
**kwargs,
|
|
1148
|
+
)
|
|
1149
|
+
if chat_history is not None:
|
|
1150
|
+
call_kwargs["chat_history"] = chat_history
|
|
1151
|
+
|
|
1152
|
+
async for chunk in agent_client.arun_agent(**call_kwargs):
|
|
1153
|
+
yield chunk
|
|
1154
|
+
return
|
|
1155
|
+
|
|
1156
|
+
# Local execution path (agent is not deployed OR local=True)
|
|
1157
|
+
runner = self._get_local_runner_or_raise()
|
|
1158
|
+
local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
|
|
1159
|
+
result = await runner.arun(**local_kwargs)
|
|
1160
|
+
# Yield a final_response event for consistency with server-backed execution
|
|
1161
|
+
# Include event_type for A2A event shape parity
|
|
1162
|
+
yield {
|
|
1163
|
+
"event_type": "final_response",
|
|
1164
|
+
"metadata": {"kind": "final_response"},
|
|
1165
|
+
"content": result,
|
|
1166
|
+
"is_final": True,
|
|
1167
|
+
}
|
|
993
1168
|
|
|
994
1169
|
def update(self, **kwargs: Any) -> Agent:
|
|
995
1170
|
"""Update the deployed agent with new configuration.
|
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
|
glaip_sdk/cli/account_store.py
CHANGED
|
@@ -523,6 +523,21 @@ class AccountStore:
|
|
|
523
523
|
|
|
524
524
|
self._save_config(config)
|
|
525
525
|
|
|
526
|
+
def save_config_updates(self, config: dict[str, Any]) -> None:
|
|
527
|
+
"""Save config updates, preserving all existing keys.
|
|
528
|
+
|
|
529
|
+
This method allows external code to update arbitrary config keys
|
|
530
|
+
(e.g., TUI preferences) while preserving the full config structure.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
config: Complete configuration dictionary to save. This should
|
|
534
|
+
include all keys that should be preserved, not just updates.
|
|
535
|
+
|
|
536
|
+
Raises:
|
|
537
|
+
AccountStoreError: If config file cannot be written.
|
|
538
|
+
"""
|
|
539
|
+
self._save_config(config)
|
|
540
|
+
|
|
526
541
|
|
|
527
542
|
# Global instance for convenience
|
|
528
543
|
_account_store = AccountStore()
|