glaip-sdk 0.6.5b3__py3-none-any.whl → 0.7.17__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 (145) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. glaip_sdk/agents/base.py +362 -39
  3. glaip_sdk/branding.py +113 -2
  4. glaip_sdk/cli/account_store.py +15 -0
  5. glaip_sdk/cli/auth.py +14 -8
  6. glaip_sdk/cli/commands/accounts.py +1 -1
  7. glaip_sdk/cli/commands/agents/__init__.py +116 -0
  8. glaip_sdk/cli/commands/agents/_common.py +562 -0
  9. glaip_sdk/cli/commands/agents/create.py +155 -0
  10. glaip_sdk/cli/commands/agents/delete.py +64 -0
  11. glaip_sdk/cli/commands/agents/get.py +89 -0
  12. glaip_sdk/cli/commands/agents/list.py +129 -0
  13. glaip_sdk/cli/commands/agents/run.py +264 -0
  14. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  15. glaip_sdk/cli/commands/agents/update.py +112 -0
  16. glaip_sdk/cli/commands/common_config.py +15 -12
  17. glaip_sdk/cli/commands/configure.py +2 -3
  18. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  19. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  20. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  21. glaip_sdk/cli/commands/mcps/create.py +152 -0
  22. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  23. glaip_sdk/cli/commands/mcps/get.py +212 -0
  24. glaip_sdk/cli/commands/mcps/list.py +69 -0
  25. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  26. glaip_sdk/cli/commands/mcps/update.py +190 -0
  27. glaip_sdk/cli/commands/models.py +2 -4
  28. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  29. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  30. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  31. glaip_sdk/cli/commands/tools/_common.py +80 -0
  32. glaip_sdk/cli/commands/tools/create.py +228 -0
  33. glaip_sdk/cli/commands/tools/delete.py +61 -0
  34. glaip_sdk/cli/commands/tools/get.py +103 -0
  35. glaip_sdk/cli/commands/tools/list.py +69 -0
  36. glaip_sdk/cli/commands/tools/script.py +49 -0
  37. glaip_sdk/cli/commands/tools/update.py +102 -0
  38. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  39. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  40. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  41. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  42. glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
  43. glaip_sdk/cli/commands/update.py +163 -17
  44. glaip_sdk/cli/config.py +1 -0
  45. glaip_sdk/cli/core/output.py +12 -7
  46. glaip_sdk/cli/entrypoint.py +20 -0
  47. glaip_sdk/cli/main.py +127 -39
  48. glaip_sdk/cli/pager.py +3 -3
  49. glaip_sdk/cli/resolution.py +2 -1
  50. glaip_sdk/cli/slash/accounts_controller.py +112 -32
  51. glaip_sdk/cli/slash/agent_session.py +5 -2
  52. glaip_sdk/cli/slash/prompt.py +11 -0
  53. glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
  54. glaip_sdk/cli/slash/session.py +375 -25
  55. glaip_sdk/cli/slash/tui/__init__.py +28 -1
  56. glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
  57. glaip_sdk/cli/slash/tui/accounts_app.py +1107 -126
  58. glaip_sdk/cli/slash/tui/clipboard.py +195 -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 +152 -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 +5 -3
  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 +57 -26
  90. glaip_sdk/config/constants.py +22 -2
  91. glaip_sdk/guardrails/__init__.py +80 -0
  92. glaip_sdk/guardrails/serializer.py +89 -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/registry/tool.py +273 -66
  109. glaip_sdk/runner/__init__.py +76 -0
  110. glaip_sdk/runner/base.py +84 -0
  111. glaip_sdk/runner/deps.py +115 -0
  112. glaip_sdk/runner/langgraph.py +1055 -0
  113. glaip_sdk/runner/logging_config.py +77 -0
  114. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  115. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  116. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  117. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +116 -0
  118. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  119. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  120. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
  121. glaip_sdk/schedules/__init__.py +22 -0
  122. glaip_sdk/schedules/base.py +291 -0
  123. glaip_sdk/tools/base.py +67 -14
  124. glaip_sdk/utils/__init__.py +1 -0
  125. glaip_sdk/utils/a2a/__init__.py +34 -0
  126. glaip_sdk/utils/a2a/event_processor.py +188 -0
  127. glaip_sdk/utils/agent_config.py +8 -2
  128. glaip_sdk/utils/bundler.py +138 -2
  129. glaip_sdk/utils/import_resolver.py +43 -11
  130. glaip_sdk/utils/rendering/renderer/base.py +58 -0
  131. glaip_sdk/utils/runtime_config.py +120 -0
  132. glaip_sdk/utils/sync.py +31 -11
  133. glaip_sdk/utils/tool_detection.py +301 -0
  134. glaip_sdk/utils/tool_storage_provider.py +140 -0
  135. {glaip_sdk-0.6.5b3.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +49 -38
  136. glaip_sdk-0.7.17.dist-info/RECORD +224 -0
  137. {glaip_sdk-0.6.5b3.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
  138. glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
  139. glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
  140. glaip_sdk/cli/commands/agents.py +0 -1509
  141. glaip_sdk/cli/commands/mcps.py +0 -1356
  142. glaip_sdk/cli/commands/tools.py +0 -576
  143. glaip_sdk/cli/utils.py +0 -263
  144. glaip_sdk-0.6.5b3.dist-info/RECORD +0 -145
  145. glaip_sdk-0.6.5b3.dist-info/entry_points.txt +0 -3
glaip_sdk/agents/base.py CHANGED
@@ -50,14 +50,17 @@ 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.utils.discovery import find_agent
54
53
  from glaip_sdk.utils.resource_refs import is_uuid
55
- from glaip_sdk.utils.runtime_config import normalize_runtime_config_keys
56
54
 
57
55
  if TYPE_CHECKING:
58
- from glaip_sdk.models import AgentResponse
56
+ from glaip_sdk.client.schedules import AgentScheduleManager
57
+ from glaip_sdk.models import AgentResponse, Model
58
+ from glaip_sdk.guardrails import GuardrailManager
59
59
  from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
60
60
 
61
+ # Import model validation utility
62
+ from glaip_sdk.models._validation import _validate_model
63
+
61
64
  logger = logging.getLogger(__name__)
62
65
 
63
66
  _AGENT_NOT_DEPLOYED_MSG = "Agent must be deployed before running. Call deploy() first."
@@ -99,11 +102,11 @@ class Agent:
99
102
  - instruction: Agent instruction text (required)
100
103
  - description: Agent description (default: "")
101
104
  - tools: List of tools (default: [])
105
+ - model: Optional model override (default: None)
102
106
  - agents: List of sub-agents (default: [])
103
107
  - mcps: List of MCPs (default: [])
104
108
  - timeout: Timeout in seconds (default: 300)
105
109
  - metadata: Optional metadata dict (default: None)
106
- - model: Optional model override (default: None)
107
110
  - framework: Agent framework (default: "langchain")
108
111
  - version: Agent version (default: "1.0.0")
109
112
  - agent_type: Agent type (default: "config")
@@ -130,7 +133,8 @@ class Agent:
130
133
  tools: list | None = None,
131
134
  agents: list | None = None,
132
135
  mcps: list | None = None,
133
- model: str | None = _UNSET, # type: ignore[assignment]
136
+ model: str | Model | None = _UNSET, # type: ignore[assignment]
137
+ guardrail: GuardrailManager | None = None,
134
138
  _client: Any = None,
135
139
  **kwargs: Any,
136
140
  ) -> None:
@@ -147,8 +151,10 @@ class Agent:
147
151
  tools: List of tools (Tool classes, SDK Tool objects, or strings).
148
152
  agents: List of sub-agents (Agent classes, instances, or strings).
149
153
  mcps: List of MCPs.
150
- model: Model identifier.
154
+ model: Model identifier or Model configuration object.
155
+ guardrail: The guardrail manager for content safety.
151
156
  _client: Internal client reference (set automatically).
157
+
152
158
  **kwargs: Additional configuration parameters:
153
159
  - timeout: Execution timeout in seconds.
154
160
  - metadata: Optional metadata dictionary.
@@ -173,7 +179,8 @@ class Agent:
173
179
  self._tools = tools
174
180
  self._agents = agents
175
181
  self._mcps = mcps
176
- self._model = model
182
+ self._model = self._validate_and_set_model(model)
183
+ self._guardrail = guardrail
177
184
  self._language_model_id: str | None = None
178
185
  # Extract parameters from kwargs with _UNSET defaults
179
186
  self._timeout = kwargs.pop("timeout", Agent._UNSET) # type: ignore[assignment]
@@ -181,9 +188,25 @@ class Agent:
181
188
  self._framework = kwargs.pop("framework", Agent._UNSET) # type: ignore[assignment]
182
189
  self._version = kwargs.pop("version", Agent._UNSET) # type: ignore[assignment]
183
190
  self._agent_type = kwargs.pop("agent_type", Agent._UNSET) # type: ignore[assignment]
191
+
192
+ # Handle 'type' as a legacy alias for 'agent_type'
193
+ legacy_type = kwargs.pop("type", Agent._UNSET)
194
+ if legacy_type is not Agent._UNSET:
195
+ warnings.warn(
196
+ "The 'type' parameter is deprecated and will be removed in a future version. Use 'agent_type' instead.",
197
+ DeprecationWarning,
198
+ stacklevel=2,
199
+ )
200
+ if self._agent_type is Agent._UNSET:
201
+ self._agent_type = legacy_type
202
+
184
203
  self._agent_config = kwargs.pop("agent_config", Agent._UNSET) # type: ignore[assignment]
185
204
  self._tool_configs = kwargs.pop("tool_configs", Agent._UNSET) # type: ignore[assignment]
186
- self._mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET) # type: ignore[assignment]
205
+ mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET)
206
+ if mcp_configs is not Agent._UNSET and isinstance(mcp_configs, dict):
207
+ self._mcp_configs = self._normalize_mcp_configs(mcp_configs)
208
+ else:
209
+ self._mcp_configs = mcp_configs # type: ignore[assignment]
187
210
  self._a2a_profile = kwargs.pop("a2a_profile", Agent._UNSET) # type: ignore[assignment]
