glaip-sdk 0.6.10__py3-none-any.whl → 0.7.27__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.
Files changed (139) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. glaip_sdk/agents/base.py +295 -37
  3. glaip_sdk/agents/component.py +233 -0
  4. glaip_sdk/branding.py +113 -2
  5. glaip_sdk/cli/account_store.py +15 -0
  6. glaip_sdk/cli/auth.py +14 -8
  7. glaip_sdk/cli/commands/accounts.py +1 -1
  8. glaip_sdk/cli/commands/agents/__init__.py +116 -0
  9. glaip_sdk/cli/commands/agents/_common.py +562 -0
  10. glaip_sdk/cli/commands/agents/create.py +155 -0
  11. glaip_sdk/cli/commands/agents/delete.py +64 -0
  12. glaip_sdk/cli/commands/agents/get.py +89 -0
  13. glaip_sdk/cli/commands/agents/list.py +129 -0
  14. glaip_sdk/cli/commands/agents/run.py +264 -0
  15. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  16. glaip_sdk/cli/commands/agents/update.py +112 -0
  17. glaip_sdk/cli/commands/common_config.py +15 -12
  18. glaip_sdk/cli/commands/configure.py +1 -2
  19. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  20. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  21. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  22. glaip_sdk/cli/commands/mcps/create.py +152 -0
  23. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  24. glaip_sdk/cli/commands/mcps/get.py +212 -0
  25. glaip_sdk/cli/commands/mcps/list.py +69 -0
  26. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  27. glaip_sdk/cli/commands/mcps/update.py +190 -0
  28. glaip_sdk/cli/commands/models.py +2 -4
  29. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  30. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  31. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  32. glaip_sdk/cli/commands/tools/_common.py +80 -0
  33. glaip_sdk/cli/commands/tools/create.py +228 -0
  34. glaip_sdk/cli/commands/tools/delete.py +61 -0
  35. glaip_sdk/cli/commands/tools/get.py +103 -0
  36. glaip_sdk/cli/commands/tools/list.py +69 -0
  37. glaip_sdk/cli/commands/tools/script.py +49 -0
  38. glaip_sdk/cli/commands/tools/update.py +102 -0
  39. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  40. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  41. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  42. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  43. glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
  44. glaip_sdk/cli/commands/update.py +163 -17
  45. glaip_sdk/cli/config.py +1 -0
  46. glaip_sdk/cli/core/output.py +12 -7
  47. glaip_sdk/cli/entrypoint.py +20 -0
  48. glaip_sdk/cli/main.py +127 -39
  49. glaip_sdk/cli/pager.py +3 -3
  50. glaip_sdk/cli/resolution.py +2 -1
  51. glaip_sdk/cli/slash/accounts_controller.py +3 -1
  52. glaip_sdk/cli/slash/agent_session.py +1 -1
  53. glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
  54. glaip_sdk/cli/slash/session.py +343 -20
  55. glaip_sdk/cli/slash/tui/__init__.py +29 -1
  56. glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
  57. glaip_sdk/cli/slash/tui/accounts_app.py +1117 -126
  58. glaip_sdk/cli/slash/tui/clipboard.py +316 -0
  59. glaip_sdk/cli/slash/tui/context.py +92 -0
  60. glaip_sdk/cli/slash/tui/indicators.py +341 -0
  61. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  62. glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
  63. glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
  64. glaip_sdk/cli/slash/tui/loading.py +43 -21
  65. glaip_sdk/cli/slash/tui/remote_runs_app.py +178 -20
  66. glaip_sdk/cli/slash/tui/terminal.py +407 -0
  67. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  68. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  69. glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
  70. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  71. glaip_sdk/cli/slash/tui/toast.py +388 -0
  72. glaip_sdk/cli/transcript/history.py +1 -1
  73. glaip_sdk/cli/transcript/viewer.py +1 -1
  74. glaip_sdk/cli/tui_settings.py +125 -0
  75. glaip_sdk/cli/update_notifier.py +215 -7
  76. glaip_sdk/cli/validators.py +1 -1
  77. glaip_sdk/client/__init__.py +2 -1
  78. glaip_sdk/client/_schedule_payloads.py +89 -0
  79. glaip_sdk/client/agents.py +290 -16
  80. glaip_sdk/client/base.py +25 -0
  81. glaip_sdk/client/hitl.py +136 -0
  82. glaip_sdk/client/main.py +7 -5
  83. glaip_sdk/client/mcps.py +44 -13
  84. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  85. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
  86. glaip_sdk/client/payloads/agent/responses.py +43 -0
  87. glaip_sdk/client/run_rendering.py +414 -3
  88. glaip_sdk/client/schedules.py +439 -0
  89. glaip_sdk/client/tools.py +52 -23
  90. glaip_sdk/config/constants.py +22 -2
  91. glaip_sdk/guardrails/__init__.py +80 -0
  92. glaip_sdk/guardrails/serializer.py +91 -0
  93. glaip_sdk/hitl/__init__.py +48 -0
  94. glaip_sdk/hitl/base.py +64 -0
  95. glaip_sdk/hitl/callback.py +43 -0
  96. glaip_sdk/hitl/local.py +121 -0
  97. glaip_sdk/hitl/remote.py +523 -0
  98. glaip_sdk/models/__init__.py +47 -1
  99. glaip_sdk/models/_provider_mappings.py +101 -0
  100. glaip_sdk/models/_validation.py +97 -0
  101. glaip_sdk/models/agent.py +2 -1
  102. glaip_sdk/models/agent_runs.py +2 -1
  103. glaip_sdk/models/constants.py +141 -0
  104. glaip_sdk/models/model.py +170 -0
  105. glaip_sdk/models/schedule.py +224 -0
  106. glaip_sdk/payload_schemas/agent.py +1 -0
  107. glaip_sdk/payload_schemas/guardrails.py +34 -0
  108. glaip_sdk/ptc.py +145 -0
  109. glaip_sdk/registry/tool.py +270 -57
  110. glaip_sdk/runner/__init__.py +20 -3
  111. glaip_sdk/runner/deps.py +6 -6
  112. glaip_sdk/runner/langgraph.py +427 -39
  113. glaip_sdk/runner/logging_config.py +77 -0
  114. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +30 -9
  115. glaip_sdk/runner/ptc_adapter.py +98 -0
  116. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +25 -2
  117. glaip_sdk/schedules/__init__.py +22 -0
  118. glaip_sdk/schedules/base.py +291 -0
  119. glaip_sdk/tools/base.py +67 -14
  120. glaip_sdk/utils/__init__.py +1 -0
  121. glaip_sdk/utils/agent_config.py +8 -2
  122. glaip_sdk/utils/bundler.py +138 -2
  123. glaip_sdk/utils/import_resolver.py +427 -49
  124. glaip_sdk/utils/rendering/renderer/base.py +58 -0
  125. glaip_sdk/utils/runtime_config.py +3 -2
  126. glaip_sdk/utils/sync.py +31 -11
  127. glaip_sdk/utils/tool_detection.py +274 -6
  128. glaip_sdk/utils/tool_storage_provider.py +140 -0
  129. {glaip_sdk-0.6.10.dist-info → glaip_sdk-0.7.27.dist-info}/METADATA +51 -40
  130. glaip_sdk-0.7.27.dist-info/RECORD +227 -0
  131. {glaip_sdk-0.6.10.dist-info → glaip_sdk-0.7.27.dist-info}/WHEEL +2 -1
  132. glaip_sdk-0.7.27.dist-info/entry_points.txt +2 -0
  133. glaip_sdk-0.7.27.dist-info/top_level.txt +1 -0
  134. glaip_sdk/cli/commands/agents.py +0 -1509
  135. glaip_sdk/cli/commands/mcps.py +0 -1356
  136. glaip_sdk/cli/commands/tools.py +0 -576
  137. glaip_sdk/cli/utils.py +0 -263
  138. glaip_sdk-0.6.10.dist-info/RECORD +0 -159
  139. glaip_sdk-0.6.10.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
