glaip-sdk 0.6.19__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 (135) hide show
  1. glaip_sdk/agents/base.py +283 -30
  2. glaip_sdk/agents/component.py +233 -0
  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 +1 -1
  17. glaip_sdk/cli/commands/configure.py +1 -2
  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/entrypoint.py +20 -0
  46. glaip_sdk/cli/main.py +112 -35
  47. glaip_sdk/cli/pager.py +3 -3
  48. glaip_sdk/cli/resolution.py +2 -1
  49. glaip_sdk/cli/slash/accounts_controller.py +3 -1
  50. glaip_sdk/cli/slash/agent_session.py +1 -1
  51. glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
  52. glaip_sdk/cli/slash/session.py +343 -20
  53. glaip_sdk/cli/slash/tui/__init__.py +29 -1
  54. glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
  55. glaip_sdk/cli/slash/tui/accounts_app.py +1117 -126
  56. glaip_sdk/cli/slash/tui/clipboard.py +316 -0
  57. glaip_sdk/cli/slash/tui/context.py +92 -0
  58. glaip_sdk/cli/slash/tui/indicators.py +341 -0
  59. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  60. glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
  61. glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
  62. glaip_sdk/cli/slash/tui/loading.py +43 -21
  63. glaip_sdk/cli/slash/tui/remote_runs_app.py +178 -20
  64. glaip_sdk/cli/slash/tui/terminal.py +407 -0
  65. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  66. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  67. glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
  68. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  69. glaip_sdk/cli/slash/tui/toast.py +388 -0
  70. glaip_sdk/cli/transcript/history.py +1 -1
  71. glaip_sdk/cli/transcript/viewer.py +1 -1
  72. glaip_sdk/cli/tui_settings.py +125 -0
  73. glaip_sdk/cli/update_notifier.py +215 -7
  74. glaip_sdk/cli/validators.py +1 -1
  75. glaip_sdk/client/__init__.py +2 -1
  76. glaip_sdk/client/_schedule_payloads.py +89 -0
  77. glaip_sdk/client/agents.py +293 -17
  78. glaip_sdk/client/base.py +25 -0
  79. glaip_sdk/client/hitl.py +136 -0
  80. glaip_sdk/client/main.py +7 -5
  81. glaip_sdk/client/mcps.py +44 -13
  82. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  83. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
  84. glaip_sdk/client/payloads/agent/responses.py +43 -0
  85. glaip_sdk/client/run_rendering.py +109 -30
  86. glaip_sdk/client/schedules.py +439 -0
  87. glaip_sdk/client/tools.py +52 -23
  88. glaip_sdk/config/constants.py +22 -2
  89. glaip_sdk/guardrails/__init__.py +80 -0
  90. glaip_sdk/guardrails/serializer.py +91 -0
  91. glaip_sdk/hitl/__init__.py +35 -2
  92. glaip_sdk/hitl/base.py +64 -0
  93. glaip_sdk/hitl/callback.py +43 -0
  94. glaip_sdk/hitl/local.py +1 -31
  95. glaip_sdk/hitl/remote.py +523 -0
  96. glaip_sdk/models/__init__.py +47 -1
  97. glaip_sdk/models/_provider_mappings.py +101 -0
  98. glaip_sdk/models/_validation.py +97 -0
  99. glaip_sdk/models/agent.py +2 -1
  100. glaip_sdk/models/agent_runs.py +2 -1
  101. glaip_sdk/models/constants.py +141 -0
  102. glaip_sdk/models/model.py +170 -0
  103. glaip_sdk/models/schedule.py +224 -0
  104. glaip_sdk/payload_schemas/agent.py +1 -0
  105. glaip_sdk/payload_schemas/guardrails.py +34 -0
  106. glaip_sdk/ptc.py +145 -0
  107. glaip_sdk/registry/tool.py +270 -57
  108. glaip_sdk/runner/__init__.py +20 -3
  109. glaip_sdk/runner/deps.py +4 -1
  110. glaip_sdk/runner/langgraph.py +251 -27
  111. glaip_sdk/runner/logging_config.py +77 -0
  112. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +30 -9
  113. glaip_sdk/runner/ptc_adapter.py +98 -0
  114. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +25 -2
  115. glaip_sdk/schedules/__init__.py +22 -0
  116. glaip_sdk/schedules/base.py +291 -0
  117. glaip_sdk/tools/base.py +67 -14
  118. glaip_sdk/utils/__init__.py +1 -0
  119. glaip_sdk/utils/agent_config.py +8 -2
  120. glaip_sdk/utils/bundler.py +138 -2
  121. glaip_sdk/utils/import_resolver.py +427 -49
  122. glaip_sdk/utils/runtime_config.py +3 -2
  123. glaip_sdk/utils/sync.py +31 -11
  124. glaip_sdk/utils/tool_detection.py +274 -6
  125. {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/METADATA +22 -8
  126. glaip_sdk-0.7.27.dist-info/RECORD +227 -0
  127. {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/WHEEL +1 -1
  128. glaip_sdk-0.7.27.dist-info/entry_points.txt +2 -0
  129. glaip_sdk/cli/commands/agents.py +0 -1509
  130. glaip_sdk/cli/commands/mcps.py +0 -1356
  131. glaip_sdk/cli/commands/tools.py +0 -576
  132. glaip_sdk/cli/utils.py +0 -263
  133. glaip_sdk-0.6.19.dist-info/RECORD +0 -163
  134. glaip_sdk-0.6.19.dist-info/entry_points.txt +0 -2
  135. {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/top_level.txt +0 -0
glaip_sdk/agents/base.py CHANGED
@@ -46,8 +46,6 @@ 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
 
@@ -55,9 +53,15 @@ from glaip_sdk.registry import get_agent_registry, get_mcp_registry, get_tool_re
55
53
  from glaip_sdk.utils.resource_refs import is_uuid
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.guardrails import GuardrailManager
58
+ from glaip_sdk.models import AgentResponse, Model
59
+ from glaip_sdk.ptc import PTC
59
60
  from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
60
61
 
62
+ # Import model validation utility
63
+ from glaip_sdk.models._validation import _validate_model
64
+
61
65
  logger = logging.getLogger(__name__)
62
66
 
63
67
  _AGENT_NOT_DEPLOYED_MSG = "Agent must be deployed before running. Call deploy() first."
@@ -99,11 +103,11 @@ class Agent:
99
103
  - instruction: Agent instruction text (required)
100
104
  - description: Agent description (default: "")
101
105
  - tools: List of tools (default: [])
106
+ - model: Optional model override (default: None)
102
107
  - agents: List of sub-agents (default: [])
103
108
  - mcps: List of MCPs (default: [])
104
109
  - timeout: Timeout in seconds (default: 300)
105
110
  - metadata: Optional metadata dict (default: None)
106
- - model: Optional model override (default: None)
107
111
  - framework: Agent framework (default: "langchain")
108
112
  - version: Agent version (default: "1.0.0")
109
113
  - agent_type: Agent type (default: "config")
@@ -130,7 +134,9 @@ class Agent:
130
134
  tools: list | None = None,
131
135
  agents: list | None = None,
132
136
  mcps: list | None = None,
133
- 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,
134
140
  _client: Any = None,
135
141
  **kwargs: Any,
136
142
  ) -> None:
@@ -147,8 +153,11 @@ class Agent:
147
153
  tools: List of tools (Tool classes, SDK Tool objects, or strings).
148
154
  agents: List of sub-agents (Agent classes, instances, or strings).
149
155
  mcps: List of MCPs.
150
- 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).
151
159
  _client: Internal client reference (set automatically).
160
+
152
161
  **kwargs: Additional configuration parameters:
153
162
  - timeout: Execution timeout in seconds.
154
163
  - metadata: Optional metadata dictionary.
@@ -159,6 +168,7 @@ class Agent:
159
168
  - tool_configs: Per-tool configuration overrides.
160
169
  - mcp_configs: Per-MCP configuration overrides.
161
170
  - a2a_profile: A2A profile configuration.
171
+ - ptc: PTC configuration (local runs only).
162
172
  """
163
173
  # Instance attributes for deployed agents
164
174
  self._id = id
@@ -173,7 +183,9 @@ class Agent:
173
183
  self._tools = tools
174
184
  self._agents = agents
175
185
  self._mcps = mcps
176
- self._model = model
186
+ self._model = self._validate_and_set_model(model)
187
+ self._guardrail = guardrail
188
+ self._ptc = ptc
177
189
  self._language_model_id: str | None = None
178
190
  # Extract parameters from kwargs with _UNSET defaults
179
191
  self._timeout = kwargs.pop("timeout", Agent._UNSET) # type: ignore[assignment]
@@ -181,9 +193,25 @@ class Agent:
181
193
  self._framework = kwargs.pop("framework", Agent._UNSET) # type: ignore[assignment]
182
194
  self._version = kwargs.pop("version", Agent._UNSET) # type: ignore[assignment]
183
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
+
184
208
  self._agent_config = kwargs.pop("agent_config", Agent._UNSET) # type: ignore[assignment]
185
209
  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]
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]
187
215
  self._a2a_profile = kwargs.pop("a2a_profile", Agent._UNSET) # type: ignore[assignment]
188
216
 
189
217
  # Warn about unexpected kwargs
@@ -194,6 +222,30 @@ class Agent:
194
222
  stacklevel=2,
195
223
  )
196
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
+
197
249
  # ─────────────────────────────────────────────────────────────────
198
250
  # Properties (override in subclasses OR pass to __init__)
199
251
  # ─────────────────────────────────────────────────────────────────
@@ -338,11 +390,11 @@ class Agent:
338
390
  return None
339
391
 
340
392
  @property
341
- def model(self) -> str | None:
393
+ def model(self) -> str | Model | None:
342
394
  """Optional model override.
343
395
 
344
396
  Returns:
345
- Model identifier string or None to use default.
397
+ Model identifier string, Model object, or None to use default.
346
398
  """
347
399
  if self._model is not self._UNSET:
348
400
  return self._model
@@ -453,6 +505,16 @@ class Agent:
453
505
  return self._mcp_configs
454
506
  return None
455
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
+
456
518
  @property
457
519
  def a2a_profile(self) -> dict[str, Any] | None:
458
520
  """A2A (Agent-to-Agent) profile configuration.
@@ -499,6 +561,15 @@ class Agent:
499
561
  """
500
562
  logger.info("Deploying agent: %s", self.name)
501
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
+
502
573
  # Resolve tools FIRST - this uploads them and populates the registry
503
574
  tool_ids = self._resolve_tools(get_tool_registry())
504
575
 
@@ -540,19 +611,33 @@ class Agent:
540
611
  "framework": self.framework,
541
612
  "version": self.version,
542
613
  "agent_type": self.agent_type,
543
- "model": self.model,
544
614
  }
545
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
+
546
622
  # Handle metadata (default to empty dict if None)
547
623
  config["metadata"] = self.metadata or {}
548
624
 
549
625
  # Handle agent_config with timeout
550
626
  # The timeout property is a convenience that maps to agent_config.execution_timeout
551
- 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
+
552
630
  if self.timeout and "execution_timeout" not in agent_config:
553
631
  agent_config["execution_timeout"] = self.timeout
554
- if agent_config:
555
- 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
556
641
 
557
642
  # Handle tool_configs - resolve tool names/classes to IDs
558
643
  if self.tool_configs:
@@ -584,11 +669,20 @@ class Agent:
584
669
 
585
670
  Returns:
586
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.
587
675
  """
588
676
  if not self.mcps:
589
677
  return []
590
678
 
591
- 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
592
686
 
593
687
  def _resolve_tools(self, registry: ToolRegistry) -> list[str]:
594
688
  """Resolve tool references to IDs using ToolRegistry.
@@ -602,12 +696,20 @@ class Agent:
602
696
 
603
697
  Returns:
604
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.
605
702
  """
606
703
  if not self.tools:
607
704
  return []
608
705
 
609
- # Resolve each tool reference to a Tool object, extract ID
610
- 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
611
713
 
612
714
  def _resolve_tool_configs(self, registry: ToolRegistry) -> dict[str, Any]:
613
715
  """Resolve tool_configs keys from tool names/classes to tool IDs.
@@ -650,6 +752,8 @@ class Agent:
650
752
  try:
651
753
  # Resolve key (tool name/class) to Tool object, get ID
652
754
  tool = registry.resolve(key)
755
+ if not tool.id:
756
+ raise ValueError(f"Resolved tool has no ID: {key}")
653
757
  resolved[tool.id] = config
654
758
  except (ValueError, KeyError) as e:
655
759
  raise ValueError(f"Failed to resolve tool config key: {key}") from e
@@ -685,6 +789,8 @@ class Agent:
685
789
  resolved_id = key
686
790
  else:
687
791
  mcp = registry.resolve(key)
792
+ if not mcp.id:
793
+ raise ValueError(f"Resolved MCP has no ID: {key}")
688
794
  resolved_id = mcp.id
689
795
 
690
796
  if resolved_id in resolved:
@@ -700,7 +806,48 @@ class Agent:
700
806
 
701
807
  return resolved
702
808
 
703
- 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]:
704
851
  """Resolve sub-agent references using AgentRegistry.
705
852
 
706
853
  Uses the global AgentRegistry to cache Agent objects across deployments.
@@ -712,12 +859,20 @@ class Agent:
712
859
 
713
860
  Returns:
714
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.
715
865
  """
716
866
  if not self.agents:
717
867
  return []
718
868
 
719
- # Resolve each agent reference to a deployed Agent, extract ID
720
- 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
721
876
 
722
877
  def _create_or_update_agent(
723
878
  self,
@@ -793,6 +948,19 @@ class Agent:
793
948
 
794
949
  return content
795
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
+
796
964
  # =========================================================================
797
965
  # API Methods - Available after deploy()
798
966
  # =========================================================================
@@ -801,19 +969,42 @@ class Agent:
801
969
  """Return a dict representation of the Agent.
802
970
 
803
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.
804
974
 
805
975
  Args:
806
976
  exclude_none: If True, exclude None values from the output.
807
977
 
808
978
  Returns:
809
- 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.
810
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
+
811
1001
  data = {
812
1002
  "id": self._id,
813
1003
  "name": self.name,
814
1004
  "instruction": self.instruction,
815
1005
  "description": self.description,
816
- "type": self.agent_type,
1006
+ "agent_type": self.agent_type,
1007
+ "type": self.agent_type, # Legacy key for backward compatibility
817
1008
  "framework": self.framework,
818
1009
  "version": self.version,
819
1010
  "tools": self.tools,
@@ -821,13 +1012,16 @@ class Agent:
821
1012
  "mcps": self.mcps,
822
1013
  "timeout": self.timeout,
823
1014
  "metadata": self.metadata,
824
- "agent_config": self.agent_config,
1015
+ "model": self.model,
1016
+ "agent_config": agent_config,
825
1017
  "tool_configs": self.tool_configs,
826
1018
  "mcp_configs": self.mcp_configs,
827
1019
  "a2a_profile": self.a2a_profile,
1020
+ "guardrail": self.guardrail,
828
1021
  "created_at": self._created_at,
829
1022
  "updated_at": self._updated_at,
830
1023
  }
1024
+
831
1025
  if exclude_none:
832
1026
  return {k: v for k, v in data.items() if v is not None}
833
1027
  return data
@@ -844,6 +1038,36 @@ class Agent:
844
1038
  self._client = client
845
1039
  return self
846
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
+
847
1071
  def _prepare_run_kwargs(
848
1072
  self,
849
1073
  message: str,
@@ -892,6 +1116,10 @@ class Agent:
892
1116
  )
893
1117
 
894
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
895
1123
  return agent_client, call_kwargs
896
1124
 
897
1125
  def _get_local_runner_or_raise(self) -> Any:
@@ -911,7 +1139,16 @@ class Agent:
911
1139
 
912
1140
  if check_local_runtime_available():
913
1141
  return get_default_runner()
914
- 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
+ )
915
1152
 