188
211
 
189
212
  # Warn about unexpected kwargs
@@ -194,6 +217,30 @@ class Agent:
194
217
  stacklevel=2,
195
218
  )
196
219
 
220
+ def _validate_and_set_model(self, model: str | Any) -> str | Any:
221
+ """Validate and normalize model parameter.
222
+
223
+ Supports both string model identifiers and Model objects:
224
+ - String: Simple model identifier (e.g., "openai/gpt-4o" or OpenAI.GPT_4O)
225
+ - Model: Model object with credentials/hyperparameters for local execution
226
+
227
+ Args:
228
+ model: Model identifier (string) or Model object.
229
+
230
+ Returns:
231
+ Validated model (string or Model object).
232
+ """
233
+ if model is None or model is Agent._UNSET:
234
+ return model
235
+
236
+ from glaip_sdk.models import Model # noqa: PLC0415
237
+
238
+ if isinstance(model, str):
239
+ return _validate_model(model)
240
+ elif isinstance(model, Model):
241
+ return model
242
+ return model
243
+
197
244
  # ─────────────────────────────────────────────────────────────────
198
245
  # Properties (override in subclasses OR pass to __init__)
199
246
  # ─────────────────────────────────────────────────────────────────
@@ -338,11 +385,11 @@ class Agent:
338
385
  return None