@@ -46,25 +46,22 @@ import inspect
46
46
  import logging
47
47
  import warnings
48
48
  from collections.abc import AsyncGenerator
49
-
50
-
51
49
  from pathlib import Path
52
50
  from typing import TYPE_CHECKING, Any
53
51
 
54
52
  from glaip_sdk.registry import get_agent_registry, get_mcp_registry, get_tool_registry
55
- from glaip_sdk.runner import get_default_runner
56
- from glaip_sdk.runner.deps import (
57
- check_local_runtime_available,
58
- get_local_runtime_missing_message,
59
- )
60
- from glaip_sdk.utils.discovery import find_agent
61
53
  from glaip_sdk.utils.resource_refs import is_uuid
62
- from glaip_sdk.utils.runtime_config import normalize_runtime_config_keys
63
54
 
64
55
  if TYPE_CHECKING:
65
- from glaip_sdk.models import AgentResponse
56
+ from glaip_sdk.client.schedules import AgentScheduleManager
57
+ from glaip_sdk.guardrails import GuardrailManager
58
+ from glaip_sdk.models import AgentResponse, Model
59
+ from glaip_sdk.ptc import PTC
66
60
  from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
67
61
 
62
+ # Import model validation utility
63
+ from glaip_sdk.models._validation import _validate_model
64
+
68
65
  logger = logging.getLogger(__name__)