916
1153
  def _prepare_local_runner_kwargs(
917
1154
  self,
@@ -946,6 +1183,7 @@ class Agent:
946
1183
  self,
947
1184
  message: str,
948
1185
  verbose: bool = False,
1186
+ local: bool = False,
949
1187
  runtime_config: dict[str, Any] | None = None,
950
1188
  chat_history: list[dict[str, str]] | None = None,
951
1189
  **kwargs: Any,
@@ -958,9 +1196,13 @@ class Agent:
958
1196
  - **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
959
1197
  execution happens locally via aip-agents (no server required).
960
1198
 
1199
+ You can force local execution for a deployed agent by passing `local=True`.
1200
+
961
1201
  Args:
962
1202
  message: The message to send to the agent.
963
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.
964
1206
  runtime_config: Optional runtime configuration for tools, MCPs, and agents.
965
1207
  Keys can be SDK objects, UUIDs, or names. Example:
966
1208
  {
@@ -982,16 +1224,19 @@ class Agent:
982
1224
  RuntimeError: If server-backed execution fails due to client issues.
983
1225
  """
984
1226
  # Backend routing: deployed agents use server, undeployed use local (if available)
985
- if self.id:
1227
+ if self.id and not local:
986
1228
  # Server-backed execution path (agent is deployed)
987
1229
  agent_client, call_kwargs = self._prepare_run_kwargs(
988
- message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
1230
+ message,
1231
+ verbose,
1232
+ runtime_config or kwargs.get("runtime_config"),
1233
+ **kwargs,
989
1234
  )
990
1235
  if chat_history is not None:
991
1236
  call_kwargs["chat_history"] = chat_history
992
1237
  return agent_client.run_agent(**call_kwargs)
993
1238
 
994
- # Local execution path (agent is not deployed)
1239
+ # Local execution path (agent is not deployed OR local=True)
995
1240
  runner = self._get_local_runner_or_raise()
996
1241
  local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
997
1242
  return runner.run(**local_kwargs)
@@ -1000,6 +1245,7 @@ class Agent:
1000
1245
  self,
1001
1246
  message: str,
1002
1247
  verbose: bool = False,
1248
+ local: bool = False,
1003
1249
  runtime_config: dict[str, Any] | None = None,
1004
1250
  chat_history: list[dict[str, str]] | None = None,
1005
1251
  **kwargs: Any,
@@ -1012,9 +1258,13 @@ class Agent:
1012
1258
  - **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
1013
1259
  execution happens locally via aip-agents (no server required).
1014
1260
 
1261
+ You can force local execution for a deployed agent by passing `local=True`.
1262
+
1015
1263
  Args:
1016
1264
  message: The message to send to the agent.
1017
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.
1018
1268
  runtime_config: Optional runtime configuration for tools, MCPs, and agents.
1019
1269
  Keys can be SDK objects, UUIDs, or names. Example:
1020
1270
  {
@@ -1036,10 +1286,13 @@ class Agent:
1036
1286
  RuntimeError: If server-backed execution fails due to client issues.
1037
1287
  """
1038
1288
  # Backend routing: deployed agents use server, undeployed use local (if available)
1039
- if self.id:
1289
+ if self.id and not local:
1040
1290
  # Server-backed execution path (agent is deployed)
1041
1291
  agent_client, call_kwargs = self._prepare_run_kwargs(
1042
- message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
1292
+ message,
1293
+ verbose,
1294
+ runtime_config or kwargs.get("runtime_config"),
1295
+ **kwargs,
1043
1296
  )
1044
1297
  if chat_history is not None:
1045
1298
  call_kwargs["chat_history"] = chat_history
@@ -1048,7 +1301,7 @@ class Agent:
1048
1301
  yield chunk
1049
1302
  return
1050
1303
 
1051
- # Local execution path (agent is not deployed)
1304
+ # Local execution path (agent is not deployed OR local=True)
1052
1305
  runner = self._get_local_runner_or_raise()
1053
1306
  local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
1054
1307
  result = await runner.arun(**local_kwargs)