339
386
 
340
387
  @property
341
- def model(self) -> str | None:
388
+ def model(self) -> str | Model | None:
342
389
  """Optional model override.
343
390
 
344
391
  Returns:
345
- Model identifier string or None to use default.
392
+ Model identifier string, Model object, or None to use default.
346
393
  """
347
394
  if self._model is not self._UNSET:
348
395
  return self._model
@@ -453,6 +500,11 @@ class Agent:
453
500
  return self._mcp_configs
454
501
  return None
455
502
 
503
+ @property
504
+ def guardrail(self) -> GuardrailManager | None:
505
+ """The guardrail manager for content safety."""
506
+ return self._guardrail
507
+
456
508
  @property
457
509
  def a2a_profile(self) -> dict[str, Any] | None:
458
510
  """A2A (Agent-to-Agent) profile configuration.
@@ -513,6 +565,8 @@ class Agent:
513
565
  from glaip_sdk.utils.client import get_client # noqa: PLC0415
514
566
 
515
567
  client = get_client()
568
+ from glaip_sdk.utils.discovery import find_agent # noqa: PLC0415
569
+
516
570
  response = self._create_or_update_agent(config, client, find_agent)
517
571
 
518
572
  # Update self with deployed info
@@ -538,19 +592,33 @@ class Agent:
538
592
  "framework": self.framework,
539
593
  "version": self.version,
540
594
  "agent_type": self.agent_type,
541
- "model": self.model,
542
595
  }
543
596
 
597
+ if self.model:
598
+ if isinstance(self.model, str):
599
+ config["model"] = self.model
600
+ else:
601
+ config["model"] = self.model.id
602
+
544
603
  # Handle metadata (default to empty dict if None)
545
604
  config["metadata"] = self.metadata or {}
546
605
 
547
606
  # Handle agent_config with timeout
548
607
  # The timeout property is a convenience that maps to agent_config.execution_timeout
549
- agent_config = dict(self.agent_config) if self.agent_config else {}
608
+ raw_config = self.agent_config if self.agent_config is not self._UNSET else {}
609
+ agent_config = dict(raw_config) if raw_config else {}
610
+
550
611
  if self.timeout and "execution_timeout" not in agent_config:
551
612
  agent_config["execution_timeout"] = self.timeout
552
- if agent_config:
553
- config["agent_config"] = agent_config
613
+
614
+ if self.guardrail:
615
+ from glaip_sdk.guardrails.serializer import ( # noqa: PLC0415
616
+ serialize_guardrail_manager,
617
+ )
618
+
619
+ agent_config["guardrails"] = serialize_guardrail_manager(self.guardrail)
620
+
621
+ config["agent_config"] = agent_config
554
622
 
555
623
  # Handle tool_configs - resolve tool names/classes to IDs
556
624
  if self.tool_configs:
@@ -582,11 +650,20 @@ class Agent:
582
650
 
583
651
  Returns:
584
652
  List of resolved MCP IDs for the API payload.
653
+
654
+ Raises:
655
+ ValueError: If an MCP fails to resolve to a valid ID.
585
656
  """