69
66
 
70
67
  _AGENT_NOT_DEPLOYED_MSG = "Agent must be deployed before running. Call deploy() first."
@@ -106,11 +103,11 @@ class Agent:
106
103
  - instruction: Agent instruction text (required)
107
104
  - description: Agent description (default: "")
108
105
  - tools: List of tools (default: [])
106
+ - model: Optional model override (default: None)
109
107
  - agents: List of sub-agents (default: [])
110
108
  - mcps: List of MCPs (default: [])
111
109
  - timeout: Timeout in seconds (default: 300)
112
110
  - metadata: Optional metadata dict (default: None)
113
- - model: Optional model override (default: None)
114
111
  - framework: Agent framework (default: "langchain")
115
112
  - version: Agent version (default: "1.0.0")
116
113
  - agent_type: Agent type (default: "config")
@@ -137,7 +134,9 @@ class Agent:
137
134
  tools: list | None = None,
138
135
  agents: list | None = None,
139
136
  mcps: list | None = None,
140
- model: str | None = _UNSET, # type: ignore[assignment]
137
+ model: str | Model | None = _UNSET, # type: ignore[assignment]
138
+ guardrail: GuardrailManager | None = None,
139
+ ptc: PTC | None = None,
141
140
  _client: Any = None,
142
141
  **kwargs: Any,
143
142
  ) -> None:
@@ -154,8 +153,11 @@ class Agent:
154
153
  tools: List of tools (Tool classes, SDK Tool objects, or strings).
155
154
  agents: List of sub-agents (Agent classes, instances, or strings).
156
155
  mcps: List of MCPs.
157
- model: Model identifier.
156
+ model: Model identifier or Model configuration object.
157
+ guardrail: The guardrail manager for content safety.
158
+ ptc: PTC configuration for local runs (sandbox code execution).
158
159
  _client: Internal client reference (set automatically).
160
+
159
161
  **kwargs: Additional configuration parameters:
160
162
  - timeout: Execution timeout in seconds.
161
163
  - metadata: Optional metadata dictionary.
@@ -166,6 +168,7 @@ class Agent:
166
168
  - tool_configs: Per-tool configuration overrides.
167
169
  - mcp_configs: Per-MCP configuration overrides.
168
170
  - a2a_profile: A2A profile configuration.