586
657
  if not self.mcps:
587
658
  return []
588
659
 
589
- return [registry.resolve(mcp_ref).id for mcp_ref in self.mcps]
660
+ resolved_ids: list[str] = []
661
+ for mcp_ref in self.mcps:
662
+ mcp = registry.resolve(mcp_ref)
663
+ if not mcp.id:
664
+ raise ValueError(f"Failed to resolve ID for MCP: {mcp_ref}")
665
+ resolved_ids.append(mcp.id)
666
+ return resolved_ids
590
667
 
591
668
  def _resolve_tools(self, registry: ToolRegistry) -> list[str]:
592
669
  """Resolve tool references to IDs using ToolRegistry.
@@ -600,12 +677,20 @@ class Agent:
600
677
 
601
678
  Returns:
602
679
  List of resolved tool IDs for the API payload.
680
+
681
+ Raises:
682
+ ValueError: If a tool fails to resolve to a valid ID.
603
683
  """
604
684
  if not self.tools:
605
685
  return []
606
686
 
607
- # Resolve each tool reference to a Tool object, extract ID
608
- return [registry.resolve(tool_ref).id for tool_ref in self.tools]
687
+ resolved_ids: list[str] = []
688
+ for tool_ref in self.tools:
689
+ tool = registry.resolve(tool_ref)
690
+ if not tool.id:
691
+ raise ValueError(f"Failed to resolve ID for tool: {tool_ref}")
692
+ resolved_ids.append(tool.id)
693
+ return resolved_ids
609
694
 
610
695
  def _resolve_tool_configs(self, registry: ToolRegistry) -> dict[str, Any]:
611
696
  """Resolve tool_configs keys from tool names/classes to tool IDs.
@@ -648,6 +733,8 @@ class Agent:
648
733
  try:
649
734
  # Resolve key (tool name/class) to Tool object, get ID
650
735
  tool = registry.resolve(key)
736
+ if not tool.id:
737
+ raise ValueError(f"Resolved tool has no ID: {key}")
651
738
  resolved[tool.id] = config
652
739
  except (ValueError, KeyError) as e:
653
740
  raise ValueError(f"Failed to resolve tool config key: {key}") from e
@@ -683,6 +770,8 @@ class Agent:
683
770
  resolved_id = key
684
771
  else:
685
772
  mcp = registry.resolve(key)
773
+ if not mcp.id:
774
+ raise ValueError(f"Resolved MCP has no ID: {key}")
686
775
  resolved_id = mcp.id
687
776
 
688
777
  if resolved_id in resolved:
@@ -698,7 +787,48 @@ class Agent:
698
787
 
699
788
  return resolved
700
789
 
701
- def _resolve_agents(self, registry: AgentRegistry) -> list:
790
+ def _normalize_mcp_configs(self, mcp_configs: dict[Any, Any]) -> dict[Any, Any]:
791
+ """Normalize mcp_configs by wrapping misplaced transport keys in 'config'.
792
+
793
+ This ensures that flat transport settings (e.g. {'url': '...'}) provided
794
+ by the user are correctly moved into the 'config' block required by the
795
+ Platform, ensuring parity between local and remote execution.
796
+
797
+ Args:
798
+ mcp_configs: The raw mcp_configs dictionary.
799
+
800
+ Returns:
801
+ Normalized mcp_configs dictionary.
802
+ """
803
+ from glaip_sdk.runner.langgraph import _MCP_TRANSPORT_KEYS # noqa: PLC0415
804
+
805
+ normalized = {}
806
+ for mcp_key, override in mcp_configs.items():
807
+ if not isinstance(override, dict):
808
+ normalized[mcp_key] = override
809
+ continue
810
+
811
+ misplaced = {k: v for k, v in override.items() if k in _MCP_TRANSPORT_KEYS}
812
+
813
+ if misplaced:
814
+ new_override = override.copy()
815
+ config_block = new_override.get("config", {})
816
+ if not isinstance(config_block, dict):
817
+ config_block = {}
818
+
819
+ config_block.update(misplaced)
820
+ new_override["config"] = config_block
821
+
822
+ for k in misplaced:
823
+ new_override.pop(k, None)
824
+
825
+ normalized[mcp_key] = new_override
826
+ else:
827
+ normalized[mcp_key] = override
828
+
829
+ return normalized
830
+
831
+ def _resolve_agents(self, registry: AgentRegistry) -> list[str]:
702
832
  """Resolve sub-agent references using AgentRegistry.
703
833
 
704
834
  Uses the global AgentRegistry to cache Agent objects across deployments.
@@ -710,12 +840,20 @@ class Agent:
710
840
 
711
841
  Returns:
712
842
  List of resolved agent IDs for the API payload.
843
+
844
+ Raises:
845
+ ValueError: If an agent fails to resolve to a valid ID.
713
846
  """
714
847
  if not self.agents:
715
848
  return []
716
849
 
717
- # Resolve each agent reference to a deployed Agent, extract ID
718
- return [registry.resolve(agent_ref).id for agent_ref in self.agents]
850
+ resolved_ids: list[str] = []
851
+ for agent_ref in self.agents:
852
+ agent = registry.resolve(agent_ref)
853
+ if not agent.id:
854
+ raise ValueError(f"Failed to resolve ID for agent: {agent_ref}")
855
+ resolved_ids.append(agent.id)
856
+ return resolved_ids
719
857
 
720
858
  def _create_or_update_agent(
721
859
  self,
@@ -799,19 +937,42 @@ class Agent:
799
937
  """Return a dict representation of the Agent.
800
938
 
801
939
  Provides Pydantic-style serialization for backward compatibility.
940
+ This implementation avoids triggering external tool resolution to ensure
941
+ it remains robust even when the environment is not fully configured.
802
942
 
803
943
  Args:
804
944
  exclude_none: If True, exclude None values from the output.
805
945
 
806
946
  Returns:
807
- Dictionary containing Agent attributes.
947
+ Dictionary containing Agent attributes. Note: Mutable fields (dicts, lists)
948
+ are returned as references. Modify with caution or make a deep copy if needed.
808
949
  """
950
+ # Map convenience timeout to agent_config if not already present
951
+ agent_config = self.agent_config if self.agent_config is not self._UNSET else {}
952
+ agent_config = dict(agent_config) if agent_config else {}
953
+
954
+ if self.timeout and "execution_timeout" not in agent_config:
955
+ agent_config["execution_timeout"] = self.timeout
956
+
957
+ # Handle guardrail serialization without full config build
958
+ if self.guardrail:
959
+ try:
960
+ from glaip_sdk.guardrails.serializer import ( # noqa: PLC0415
961
+ serialize_guardrail_manager,
962
+ )
963
+
964
+ agent_config["guardrails"] = serialize_guardrail_manager(self.guardrail)
965
+ except ImportError: # pragma: no cover
966
+ # Serializer not available (optional dependency); skip guardrail data
967
+ pass
968
+
809
969
  data = {
810
970
  "id": self._id,
811
971
  "name": self.name,
812
972
  "instruction": self.instruction,
813
973
  "description": self.description,
814
- "type": self.agent_type,
974
+ "agent_type": self.agent_type,
975
+ "type": self.agent_type, # Legacy key for backward compatibility
815
976
  "framework": self.framework,
816
977
  "version": self.version,
817
978
  "tools": self.tools,
@@ -819,13 +980,16 @@ class Agent:
819
980
  "mcps": self.mcps,
820
981
  "timeout": self.timeout,
821
982
  "metadata": self.metadata,
822
- "agent_config": self.agent_config,
983
+ "model": self.model,
984
+ "agent_config": agent_config,
823
985
  "tool_configs": self.tool_configs,
824
986
  "mcp_configs": self.mcp_configs,
825
987
  "a2a_profile": self.a2a_profile,
988
+ "guardrail": self.guardrail,
826
989
  "created_at": self._created_at,
827
990
  "updated_at": self._updated_at,
828
991
  }
992
+
829
993
  if exclude_none:
830
994
  return {k: v for k, v in data.items() if v is not None}
831
995
  return data
@@ -842,6 +1006,36 @@ class Agent:
842
1006
  self._client = client
843
1007
  return self
844
1008
 
1009
+ @property
1010
+ def schedule(self) -> AgentScheduleManager:
1011
+ """Get the schedule manager for this agent.
1012
+
1013
+ Provides a convenient interface for managing schedules scoped to this agent.
1014
+
1015
+ Returns:
1016
+ AgentScheduleManager for schedule operations
1017
+
1018
+ Raises:
1019
+ ValueError: If agent is not deployed
1020
+ RuntimeError: If agent is not bound to a client
1021
+
1022
+ Example:
1023
+ >>> agent = client.get_agent_by_id("agent-id")
1024
+ >>> schedules = agent.schedule.list()
1025
+ >>> new_schedule = agent.schedule.create(
1026
+ ... input="Daily task",
1027
+ ... schedule="0 9 * * 1-5"
1028
+ ... )
1029
+ """
1030
+ if not self.id:
1031
+ raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
1032
+ if not self._client:
1033
+ raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
1034
+
1035
+ from glaip_sdk.client.schedules import AgentScheduleManager # noqa: PLC0415
1036
+
1037
+ return AgentScheduleManager(self, self._client.schedules)
1038
+
845
1039
  def _prepare_run_kwargs(
846
1040
  self,
847
1041
  message: str,
@@ -864,7 +1058,7 @@ class Agent:
864
1058
  ValueError: If the agent hasn't been deployed yet.
865
1059
  RuntimeError: If client is not available.
866
1060
  """
867
- if not self.id:
1061
+ if not self.id: # pragma: no cover - defensive: called only when self.id is truthy
868
1062
  raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
869
1063
  if not self._client:
870
1064
  raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
@@ -878,6 +1072,10 @@ class Agent:
878
1072
  }
879
1073
 
880
1074
  if runtime_config is not None:
1075
+ from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
1076
+ normalize_runtime_config_keys,
1077
+ )
1078
+
881
1079
  call_kwargs["runtime_config"] = normalize_runtime_config_keys(
882
1080
  runtime_config,
883
1081
  tool_registry=get_tool_registry(),
@@ -888,18 +1086,87 @@ class Agent:
888
1086
  call_kwargs.update(kwargs)
889
1087
  return agent_client, call_kwargs
890
1088
 
1089
+ def _get_local_runner_or_raise(self) -> Any:
1090
+ """Get the local runner if available, otherwise raise ValueError.
1091
+
1092
+ Returns:
1093
+ The default local runner instance.
1094
+
1095
+ Raises:
1096
+ ValueError: If local runtime is not available.
1097
+ """
1098
+ from glaip_sdk.runner import get_default_runner # noqa: PLC0415
1099
+ from glaip_sdk.runner.deps import ( # noqa: PLC0415
1100
+ check_local_runtime_available,
1101
+ get_local_runtime_missing_message,
1102
+ )
1103
+
1104
+ if check_local_runtime_available():
1105
+ return get_default_runner()
1106
+
1107
+ # If agent is not deployed, it *must* use local runtime
1108
+ if not self.id:
1109
+ raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
1110
+
1111
+ # If agent IS deployed but local execution was forced (local=True)
1112
+ raise ValueError(
1113
+ f"Local execution override was requested, but local runtime is missing.\n\n"
1114
+ f"{get_local_runtime_missing_message()}"
1115
+ )
1116
+
1117
+ def _prepare_local_runner_kwargs(
1118
+ self,
1119
+ message: str,
1120
+ verbose: bool,
1121
+ runtime_config: dict[str, Any] | None,
1122
+ chat_history: list[dict[str, str]] | None,
1123
+ **kwargs: Any,
1124
+ ) -> dict[str, Any]:
1125
+ """Prepare kwargs for local runner execution.
1126
+
1127
+ Args:
1128
+ message: The message to send to the agent.
1129
+ verbose: If True, print streaming output to console.
1130
+ runtime_config: Optional runtime configuration.
1131
+ chat_history: Optional list of prior conversation messages.
1132
+ **kwargs: Additional arguments.
1133
+
1134
+ Returns:
1135
+ Dictionary of prepared kwargs for runner.run() or runner.arun().
1136
+ """
1137
+ return {
1138
+ "agent": self,
1139
+ "message": message,
1140
+ "verbose": verbose,
1141
+ "runtime_config": runtime_config,
1142
+ "chat_history": chat_history,
1143
+ **kwargs,
1144
+ }
1145
+
891
1146
  def run(
892
1147
  self,
893
1148
  message: str,
894
1149
  verbose: bool = False,
1150
+ local: bool = False,
895
1151
  runtime_config: dict[str, Any] | None = None,
1152
+ chat_history: list[dict[str, str]] | None = None,
896
1153
  **kwargs: Any,
897
1154
  ) -> str:
898
1155
  """Run the agent synchronously with a message.
899
1156
 
1157
+ Supports two execution modes:
1158
+ - **Server-backed**: When the agent is deployed (has an ID), execution
1159
+ happens via the AIP backend server.
1160
+ - **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
1161
+ execution happens locally via aip-agents (no server required).
1162
+
1163
+ You can force local execution for a deployed agent by passing `local=True`.
1164
+
900
1165
  Args:
901
1166
  message: The message to send to the agent.
902
- verbose: If True, print streaming output to console.
1167
+ verbose: If True, print streaming output to console. Defaults to False.
1168
+ local: If True, force local execution even if the agent is deployed.
1169
+ Defaults to False.
903
1170
  runtime_config: Optional runtime configuration for tools, MCPs, and agents.
904
1171
  Keys can be SDK objects, UUIDs, or names. Example:
905
1172
  {
@@ -907,32 +1174,61 @@ class Agent:
907
1174
  "mcp_configs": {"mcp-id": {"setting": "on"}},
908
1175
  "agent_config": {"planning": True},
909
1176
  }
1177
+ Defaults to None.
1178
+ chat_history: Optional list of prior conversation messages for context.
1179
+ Each message is a dict with "role" and "content" keys.
1180
+ Defaults to None.
910
1181
  **kwargs: Additional arguments to pass to the run API.
911
1182
 
912
1183
  Returns:
913
1184
  The agent's response as a string.
914
1185
 
915
1186
  Raises:
916
- ValueError: If the agent hasn't been deployed yet.
917
- RuntimeError: If client is not available.
1187
+ ValueError: If the agent is not deployed and local runtime is not available.
1188
+ RuntimeError: If server-backed execution fails due to client issues.
918
1189
  """
919
- agent_client, call_kwargs = self._prepare_run_kwargs(
920
- message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
921
- )
922
- return agent_client.run_agent(**call_kwargs)
1190
+ # Backend routing: deployed agents use server, undeployed use local (if available)
1191
+ if self.id and not local:
1192
+ # Server-backed execution path (agent is deployed)
1193
+ agent_client, call_kwargs = self._prepare_run_kwargs(
1194
+ message,
1195
+ verbose,
1196
+ runtime_config or kwargs.get("runtime_config"),
1197
+ **kwargs,
1198
+ )
1199
+ if chat_history is not None:
1200
+ call_kwargs["chat_history"] = chat_history
1201
+ return agent_client.run_agent(**call_kwargs)
1202
+
1203
+ # Local execution path (agent is not deployed OR local=True)
1204
+ runner = self._get_local_runner_or_raise()
1205
+ local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
1206
+ return runner.run(**local_kwargs)
923
1207
 
924
1208
  async def arun(
925
1209
  self,
926
1210
  message: str,
927
1211
  verbose: bool = False,
1212
+ local: bool = False,
928
1213
  runtime_config: dict[str, Any] | None = None,
1214
+ chat_history: list[dict[str, str]] | None = None,
929
1215
  **kwargs: Any,
930
1216
  ) -> AsyncGenerator[dict, None]:
931
1217
  """Run the agent asynchronously with streaming output.
932
1218
 
1219
+ Supports two execution modes:
1220
+ - **Server-backed**: When the agent is deployed (has an ID), execution
1221
+ happens via the AIP backend server with streaming.
1222
+ - **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
1223
+ execution happens locally via aip-agents (no server required).
1224
+
1225
+ You can force local execution for a deployed agent by passing `local=True`.
1226
+
933
1227
  Args:
934
1228
  message: The message to send to the agent.
935
- verbose: If True, print streaming output to console.
1229
+ verbose: If True, print streaming output to console. Defaults to False.
1230
+ local: If True, force local execution even if the agent is deployed.
1231
+ Defaults to False.
936
1232
  runtime_config: Optional runtime configuration for tools, MCPs, and agents.
937
1233
  Keys can be SDK objects, UUIDs, or names. Example:
938
1234
  {
@@ -940,20 +1236,47 @@ class Agent:
940
1236
  "mcp_configs": {"mcp-id": {"setting": "on"}},
941
1237
  "agent_config": {"planning": True},
942
1238
  }
1239
+ Defaults to None.
1240
+ chat_history: Optional list of prior conversation messages for context.
1241
+ Each message is a dict with "role" and "content" keys.
1242
+ Defaults to None.
943
1243
  **kwargs: Additional arguments to pass to the run API.
944
1244
 
945
1245
  Yields:
946
1246
  Streaming response chunks from the agent.
947
1247
 
948
1248
  Raises:
949
- ValueError: If the agent hasn't been deployed yet.
950
- RuntimeError: If client is not available.
1249
+ ValueError: If the agent is not deployed and local runtime is not available.
1250
+ RuntimeError: If server-backed execution fails due to client issues.
951
1251
  """
952
- agent_client, call_kwargs = self._prepare_run_kwargs(
953
- message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
954
- )
955
- async for chunk in agent_client.arun_agent(**call_kwargs):
956
- yield chunk
1252
+ # Backend routing: deployed agents use server, undeployed use local (if available)
1253
+ if self.id and not local:
1254
+ # Server-backed execution path (agent is deployed)
1255
+ agent_client, call_kwargs = self._prepare_run_kwargs(
1256
+ message,
1257
+ verbose,
1258
+ runtime_config or kwargs.get("runtime_config"),
1259
+ **kwargs,
1260
+ )
1261
+ if chat_history is not None:
1262
+ call_kwargs["chat_history"] = chat_history
1263
+
1264
+ async for chunk in agent_client.arun_agent(**call_kwargs):
1265
+ yield chunk
1266
+ return
1267
+
1268
+ # Local execution path (agent is not deployed OR local=True)
1269
+ runner = self._get_local_runner_or_raise()
1270
+ local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
1271
+ result = await runner.arun(**local_kwargs)
1272
+ # Yield a final_response event for consistency with server-backed execution
1273
+ # Include event_type for A2A event shape parity
1274
+ yield {
1275
+ "event_type": "final_response",
1276
+ "metadata": {"kind": "final_response"},
1277
+ "content": result,
1278
+ "is_final": True,
1279
+ }
957
1280
 
958
1281
  def update(self, **kwargs: Any) -> Agent:
959
1282
  """Update the deployed agent with new configuration.