171
+ - ptc: PTC configuration (local runs only).
169
172
  """
170
173
  # Instance attributes for deployed agents
171
174
  self._id = id
@@ -180,7 +183,9 @@ class Agent:
180
183
  self._tools = tools
181
184
  self._agents = agents
182
185
  self._mcps = mcps
183
- self._model = model
186
+ self._model = self._validate_and_set_model(model)
187
+ self._guardrail = guardrail
188
+ self._ptc = ptc
184
189
  self._language_model_id: str | None = None
185
190
  # Extract parameters from kwargs with _UNSET defaults
186
191
  self._timeout = kwargs.pop("timeout", Agent._UNSET) # type: ignore[assignment]
@@ -188,9 +193,25 @@ class Agent:
188
193
  self._framework = kwargs.pop("framework", Agent._UNSET) # type: ignore[assignment]
189
194
  self._version = kwargs.pop("version", Agent._UNSET) # type: ignore[assignment]
190
195
  self._agent_type = kwargs.pop("agent_type", Agent._UNSET) # type: ignore[assignment]
196
+
197
+ # Handle 'type' as a legacy alias for 'agent_type'
198
+ legacy_type = kwargs.pop("type", Agent._UNSET)
199
+ if legacy_type is not Agent._UNSET:
200
+ warnings.warn(
201
+ "The 'type' parameter is deprecated and will be removed in a future version. Use 'agent_type' instead.",
202
+ DeprecationWarning,
203
+ stacklevel=2,
204
+ )
205
+ if self._agent_type is Agent._UNSET:
206
+ self._agent_type = legacy_type
207
+
191
208
  self._agent_config = kwargs.pop("agent_config", Agent._UNSET) # type: ignore[assignment]
192
209
  self._tool_configs = kwargs.pop("tool_configs", Agent._UNSET) # type: ignore[assignment]
193
- self._mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET) # type: ignore[assignment]
210
+ mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET)
211
+ if mcp_configs is not Agent._UNSET and isinstance(mcp_configs, dict):
212
+ self._mcp_configs = self._normalize_mcp_configs(mcp_configs)
213
+ else:
214
+ self._mcp_configs = mcp_configs # type: ignore[assignment]
194
215
  self._a2a_profile = kwargs.pop("a2a_profile", Agent._UNSET) # type: ignore[assignment]
195
216
 
196
217
  # Warn about unexpected kwargs
@@ -201,6 +222,30 @@ class Agent:
201
222
  stacklevel=2,
202
223
  )
203
224
 
225
+ def _validate_and_set_model(self, model: str | Any) -> str | Any:
226
+ """Validate and normalize model parameter.
227
+
228
+ Supports both string model identifiers and Model objects:
229
+ - String: Simple model identifier (e.g., "openai/gpt-4o" or OpenAI.GPT_4O)
230
+ - Model: Model object with credentials/hyperparameters for local execution
231
+
232
+ Args:
233
+ model: Model identifier (string) or Model object.
234
+
235
+ Returns:
236
+ Validated model (string or Model object).
237
+ """
238
+ if model is None or model is Agent._UNSET:
239
+ return model
240
+
241
+ from glaip_sdk.models import Model # noqa: PLC0415
242
+
243
+ if isinstance(model, str):
244
+ return _validate_model(model)
245
+ elif isinstance(model, Model):
246
+ return model
247
+ return model
248
+
204
249
  # ─────────────────────────────────────────────────────────────────
205
250
  # Properties (override in subclasses OR pass to __init__)
206
251
  # ─────────────────────────────────────────────────────────────────
@@ -345,11 +390,11 @@ class Agent:
345
390
  return None
346
391
 
347
392
  @property
348
- def model(self) -> str | None:
393
+ def model(self) -> str | Model | None:
349
394
  """Optional model override.
350
395
 
351
396
  Returns:
352
- Model identifier string or None to use default.
397
+ Model identifier string, Model object, or None to use default.
353
398
  """
354
399
  if self._model is not self._UNSET:
355
400
  return self._model
@@ -460,6 +505,16 @@ class Agent:
460
505
  return self._mcp_configs
461
506
  return None
462
507
 
508
+ @property
509
+ def guardrail(self) -> GuardrailManager | None:
510
+ """The guardrail manager for content safety."""
511
+ return self._guardrail
512
+
513
+ @property
514
+ def ptc(self) -> PTC | None:
515
+ """PTC configuration for local runs (sandbox code execution)."""
516
+ return self._ptc
517
+
463
518
  @property
464
519
  def a2a_profile(self) -> dict[str, Any] | None:
465
520
  """A2A (Agent-to-Agent) profile configuration.
@@ -506,6 +561,15 @@ class Agent:
506
561
  """
507
562
  logger.info("Deploying agent: %s", self.name)
508
563
 
564
+ if self._ptc is not None:
565
+ warnings.warn(
566
+ "PTC (Programmatic Tool Calling) is configured but not supported for remote deployments yet. "
567
+ "PTC will only work for local runs (agent.run(..., local=True)). "
568
+ "The PTC configuration will not be included in the deployed agent.",
569
+ UserWarning,
570
+ stacklevel=2,
571
+ )
572
+
509
573
  # Resolve tools FIRST - this uploads them and populates the registry
510
574
  tool_ids = self._resolve_tools(get_tool_registry())
511
575
 
@@ -520,6 +584,8 @@ class Agent:
520
584
  from glaip_sdk.utils.client import get_client # noqa: PLC0415
521
585
 
522
586
  client = get_client()
587
+ from glaip_sdk.utils.discovery import find_agent # noqa: PLC0415
588
+
523
589
  response = self._create_or_update_agent(config, client, find_agent)
524
590
 
525
591
  # Update self with deployed info
@@ -545,19 +611,33 @@ class Agent:
545
611
  "framework": self.framework,
546
612
  "version": self.version,
547
613
  "agent_type": self.agent_type,
548
- "model": self.model,
549
614
  }
550
615
 
616
+ if self.model:
617
+ if isinstance(self.model, str):
618
+ config["model"] = self.model
619
+ else:
620
+ config["model"] = self.model.id
621
+
551
622
  # Handle metadata (default to empty dict if None)
552
623
  config["metadata"] = self.metadata or {}
553
624
 
554
625
  # Handle agent_config with timeout
555
626
  # The timeout property is a convenience that maps to agent_config.execution_timeout
556
- agent_config = dict(self.agent_config) if self.agent_config else {}
627
+ raw_config = self.agent_config if self.agent_config is not self._UNSET else {}
628
+ agent_config = dict(raw_config) if raw_config else {}
629
+
557
630
  if self.timeout and "execution_timeout" not in agent_config:
558
631
  agent_config["execution_timeout"] = self.timeout
559
- if agent_config:
560
- config["agent_config"] = agent_config
632
+
633
+ if self.guardrail:
634
+ from glaip_sdk.guardrails.serializer import ( # noqa: PLC0415
635
+ serialize_guardrail_manager,
636
+ )
637
+
638
+ agent_config["guardrails"] = serialize_guardrail_manager(self.guardrail)
639
+
640
+ config["agent_config"] = agent_config
561
641
 
562
642
  # Handle tool_configs - resolve tool names/classes to IDs
563
643
  if self.tool_configs:
@@ -589,11 +669,20 @@ class Agent:
589
669
 
590
670
  Returns:
591
671
  List of resolved MCP IDs for the API payload.
672
+
673
+ Raises:
674
+ ValueError: If an MCP fails to resolve to a valid ID.
592
675
  """
593
676
  if not self.mcps:
594
677
  return []
595
678
 
596
- return [registry.resolve(mcp_ref).id for mcp_ref in self.mcps]
679
+ resolved_ids: list[str] = []
680
+ for mcp_ref in self.mcps:
681
+ mcp = registry.resolve(mcp_ref)
682
+ if not mcp.id:
683
+ raise ValueError(f"Failed to resolve ID for MCP: {mcp_ref}")
684
+ resolved_ids.append(mcp.id)
685
+ return resolved_ids
597
686
 
598
687
  def _resolve_tools(self, registry: ToolRegistry) -> list[str]:
599
688
  """Resolve tool references to IDs using ToolRegistry.
@@ -607,12 +696,20 @@ class Agent:
607
696
 
608
697
  Returns:
609
698
  List of resolved tool IDs for the API payload.
699
+
700
+ Raises:
701
+ ValueError: If a tool fails to resolve to a valid ID.
610
702
  """
611
703
  if not self.tools:
612
704
  return []
613
705
 
614
- # Resolve each tool reference to a Tool object, extract ID
615
- return [registry.resolve(tool_ref).id for tool_ref in self.tools]
706
+ resolved_ids: list[str] = []
707
+ for tool_ref in self.tools:
708
+ tool = registry.resolve(tool_ref)
709
+ if not tool.id:
710
+ raise ValueError(f"Failed to resolve ID for tool: {tool_ref}")
711
+ resolved_ids.append(tool.id)
712
+ return resolved_ids
616
713
 
617
714
  def _resolve_tool_configs(self, registry: ToolRegistry) -> dict[str, Any]:
618
715
  """Resolve tool_configs keys from tool names/classes to tool IDs.
@@ -655,6 +752,8 @@ class Agent:
655
752
  try:
656
753
  # Resolve key (tool name/class) to Tool object, get ID
657
754
  tool = registry.resolve(key)
755
+ if not tool.id:
756
+ raise ValueError(f"Resolved tool has no ID: {key}")
658
757
  resolved[tool.id] = config
659
758
  except (ValueError, KeyError) as e:
660
759
  raise ValueError(f"Failed to resolve tool config key: {key}") from e
@@ -690,6 +789,8 @@ class Agent:
690
789
  resolved_id = key
691
790
  else:
692
791
  mcp = registry.resolve(key)
792
+ if not mcp.id:
793
+ raise ValueError(f"Resolved MCP has no ID: {key}")
693
794
  resolved_id = mcp.id
694
795
 
695
796
  if resolved_id in resolved:
@@ -705,7 +806,48 @@ class Agent:
705
806
 
706
807
  return resolved
707
808
 
708
- def _resolve_agents(self, registry: AgentRegistry) -> list:
809
+ def _normalize_mcp_configs(self, mcp_configs: dict[Any, Any]) -> dict[Any, Any]:
810
+ """Normalize mcp_configs by wrapping misplaced transport keys in 'config'.
811
+
812
+ This ensures that flat transport settings (e.g. {'url': '...'}) provided
813
+ by the user are correctly moved into the 'config' block required by the
814
+ Platform, ensuring parity between local and remote execution.
815
+
816
+ Args:
817
+ mcp_configs: The raw mcp_configs dictionary.
818
+
819
+ Returns:
820
+ Normalized mcp_configs dictionary.
821
+ """
822
+ from glaip_sdk.runner.langgraph import _MCP_TRANSPORT_KEYS # noqa: PLC0415
823
+
824
+ normalized = {}
825
+ for mcp_key, override in mcp_configs.items():
826
+ if not isinstance(override, dict):
827
+ normalized[mcp_key] = override
828
+ continue
829
+
830
+ misplaced = {k: v for k, v in override.items() if k in _MCP_TRANSPORT_KEYS}
831
+
832
+ if misplaced:
833
+ new_override = override.copy()
834
+ config_block = new_override.get("config", {})
835
+ if not isinstance(config_block, dict):
836
+ config_block = {}
837
+
838
+ config_block.update(misplaced)
839
+ new_override["config"] = config_block
840
+
841
+ for k in misplaced:
842
+ new_override.pop(k, None)
843
+
844
+ normalized[mcp_key] = new_override
845
+ else:
846
+ normalized[mcp_key] = override
847
+
848
+ return normalized
849
+
850
+ def _resolve_agents(self, registry: AgentRegistry) -> list[str]:
709
851
  """Resolve sub-agent references using AgentRegistry.
710
852
 
711
853
  Uses the global AgentRegistry to cache Agent objects across deployments.
@@ -717,12 +859,20 @@ class Agent:
717
859
 
718
860
  Returns:
719
861
  List of resolved agent IDs for the API payload.
862
+
863
+ Raises:
864
+ ValueError: If an agent fails to resolve to a valid ID.
720
865
  """
721
866
  if not self.agents:
722
867
  return []
723
868
 
724
- # Resolve each agent reference to a deployed Agent, extract ID
725
- return [registry.resolve(agent_ref).id for agent_ref in self.agents]
869
+ resolved_ids: list[str] = []
870
+ for agent_ref in self.agents:
871
+ agent = registry.resolve(agent_ref)
872
+ if not agent.id:
873
+ raise ValueError(f"Failed to resolve ID for agent: {agent_ref}")
874
+ resolved_ids.append(agent.id)
875
+ return resolved_ids
726
876
 
727
877
  def _create_or_update_agent(
728
878
  self,
@@ -798,6 +948,19 @@ class Agent:
798
948
 
799
949
  return content
800
950
 
951
+ def to_component(self) -> Any:
952
+ """Convert this Agent into a pipeline-compatible Component.
953
+
954
+ The returned AgentComponent wraps this agent instance and allows it
955
+ to be used within a Pipeline (from gllm-pipeline).
956
+
957
+ Returns:
958
+ An AgentComponent instance wrapping this agent.
959
+ """
960
+ from glaip_sdk.agents.component import AgentComponent # noqa: PLC0415
961
+
962
+ return AgentComponent(self)
963
+
801
964
  # =========================================================================
802
965
  # API Methods - Available after deploy()
803
966
  # =========================================================================
@@ -806,19 +969,42 @@ class Agent:
806
969
  """Return a dict representation of the Agent.
807
970
 
808
971
  Provides Pydantic-style serialization for backward compatibility.
972
+ This implementation avoids triggering external tool resolution to ensure
973
+ it remains robust even when the environment is not fully configured.
809
974
 
810
975
  Args:
811
976
  exclude_none: If True, exclude None values from the output.
812
977
 
813
978
  Returns:
814
- Dictionary containing Agent attributes.
979
+ Dictionary containing Agent attributes. Note: Mutable fields (dicts, lists)
980
+ are returned as references. Modify with caution or make a deep copy if needed.
815
981
  """
982
+ # Map convenience timeout to agent_config if not already present
983
+ agent_config = self.agent_config if self.agent_config is not self._UNSET else {}
984
+ agent_config = dict(agent_config) if agent_config else {}
985
+
986
+ if self.timeout and "execution_timeout" not in agent_config:
987
+ agent_config["execution_timeout"] = self.timeout
988
+
989
+ # Handle guardrail serialization without full config build
990
+ if self.guardrail:
991
+ try:
992
+ from glaip_sdk.guardrails.serializer import ( # noqa: PLC0415
993
+ serialize_guardrail_manager,
994
+ )
995
+
996
+ agent_config["guardrails"] = serialize_guardrail_manager(self.guardrail)
997
+ except ImportError: # pragma: no cover
998
+ # Serializer not available (optional dependency); skip guardrail data
999
+ pass
1000
+
816
1001
  data = {
817
1002
  "id": self._id,
818
1003
  "name": self.name,
819
1004
  "instruction": self.instruction,
820
1005
  "description": self.description,
821
- "type": self.agent_type,
1006
+ "agent_type": self.agent_type,
1007
+ "type": self.agent_type, # Legacy key for backward compatibility
822
1008
  "framework": self.framework,
823
1009
  "version": self.version,
824
1010
  "tools": self.tools,
@@ -826,13 +1012,16 @@ class Agent:
826
1012
  "mcps": self.mcps,
827
1013
  "timeout": self.timeout,
828
1014
  "metadata": self.metadata,
829
- "agent_config": self.agent_config,
1015
+ "model": self.model,
1016
+ "agent_config": agent_config,
830
1017
  "tool_configs": self.tool_configs,
831
1018
  "mcp_configs": self.mcp_configs,
832
1019
  "a2a_profile": self.a2a_profile,
1020
+ "guardrail": self.guardrail,
833
1021
  "created_at": self._created_at,
834
1022
  "updated_at": self._updated_at,
835
1023
  }
1024
+
836
1025
  if exclude_none:
837
1026
  return {k: v for k, v in data.items() if v is not None}
838
1027
  return data
@@ -849,6 +1038,36 @@ class Agent:
849
1038
  self._client = client
850
1039
  return self
851
1040
 
1041
+ @property
1042
+ def schedule(self) -> AgentScheduleManager:
1043
+ """Get the schedule manager for this agent.
1044
+
1045
+ Provides a convenient interface for managing schedules scoped to this agent.
1046
+
1047
+ Returns:
1048
+ AgentScheduleManager for schedule operations
1049
+
1050
+ Raises:
1051
+ ValueError: If agent is not deployed
1052
+ RuntimeError: If agent is not bound to a client
1053
+
1054
+ Example:
1055
+ >>> agent = client.get_agent_by_id("agent-id")
1056
+ >>> schedules = agent.schedule.list()
1057
+ >>> new_schedule = agent.schedule.create(
1058
+ ... input="Daily task",
1059
+ ... schedule="0 9 * * 1-5"
1060
+ ... )
1061
+ """
1062
+ if not self.id:
1063
+ raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
1064
+ if not self._client:
1065
+ raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
1066
+
1067
+ from glaip_sdk.client.schedules import AgentScheduleManager # noqa: PLC0415
1068
+
1069
+ return AgentScheduleManager(self, self._client.schedules)
1070
+
852
1071
  def _prepare_run_kwargs(
853
1072
  self,
854
1073
  message: str,
@@ -885,6 +1104,10 @@ class Agent:
885
1104
  }
886
1105
 
887
1106
  if runtime_config is not None:
1107
+ from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
1108
+ normalize_runtime_config_keys,
1109
+ )
1110
+
888
1111
  call_kwargs["runtime_config"] = normalize_runtime_config_keys(
889
1112
  runtime_config,
890
1113
  tool_registry=get_tool_registry(),
@@ -893,6 +1116,10 @@ class Agent:
893
1116
  )
894
1117
 
895
1118
  call_kwargs.update(kwargs)
1119
+
1120
+ memory_user_id = call_kwargs.get("memory_user_id")
1121
+ if memory_user_id and not call_kwargs.get("user_id"):
1122
+ call_kwargs["user_id"] = memory_user_id
896
1123
  return agent_client, call_kwargs
897
1124
 
898
1125
  def _get_local_runner_or_raise(self) -> Any:
@@ -904,9 +1131,24 @@ class Agent:
904
1131
  Raises:
905
1132
  ValueError: If local runtime is not available.
906
1133
  """
1134
+ from glaip_sdk.runner import get_default_runner # noqa: PLC0415
1135
+ from glaip_sdk.runner.deps import ( # noqa: PLC0415
1136
+ check_local_runtime_available,
1137
+ get_local_runtime_missing_message,
1138
+ )
1139
+
907
1140
  if check_local_runtime_available():
908
1141
  return get_default_runner()
909
- raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
1142
+
1143
+ # If agent is not deployed, it *must* use local runtime
1144
+ if not self.id:
1145
+ raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
1146
+
1147
+ # If agent IS deployed but local execution was forced (local=True)
1148
+ raise ValueError(
1149
+ f"Local execution override was requested, but local runtime is missing.\n\n"
1150
+ f"{get_local_runtime_missing_message()}"
1151
+ )
910
1152
 
911
1153
  def _prepare_local_runner_kwargs(
912
1154
  self,
@@ -941,6 +1183,7 @@ class Agent:
941
1183
  self,
942
1184
  message: str,
943
1185
  verbose: bool = False,
1186
+ local: bool = False,
944
1187
  runtime_config: dict[str, Any] | None = None,
945
1188
  chat_history: list[dict[str, str]] | None = None,
946
1189
  **kwargs: Any,
@@ -953,9 +1196,13 @@ class Agent:
953
1196
  - **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
954
1197
  execution happens locally via aip-agents (no server required).
955
1198
 
1199
+ You can force local execution for a deployed agent by passing `local=True`.
1200
+
956
1201
  Args:
957
1202
  message: The message to send to the agent.
958
1203
  verbose: If True, print streaming output to console. Defaults to False.
1204
+ local: If True, force local execution even if the agent is deployed.
1205
+ Defaults to False.
959
1206
  runtime_config: Optional runtime configuration for tools, MCPs, and agents.
960
1207
  Keys can be SDK objects, UUIDs, or names. Example:
961
1208
  {
@@ -977,16 +1224,19 @@ class Agent:
977
1224
  RuntimeError: If server-backed execution fails due to client issues.
978
1225
  """
979
1226
  # Backend routing: deployed agents use server, undeployed use local (if available)
980
- if self.id:
1227
+ if self.id and not local:
981
1228
  # Server-backed execution path (agent is deployed)
982
1229
  agent_client, call_kwargs = self._prepare_run_kwargs(
983
- message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
1230
+ message,
1231
+ verbose,
1232
+ runtime_config or kwargs.get("runtime_config"),
1233
+ **kwargs,
984
1234
  )
985
1235
  if chat_history is not None:
986
1236
  call_kwargs["chat_history"] = chat_history
987
1237
  return agent_client.run_agent(**call_kwargs)
988
1238
 
989
- # Local execution path (agent is not deployed)
1239
+ # Local execution path (agent is not deployed OR local=True)
990
1240
  runner = self._get_local_runner_or_raise()
991
1241
  local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
992
1242
  return runner.run(**local_kwargs)
@@ -995,6 +1245,7 @@ class Agent:
995
1245
  self,
996
1246
  message: str,
997
1247
  verbose: bool = False,
1248
+ local: bool = False,
998
1249
  runtime_config: dict[str, Any] | None = None,
999
1250
  chat_history: list[dict[str, str]] | None = None,
1000
1251
  **kwargs: Any,
@@ -1007,9 +1258,13 @@ class Agent:
1007
1258
  - **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
1008
1259
  execution happens locally via aip-agents (no server required).
1009
1260
 
1261
+ You can force local execution for a deployed agent by passing `local=True`.
1262
+
1010
1263
  Args:
1011
1264
  message: The message to send to the agent.
1012
1265
  verbose: If True, print streaming output to console. Defaults to False.
1266
+ local: If True, force local execution even if the agent is deployed.
1267
+ Defaults to False.
1013
1268
  runtime_config: Optional runtime configuration for tools, MCPs, and agents.
1014
1269
  Keys can be SDK objects, UUIDs, or names. Example:
1015
1270
  {
@@ -1031,10 +1286,13 @@ class Agent:
1031
1286
  RuntimeError: If server-backed execution fails due to client issues.
1032
1287
  """
1033
1288
  # Backend routing: deployed agents use server, undeployed use local (if available)
1034
- if self.id:
1289
+ if self.id and not local:
1035
1290
  # Server-backed execution path (agent is deployed)
1036
1291
  agent_client, call_kwargs = self._prepare_run_kwargs(
1037
- message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
1292
+ message,
1293
+ verbose,
1294
+ runtime_config or kwargs.get("runtime_config"),
1295
+ **kwargs,
1038
1296
  )
1039
1297
  if chat_history is not None:
1040
1298
  call_kwargs["chat_history"] = chat_history
@@ -1043,7 +1301,7 @@ class Agent:
1043
1301
  yield chunk
1044
1302
  return
1045
1303
 
1046
- # Local execution path (agent is not deployed)
1304
+ # Local execution path (agent is not deployed OR local=True)
1047
1305
  runner = self._get_local_runner_or_raise()
1048
1306
  local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
1049
1307
  result = await runner.arun(**local_kwargs)