glaip-sdk 0.6.11__py3-none-any.whl → 0.6.14__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 (156) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
  3. glaip_sdk-0.6.14.dist-info/RECORD +12 -0
  4. {glaip_sdk-0.6.11.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
  5. glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
  6. glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
  7. glaip_sdk/agents/__init__.py +0 -27
  8. glaip_sdk/agents/base.py +0 -1191
  9. glaip_sdk/cli/__init__.py +0 -9
  10. glaip_sdk/cli/account_store.py +0 -540
  11. glaip_sdk/cli/agent_config.py +0 -78
  12. glaip_sdk/cli/auth.py +0 -699
  13. glaip_sdk/cli/commands/__init__.py +0 -5
  14. glaip_sdk/cli/commands/accounts.py +0 -746
  15. glaip_sdk/cli/commands/agents.py +0 -1509
  16. glaip_sdk/cli/commands/common_config.py +0 -101
  17. glaip_sdk/cli/commands/configure.py +0 -896
  18. glaip_sdk/cli/commands/mcps.py +0 -1356
  19. glaip_sdk/cli/commands/models.py +0 -69
  20. glaip_sdk/cli/commands/tools.py +0 -576
  21. glaip_sdk/cli/commands/transcripts.py +0 -755
  22. glaip_sdk/cli/commands/update.py +0 -61
  23. glaip_sdk/cli/config.py +0 -95
  24. glaip_sdk/cli/constants.py +0 -38
  25. glaip_sdk/cli/context.py +0 -150
  26. glaip_sdk/cli/core/__init__.py +0 -79
  27. glaip_sdk/cli/core/context.py +0 -124
  28. glaip_sdk/cli/core/output.py +0 -846
  29. glaip_sdk/cli/core/prompting.py +0 -649
  30. glaip_sdk/cli/core/rendering.py +0 -187
  31. glaip_sdk/cli/display.py +0 -355
  32. glaip_sdk/cli/hints.py +0 -57
  33. glaip_sdk/cli/io.py +0 -112
  34. glaip_sdk/cli/main.py +0 -604
  35. glaip_sdk/cli/masking.py +0 -136
  36. glaip_sdk/cli/mcp_validators.py +0 -287
  37. glaip_sdk/cli/pager.py +0 -266
  38. glaip_sdk/cli/parsers/__init__.py +0 -7
  39. glaip_sdk/cli/parsers/json_input.py +0 -177
  40. glaip_sdk/cli/resolution.py +0 -67
  41. glaip_sdk/cli/rich_helpers.py +0 -27
  42. glaip_sdk/cli/slash/__init__.py +0 -15
  43. glaip_sdk/cli/slash/accounts_controller.py +0 -578
  44. glaip_sdk/cli/slash/accounts_shared.py +0 -75
  45. glaip_sdk/cli/slash/agent_session.py +0 -285
  46. glaip_sdk/cli/slash/prompt.py +0 -256
  47. glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
  48. glaip_sdk/cli/slash/session.py +0 -1708
  49. glaip_sdk/cli/slash/tui/__init__.py +0 -9
  50. glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
  51. glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
  52. glaip_sdk/cli/slash/tui/loading.py +0 -58
  53. glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
  54. glaip_sdk/cli/transcript/__init__.py +0 -31
  55. glaip_sdk/cli/transcript/cache.py +0 -536
  56. glaip_sdk/cli/transcript/capture.py +0 -329
  57. glaip_sdk/cli/transcript/export.py +0 -38
  58. glaip_sdk/cli/transcript/history.py +0 -815
  59. glaip_sdk/cli/transcript/launcher.py +0 -77
  60. glaip_sdk/cli/transcript/viewer.py +0 -374
  61. glaip_sdk/cli/update_notifier.py +0 -290
  62. glaip_sdk/cli/utils.py +0 -263
  63. glaip_sdk/cli/validators.py +0 -238
  64. glaip_sdk/client/__init__.py +0 -11
  65. glaip_sdk/client/_agent_payloads.py +0 -520
  66. glaip_sdk/client/agent_runs.py +0 -147
  67. glaip_sdk/client/agents.py +0 -1335
  68. glaip_sdk/client/base.py +0 -502
  69. glaip_sdk/client/main.py +0 -249
  70. glaip_sdk/client/mcps.py +0 -370
  71. glaip_sdk/client/run_rendering.py +0 -700
  72. glaip_sdk/client/shared.py +0 -21
  73. glaip_sdk/client/tools.py +0 -661
  74. glaip_sdk/client/validators.py +0 -198
  75. glaip_sdk/config/constants.py +0 -52
  76. glaip_sdk/mcps/__init__.py +0 -21
  77. glaip_sdk/mcps/base.py +0 -345
  78. glaip_sdk/models/__init__.py +0 -90
  79. glaip_sdk/models/agent.py +0 -47
  80. glaip_sdk/models/agent_runs.py +0 -116
  81. glaip_sdk/models/common.py +0 -42
  82. glaip_sdk/models/mcp.py +0 -33
  83. glaip_sdk/models/tool.py +0 -33
  84. glaip_sdk/payload_schemas/__init__.py +0 -7
  85. glaip_sdk/payload_schemas/agent.py +0 -85
  86. glaip_sdk/registry/__init__.py +0 -55
  87. glaip_sdk/registry/agent.py +0 -164
  88. glaip_sdk/registry/base.py +0 -139
  89. glaip_sdk/registry/mcp.py +0 -253
  90. glaip_sdk/registry/tool.py +0 -232
  91. glaip_sdk/runner/__init__.py +0 -59
  92. glaip_sdk/runner/base.py +0 -84
  93. glaip_sdk/runner/deps.py +0 -115
  94. glaip_sdk/runner/langgraph.py +0 -782
  95. glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
  99. glaip_sdk/runner/tool_adapter/__init__.py +0 -18
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
  102. glaip_sdk/tools/__init__.py +0 -22
  103. glaip_sdk/tools/base.py +0 -435
  104. glaip_sdk/utils/__init__.py +0 -86
  105. glaip_sdk/utils/a2a/__init__.py +0 -34
  106. glaip_sdk/utils/a2a/event_processor.py +0 -188
  107. glaip_sdk/utils/agent_config.py +0 -194
  108. glaip_sdk/utils/bundler.py +0 -267
  109. glaip_sdk/utils/client.py +0 -111
  110. glaip_sdk/utils/client_utils.py +0 -486
  111. glaip_sdk/utils/datetime_helpers.py +0 -58
  112. glaip_sdk/utils/discovery.py +0 -78
  113. glaip_sdk/utils/display.py +0 -135
  114. glaip_sdk/utils/export.py +0 -143
  115. glaip_sdk/utils/general.py +0 -61
  116. glaip_sdk/utils/import_export.py +0 -168
  117. glaip_sdk/utils/import_resolver.py +0 -492
  118. glaip_sdk/utils/instructions.py +0 -101
  119. glaip_sdk/utils/rendering/__init__.py +0 -115
  120. glaip_sdk/utils/rendering/formatting.py +0 -264
  121. glaip_sdk/utils/rendering/layout/__init__.py +0 -64
  122. glaip_sdk/utils/rendering/layout/panels.py +0 -156
  123. glaip_sdk/utils/rendering/layout/progress.py +0 -202
  124. glaip_sdk/utils/rendering/layout/summary.py +0 -74
  125. glaip_sdk/utils/rendering/layout/transcript.py +0 -606
  126. glaip_sdk/utils/rendering/models.py +0 -85
  127. glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
  128. glaip_sdk/utils/rendering/renderer/base.py +0 -1024
  129. glaip_sdk/utils/rendering/renderer/config.py +0 -27
  130. glaip_sdk/utils/rendering/renderer/console.py +0 -55
  131. glaip_sdk/utils/rendering/renderer/debug.py +0 -178
  132. glaip_sdk/utils/rendering/renderer/factory.py +0 -138
  133. glaip_sdk/utils/rendering/renderer/stream.py +0 -202
  134. glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
  135. glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
  136. glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
  137. glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
  138. glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
  139. glaip_sdk/utils/rendering/state.py +0 -204
  140. glaip_sdk/utils/rendering/step_tree_state.py +0 -100
  141. glaip_sdk/utils/rendering/steps/__init__.py +0 -34
  142. glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
  143. glaip_sdk/utils/rendering/steps/format.py +0 -176
  144. glaip_sdk/utils/rendering/steps/manager.py +0 -387
  145. glaip_sdk/utils/rendering/timing.py +0 -36
  146. glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
  147. glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
  148. glaip_sdk/utils/resource_refs.py +0 -195
  149. glaip_sdk/utils/run_renderer.py +0 -41
  150. glaip_sdk/utils/runtime_config.py +0 -425
  151. glaip_sdk/utils/serialization.py +0 -424
  152. glaip_sdk/utils/sync.py +0 -142
  153. glaip_sdk/utils/tool_detection.py +0 -33
  154. glaip_sdk/utils/validation.py +0 -264
  155. glaip_sdk-0.6.11.dist-info/RECORD +0 -159
  156. glaip_sdk-0.6.11.dist-info/entry_points.txt +0 -3
@@ -1,782 +0,0 @@
1
- """LangGraph-based runner for local agent execution.
2
-
3
- This module provides the LangGraphRunner which executes glaip-sdk agents
4
- locally via the aip-agents LangGraphReactAgent, without requiring the AIP server.
5
-
6
- Authors:
7
- Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
8
-
9
- Example:
10
- >>> from glaip_sdk.runner import LangGraphRunner
11
- >>> from glaip_sdk.agents import Agent
12
- >>>
13
- >>> runner = LangGraphRunner()
14
- >>> agent = Agent(name="my-agent", instruction="You are helpful.")
15
- >>> result = runner.run(agent, "Hello, world!")
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- import asyncio
21
- import inspect
22
- from dataclasses import dataclass
23
- import logging
24
- from typing import TYPE_CHECKING, Any
25
-
26
- from gllm_core.utils import LoggerManager
27
-
28
- from glaip_sdk.runner.base import BaseRunner
29
- from glaip_sdk.runner.deps import (
30
- check_local_runtime_available,
31
- get_local_runtime_missing_message,
32
- )
33
- from glaip_sdk.client.run_rendering import AgentRunRenderingManager
34
-
35
- if TYPE_CHECKING:
36
- from langchain_core.messages import BaseMessage
37
-
38
- from glaip_sdk.agents.base import Agent
39
-
40
-
41
- _AIP_LOGS_SWALLOWED = False
42
-
43
-
44
- def _swallow_aip_logs(level: int = logging.ERROR) -> None:
45
- """Consume noisy AIPAgents logs once (opt-in via runner flag)."""
46
- global _AIP_LOGS_SWALLOWED
47
- if _AIP_LOGS_SWALLOWED:
48
- return
49
- prefixes = ("aip_agents.",)
50
-
51
- def _silence(name: str) -> None:
52
- lg = logging.getLogger(name)
53
- lg.handlers = [logging.NullHandler()]
54
- lg.propagate = False
55
- lg.setLevel(level)
56
-
57
- # Silence any already-registered loggers under the given prefixes
58
- for logger_name in logging.root.manager.loggerDict:
59
- if any(logger_name.startswith(prefix) for prefix in prefixes):
60
- _silence(logger_name)
61
-
62
- # Also set the base prefix loggers so future children inherit silence
63
- for prefix in prefixes:
64
- _silence(prefix.rstrip("."))
65
- _AIP_LOGS_SWALLOWED = True
66
-
67
-
68
- logger = LoggerManager().get_logger(__name__)
69
-
70
-
71
- def _convert_chat_history_to_messages(
72
- chat_history: list[dict[str, str]] | None,
73
- ) -> list[BaseMessage]:
74
- """Convert chat history dicts to LangChain messages.
75
-
76
- Args:
77
- chat_history: List of dicts with "role" and "content" keys.
78
- Supported roles: "user"/"human", "assistant"/"ai", "system".
79
-
80
- Returns:
81
- List of LangChain BaseMessage instances.
82
- """
83
- if not chat_history:
84
- return []
85
-
86
- from langchain_core.messages import AIMessage, HumanMessage, SystemMessage # noqa: PLC0415
87
-
88
- messages: list[BaseMessage] = []
89
- for msg in chat_history:
90
- role = msg.get("role", "").lower()
91
- content = msg.get("content", "")
92
-
93
- if role in ("user", "human"):
94
- messages.append(HumanMessage(content=content))
95
- elif role in ("assistant", "ai"):
96
- messages.append(AIMessage(content=content))
97
- elif role == "system":
98
- messages.append(SystemMessage(content=content))
99
- else:
100
- # Default to human message for unknown roles
101
- logger.warning("Unknown chat history role '%s', treating as user message", role)
102
- messages.append(HumanMessage(content=content))
103
-
104
- return messages
105
-
106
-
107
- @dataclass(frozen=True, slots=True)
108
- class LangGraphRunner(BaseRunner):
109
- """Runner implementation using aip-agents LangGraphReactAgent.
110
-
111
- Current behavior:
112
- - Execute via `LangGraphReactAgent.arun_sse_stream()` (normalized SSE-compatible stream)
113
- - Route all events through `AgentRunRenderingManager.async_process_stream_events`
114
- for unified rendering between local and remote agents
115
-
116
- Attributes:
117
- default_model: Model name to use when agent.model is not set.
118
- Defaults to "gpt-4o-mini".
119
- """
120
-
121
- default_model: str = "openai/gpt-4o-mini"
122
-
123
- def run(
124
- self,
125
- agent: Agent,
126
- message: str,
127
- verbose: bool = False,
128
- runtime_config: dict[str, Any] | None = None,
129
- chat_history: list[dict[str, str]] | None = None,
130
- *,
131
- swallow_aip_logs: bool = True,
132
- **kwargs: Any,
133
- ) -> str:
134
- """Execute agent synchronously and return final response text.
135
-
136
- Args:
137
- agent: The glaip_sdk Agent to execute.
138
- message: The user message to send to the agent.
139
- verbose: If True, emit debug trace output during execution.
140
- Defaults to False.
141
- runtime_config: Optional runtime configuration for tools, MCPs, etc.
142
- Defaults to None. (Implemented in PR-04+)
143
- chat_history: Optional list of prior conversation messages.
144
- Each message is a dict with "role" and "content" keys.
145
- Defaults to None.
146
- swallow_aip_logs: When True (default), silence noisy logs from aip-agents,
147
- gllm_inference, OpenAILMInvoker, and httpx. Set to False to honor user
148
- logging configuration.
149
- **kwargs: Additional keyword arguments passed to the backend.
150
-
151
- Returns:
152
- The final response text from the agent.
153
-
154
- Raises:
155
- RuntimeError: If the local runtime dependencies are not available.
156
- RuntimeError: If no final response is received from the agent.
157
- """
158
- if not check_local_runtime_available():
159
- raise RuntimeError(get_local_runtime_missing_message())
160
-
161
- try:
162
- asyncio.get_running_loop()
163
- except RuntimeError:
164
- pass
165
- else:
166
- raise RuntimeError(
167
- "LangGraphRunner.run() cannot be called from a running event loop. "
168
- "Use 'await LangGraphRunner.arun(...)' instead."
169
- )
170
-
171
- coro = self._arun_internal(
172
- agent=agent,
173
- message=message,
174
- verbose=verbose,
175
- runtime_config=runtime_config,
176
- chat_history=chat_history,
177
- swallow_aip_logs=swallow_aip_logs,
178
- **kwargs,
179
- )
180
-
181
- return asyncio.run(coro)
182
-
183
- async def arun(
184
- self,
185
- agent: Agent,
186
- message: str,
187
- verbose: bool = False,
188
- runtime_config: dict[str, Any] | None = None,
189
- chat_history: list[dict[str, str]] | None = None,
190
- *,
191
- swallow_aip_logs: bool = True,
192
- **kwargs: Any,
193
- ) -> str:
194
- """Execute agent asynchronously and return final response text.
195
-
196
- Args:
197
- agent: The glaip_sdk Agent to execute.
198
- message: The user message to send to the agent.
199
- verbose: If True, emit debug trace output during execution.
200
- Defaults to False.
201
- runtime_config: Optional runtime configuration for tools, MCPs, etc.
202
- Defaults to None. (Implemented in PR-04+)
203
- chat_history: Optional list of prior conversation messages.
204
- Each message is a dict with "role" and "content" keys.
205
- Defaults to None.
206
- swallow_aip_logs: When True (default), silence noisy AIPAgents logs.
207
- **kwargs: Additional keyword arguments passed to the backend.
208
-
209
- Returns:
210
- The final response text from the agent.
211
-
212
- Raises:
213
- RuntimeError: If no final response is received from the agent.
214
- """
215
- return await self._arun_internal(
216
- agent=agent,
217
- message=message,
218
- verbose=verbose,
219
- runtime_config=runtime_config,
220
- chat_history=chat_history,
221
- swallow_aip_logs=swallow_aip_logs,
222
- **kwargs,
223
- )
224
-
225
- async def _arun_internal(
226
- self,
227
- agent: Agent,
228
- message: str,
229
- verbose: bool = False,
230
- runtime_config: dict[str, Any] | None = None,
231
- chat_history: list[dict[str, str]] | None = None,
232
- *,
233
- swallow_aip_logs: bool = True,
234
- **kwargs: Any,
235
- ) -> str:
236
- """Internal async implementation of agent execution.
237
-
238
- Args:
239
- agent: The glaip_sdk Agent to execute.
240
- message: The user message to send to the agent.
241
- verbose: If True, emit debug trace output during execution.
242
- runtime_config: Optional runtime configuration for tools, MCPs, etc.
243
- chat_history: Optional list of prior conversation messages.
244
- swallow_aip_logs: When True (default), silence noisy AIPAgents logs.
245
- **kwargs: Additional keyword arguments passed to the backend.
246
-
247
- Returns:
248
- The final response text from the agent.
249
- """
250
- # Optionally swallow noisy AIPAgents logs
251
- if swallow_aip_logs:
252
- _swallow_aip_logs()
253
-
254
- # Build the local LangGraphReactAgent from the glaip_sdk Agent
255
- local_agent = self.build_langgraph_agent(agent, runtime_config=runtime_config)
256
-
257
- # Convert chat history to LangChain messages for the agent
258
- langchain_messages = _convert_chat_history_to_messages(chat_history)
259
- if langchain_messages:
260
- kwargs["messages"] = langchain_messages
261
- logger.debug(
262
- "Passing %d chat history messages to agent '%s'",
263
- len(langchain_messages),
264
- agent.name,
265
- )
266
-
267
- # Use shared render manager for unified processing
268
- render_manager = AgentRunRenderingManager(logger)
269
- renderer = render_manager.create_renderer(kwargs.get("renderer"), verbose=verbose)
270
- meta = render_manager.build_initial_metadata(agent.name, message, kwargs)
271
- render_manager.start_renderer(renderer, meta)
272
-
273
- try:
274
- # Use shared async stream processor for unified event handling
275
- (
276
- final_text,
277
- stats_usage,
278
- started_monotonic,
279
- finished_monotonic,
280
- ) = await render_manager.async_process_stream_events(
281
- local_agent.arun_sse_stream(message, **kwargs),
282
- renderer,
283
- meta,
284
- skip_final_render=True,
285
- )
286
- except KeyboardInterrupt:
287
- try:
288
- renderer.close()
289
- finally:
290
- raise
291
- except Exception:
292
- try:
293
- renderer.close()
294
- finally:
295
- raise
296
-
297
- # Use shared finalizer to avoid code duplication
298
- from glaip_sdk.client.run_rendering import finalize_render_manager # noqa: PLC0415
299
-
300
- return finalize_render_manager(
301
- render_manager, renderer, final_text, stats_usage, started_monotonic, finished_monotonic
302
- )
303
-
304
- def build_langgraph_agent(
305
- self,
306
- agent: Agent,
307
- runtime_config: dict[str, Any] | None = None,
308
- ) -> Any:
309
- """Build a LangGraphReactAgent from a glaip_sdk Agent definition.
310
-
311
- Args:
312
- agent: The glaip_sdk Agent to convert.
313
- runtime_config: Optional runtime configuration with tool_configs,
314
- mcp_configs, agent_config, and agent-specific overrides.
315
-
316
- Returns:
317
- A configured LangGraphReactAgent instance.
318
-
319
- Raises:
320
- ImportError: If aip-agents is not installed.
321
- ValueError: If agent has unsupported tools, MCPs, or sub-agents for local mode.
322
- """
323
- from aip_agents.agent import LangGraphReactAgent # noqa: PLC0415
324
-
325
- from glaip_sdk.runner.tool_adapter import LangChainToolAdapter # noqa: PLC0415
326
-
327
- # Adapt tools for local execution
328
- # NOTE: CLI parity waiver - local tool execution is SDK-only for MVP.
329
- # See specs/f/local-agent-runtime/plan.md: "CLI parity is explicitly deferred
330
- # and will require SDK Technical Lead sign-off per constitution principle IV."
331
- langchain_tools: list[Any] = []
332
- if agent.tools:
333
- adapter = LangChainToolAdapter()
334
- langchain_tools = adapter.adapt_tools(agent.tools)
335
-
336
- # Build sub-agents recursively
337
- sub_agent_instances = self._build_sub_agents(agent.agents, runtime_config)
338
-
339
- # Normalize runtime config: merge global and agent-specific configs
340
- normalized_config = self._normalize_runtime_config(runtime_config, agent)
341
-
342
- # Merge tool_configs: agent definition < runtime config
343
- tool_configs = self._merge_tool_configs(agent, normalized_config)
344
-
345
- # Merge mcp_configs: agent definition < runtime config
346
- mcp_configs = self._merge_mcp_configs(agent, normalized_config)
347
-
348
- # Merge agent_config: agent definition < runtime config
349
- merged_agent_config = self._merge_agent_config(agent, normalized_config)
350
- agent_config_params, agent_config_kwargs = self._apply_agent_config(merged_agent_config)
351
-
352
- # Build the LangGraphReactAgent with tools, sub-agents, and configs
353
- local_agent = LangGraphReactAgent(
354
- name=agent.name,
355
- instruction=agent.instruction,
356
- description=agent.description,
357
- model=agent.model or self.default_model,
358
- tools=langchain_tools,
359
- agents=sub_agent_instances if sub_agent_instances else None,
360
- tool_configs=tool_configs if tool_configs else None,
361
- **agent_config_params,
362
- **agent_config_kwargs,
363
- )
364
-
365
- # Add MCP servers if configured
366
- self._add_mcp_servers(local_agent, agent, mcp_configs)
367
-
368
- logger.debug(
369
- "Built local LangGraphReactAgent for agent '%s' with %d tools, %d sub-agents, and %d MCPs",
370
- agent.name,
371
- len(langchain_tools),
372
- len(sub_agent_instances),
373
- len(agent.mcps) if agent.mcps else 0,
374
- )
375
- return local_agent
376
-
377
- def _build_sub_agents(
378
- self,
379
- sub_agents: list[Any] | None,
380
- runtime_config: dict[str, Any] | None,
381
- ) -> list[Any]:
382
- """Build sub-agent instances recursively.
383
-
384
- Args:
385
- sub_agents: List of sub-agent definitions.
386
- runtime_config: Runtime config to pass to sub-agents.
387
-
388
- Returns:
389
- List of built sub-agent instances.
390
-
391
- Raises:
392
- ValueError: If sub-agent is platform-only.
393
- """
394
- if not sub_agents:
395
- return []
396
-
397
- sub_agent_instances = []
398
- for sub_agent in sub_agents:
399
- self._validate_sub_agent_for_local_mode(sub_agent)
400
- sub_agent_instances.append(self.build_langgraph_agent(sub_agent, runtime_config))
401
- return sub_agent_instances
402
-
403
- def _add_mcp_servers(
404
- self,
405
- local_agent: Any,
406
- agent: Agent,
407
- merged_mcp_configs: dict[str, Any],
408
- ) -> None:
409
- """Add MCP servers to a built agent.
410
-
411
- Args:
412
- local_agent: The LangGraphReactAgent to add MCPs to.
413
- agent: The glaip_sdk Agent with MCP definitions.
414
- merged_mcp_configs: Merged mcp_configs (agent definition + runtime).
415
- """
416
- if not agent.mcps:
417
- return
418
-
419
- from glaip_sdk.runner.mcp_adapter import LangChainMCPAdapter # noqa: PLC0415
420
-
421
- mcp_adapter = LangChainMCPAdapter()
422
- base_mcp_configs = mcp_adapter.adapt_mcps(agent.mcps)
423
-
424
- # Apply merged mcp_configs overrides (agent definition + runtime)
425
- if merged_mcp_configs:
426
- base_mcp_configs = self._apply_runtime_mcp_configs(base_mcp_configs, merged_mcp_configs)
427
-
428
- if base_mcp_configs:
429
- local_agent.add_mcp_server(base_mcp_configs)
430
- logger.debug(
431
- "Registered %d MCP server(s) for agent '%s'",
432
- len(base_mcp_configs),
433
- agent.name,
434
- )
435
-
436
- def _normalize_runtime_config(
437
- self,
438
- runtime_config: dict[str, Any] | None,
439
- agent: Agent,
440
- ) -> dict[str, Any]:
441
- """Normalize runtime_config for local execution.
442
-
443
- Merges global and agent-specific configs with proper priority.
444
- Keys are resolved from instances/classes to string names.
445
-
446
- Args:
447
- runtime_config: Raw runtime config from user.
448
- agent: The agent being built (for resolving agent-specific overrides).
449
-
450
- Returns:
451
- Normalized config with string keys and merged priorities.
452
- """
453
- from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
454
- merge_configs,
455
- normalize_local_config_keys,
456
- )
457
-
458
- if not runtime_config:
459
- return {}
460
-
461
- # 1. Extract global configs and normalize keys
462
- global_tool_configs = normalize_local_config_keys(runtime_config.get("tool_configs", {}))
463
- global_mcp_configs = normalize_local_config_keys(runtime_config.get("mcp_configs", {}))
464
- global_agent_config = runtime_config.get("agent_config", {})
465
-
466
- # 2. Extract agent-specific overrides (highest priority)
467
- agent_specific = self._get_agent_specific_config(runtime_config, agent)
468
- agent_tool_configs = normalize_local_config_keys(agent_specific.get("tool_configs", {}))
469
- agent_mcp_configs = normalize_local_config_keys(agent_specific.get("mcp_configs", {}))
470
- agent_config_override = agent_specific.get("agent_config", {})
471
-
472
- # 3. Merge with priority: global < agent-specific
473
- merged_result = {
474
- "tool_configs": merge_configs(global_tool_configs, agent_tool_configs),
475
- "mcp_configs": merge_configs(global_mcp_configs, agent_mcp_configs),
476
- "agent_config": merge_configs(global_agent_config, agent_config_override),
477
- }
478
- return merged_result
479
-
480
- def _get_agent_specific_config(
481
- self,
482
- runtime_config: dict[str, Any],
483
- agent: Agent,
484
- ) -> dict[str, Any]:
485
- """Extract agent-specific config from runtime_config.
486
-
487
- Args:
488
- runtime_config: Runtime config that may contain agent-specific overrides.
489
- agent: The agent to find config for.
490
-
491
- Returns:
492
- Agent-specific config dict, or empty dict if not found.
493
- """
494
- from glaip_sdk.utils.resource_refs import is_uuid # noqa: PLC0415
495
- from glaip_sdk.utils.runtime_config import get_name_from_key # noqa: PLC0415
496
-
497
- # Reserved keys at the top level
498
- reserved_keys = {"tool_configs", "mcp_configs", "agent_config"}
499
-
500
- # Try finding agent by instance, class, or name
501
- for key, value in runtime_config.items():
502
- if key in reserved_keys:
503
- continue # Skip global configs
504
-
505
- if isinstance(key, str) and is_uuid(key):
506
- logger.warning(
507
- "UUID agent override key '%s' is not supported in local mode; skipping. "
508
- "Use agent name string or Agent instance as the key instead.",
509
- key,
510
- )
511
- continue
512
-
513
- # Check if this key matches the agent
514
- try:
515
- key_name = get_name_from_key(key)
516
- except ValueError:
517
- continue # Skip invalid keys
518
-
519
- if key_name and key_name == agent.name:
520
- return value if isinstance(value, dict) else {}
521
-
522
- return {}
523
-
524
- def _merge_tool_configs(
525
- self,
526
- agent: Agent,
527
- normalized_config: dict[str, Any],
528
- ) -> dict[str, Any]:
529
- """Merge agent.tool_configs with runtime tool_configs.
530
-
531
- Priority (lowest to highest):
532
- 1. Agent definition (agent.tool_configs)
533
- 2. Runtime config (normalized_config["tool_configs"])
534
-
535
- Args:
536
- agent: The agent with optional tool_configs property.
537
- normalized_config: Normalized runtime config.
538
-
539
- Returns:
540
- Merged tool_configs dict.
541
- """
542
- from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
543
- merge_configs,
544
- normalize_local_config_keys,
545
- )
546
-
547
- # Get agent's tool_configs if defined
548
- agent_tool_configs = {}
549
- if hasattr(agent, "tool_configs") and agent.tool_configs:
550
- agent_tool_configs = normalize_local_config_keys(agent.tool_configs)
551
-
552
- # Get runtime tool_configs
553
- runtime_tool_configs = normalized_config.get("tool_configs", {})
554
-
555
- # Merge: agent definition < runtime config
556
- return merge_configs(agent_tool_configs, runtime_tool_configs)
557
-
558
- def _merge_mcp_configs(
559
- self,
560
- agent: Agent,
561
- normalized_config: dict[str, Any],
562
- ) -> dict[str, Any]:
563
- """Merge agent.mcp_configs with runtime mcp_configs.
564
-
565
- Priority (lowest to highest):
566
- 1. Agent definition (agent.mcp_configs)
567
- 2. Runtime config (normalized_config["mcp_configs"])
568
-
569
- Args:
570
- agent: The agent with optional mcp_configs property.
571
- normalized_config: Normalized runtime config.
572
-
573
- Returns:
574
- Merged mcp_configs dict.
575
- """
576
- from glaip_sdk.utils.runtime_config import ( # noqa: PLC0415
577
- merge_configs,
578
- normalize_local_config_keys,
579
- )
580
-
581
- # Get agent's mcp_configs if defined
582
- agent_mcp_configs = {}
583
- if hasattr(agent, "mcp_configs") and agent.mcp_configs:
584
- agent_mcp_configs = normalize_local_config_keys(agent.mcp_configs)
585
-
586
- # Get runtime mcp_configs
587
- runtime_mcp_configs = normalized_config.get("mcp_configs", {})
588
-
589
- # Merge: agent definition < runtime config
590
- return merge_configs(agent_mcp_configs, runtime_mcp_configs)
591
-
592
- def _merge_agent_config(
593
- self,
594
- agent: Agent,
595
- normalized_config: dict[str, Any],
596
- ) -> dict[str, Any]:
597
- """Merge agent.agent_config with runtime agent_config.
598
-
599
- Priority (lowest to highest):
600
- 1. Agent definition (agent.agent_config)
601
- 2. Runtime config (normalized_config["agent_config"])
602
-
603
- Args:
604
- agent: The agent with optional agent_config property.
605
- normalized_config: Normalized runtime config.
606
-
607
- Returns:
608
- Merged agent_config dict.
609
- """
610
- from glaip_sdk.utils.runtime_config import merge_configs # noqa: PLC0415
611
-
612
- # Get agent's agent_config if defined
613
- agent_agent_config = {}
614
- if hasattr(agent, "agent_config") and agent.agent_config:
615
- agent_agent_config = agent.agent_config
616
-
617
- # Get runtime agent_config
618
- runtime_agent_config = normalized_config.get("agent_config", {})
619
-
620
- # Merge: agent definition < runtime config
621
- return merge_configs(agent_agent_config, runtime_agent_config)
622
-
623
- def _apply_agent_config(
624
- self,
625
- agent_config: dict[str, Any],
626
- ) -> tuple[dict[str, Any], dict[str, Any]]:
627
- """Extract and separate agent_config into direct params and kwargs.
628
-
629
- Separates agent_config into parameters that go directly to LangGraphReactAgent
630
- constructor vs those that go through **kwargs.
631
-
632
- Args:
633
- agent_config: Runtime agent configuration dict.
634
-
635
- Returns:
636
- Tuple of (direct_params, kwargs_params):
637
- - direct_params: Parameters passed directly to LangGraphReactAgent.__init__()
638
- - kwargs_params: Parameters passed via **kwargs to BaseAgent
639
- """
640
- direct_params = {}
641
- kwargs_params = {}
642
-
643
- # Direct constructor parameters
644
- if "planning" in agent_config:
645
- direct_params["planning"] = agent_config["planning"]
646
-
647
- # Kwargs parameters (passed through **kwargs to BaseAgent)
648
- if "enable_pii" in agent_config:
649
- kwargs_params["enable_pii"] = agent_config["enable_pii"]
650
-
651
- if "memory" in agent_config:
652
- # Map "memory" to "memory_backend" for aip-agents compatibility
653
- kwargs_params["memory_backend"] = agent_config["memory"]
654
-
655
- # Additional memory-related settings
656
- memory_settings = ["agent_id", "memory_namespace", "save_interaction_to_memory"]
657
- for key in memory_settings:
658
- if key in agent_config:
659
- kwargs_params[key] = agent_config[key]
660
-
661
- return direct_params, kwargs_params
662
-
663
- def _apply_runtime_mcp_configs(
664
- self,
665
- base_configs: dict[str, Any],
666
- runtime_overrides: dict[str, Any],
667
- ) -> dict[str, Any]:
668
- """Apply runtime mcp_configs overrides to base MCP configurations.
669
-
670
- Merges runtime overrides into the base configs, handling authentication
671
- conversion to headers using MCPConfigBuilder.
672
-
673
- Args:
674
- base_configs: Base MCP configs from adapter (server_name -> config).
675
- runtime_overrides: Runtime mcp_configs overrides (server_name -> config).
676
-
677
- Returns:
678
- Merged MCP configs with authentication converted to headers.
679
- """
680
- return {
681
- server_name: self._merge_single_mcp_config(server_name, base_config, runtime_overrides.get(server_name))
682
- for server_name, base_config in base_configs.items()
683
- }
684
-
685
- def _merge_single_mcp_config(
686
- self,
687
- server_name: str,
688
- base_config: dict[str, Any],
689
- override: dict[str, Any] | None,
690
- ) -> dict[str, Any]:
691
- """Merge a single MCP config with runtime override.
692
-
693
- Args:
694
- server_name: Name of the MCP server.
695
- base_config: Base config from adapter.
696
- override: Optional runtime override config.
697
-
698
- Returns:
699
- Merged config dict.
700
- """
701
- merged = base_config.copy()
702
-
703
- if not override:
704
- return merged
705
-
706
- from glaip_sdk.runner.mcp_adapter.mcp_config_builder import ( # noqa: PLC0415
707
- MCPConfigBuilder,
708
- )
709
-
710
- # Handle authentication override
711
- if "authentication" in override:
712
- headers = MCPConfigBuilder.build_headers_from_auth(override["authentication"])
713
- if headers:
714
- merged["headers"] = headers
715
- logger.debug("Applied runtime authentication headers for MCP '%s'", server_name)
716
-
717
- # Merge other config keys (excluding authentication since we converted it)
718
- for key, value in override.items():
719
- if key != "authentication":
720
- merged[key] = value
721
-
722
- return merged
723
-
724
- def _validate_sub_agent_for_local_mode(self, sub_agent: Any) -> None:
725
- """Validate that a sub-agent reference is supported for local execution.
726
-
727
- Args:
728
- sub_agent: The sub-agent reference to validate.
729
-
730
- Raises:
731
- ValueError: If the sub-agent is not supported in local mode.
732
- """
733
- # String references are allowed by SDK API but not for local mode
734
- if isinstance(sub_agent, str):
735
- raise ValueError(
736
- f"Sub-agent '{sub_agent}' is a string reference and cannot be used in local mode. "
737
- "String sub-agent references are only supported for server execution. "
738
- "For local mode, define the sub-agent with Agent(name=..., instruction=...)."
739
- )
740
-
741
- # Validate sub-agent is not a class
742
- if inspect.isclass(sub_agent):
743
- raise ValueError(
744
- f"Sub-agent '{sub_agent.__name__}' is a class, not an instance. "
745
- "Local mode requires Agent INSTANCES. "
746
- "Did you forget to instantiate it? e.g., Agent(...), not Agent"
747
- )
748
-
749
- # Validate sub-agent is an Agent-like object (has required attributes)
750
- if not hasattr(sub_agent, "name") or not hasattr(sub_agent, "instruction"):
751
- raise ValueError(
752
- f"Sub-agent {type(sub_agent).__name__} is not supported in local mode. "
753
- "Local mode requires Agent instances with 'name' and 'instruction' attributes. "
754
- "Define the sub-agent with Agent(name=..., instruction=...)."
755
- )
756
-
757
- # Validate sub-agent is not platform-only (from_id, from_native)
758
- if getattr(sub_agent, "_lookup_only", False):
759
- agent_name = getattr(sub_agent, "name", "<unknown>")
760
- raise ValueError(
761
- f"Sub-agent '{agent_name}' is not supported in local mode. "
762
- "Platform agents (from_id, from_native) cannot be used as "
763
- "sub-agents in local execution. "
764
- "Define the sub-agent locally with Agent(name=..., instruction=...) instead."
765
- )
766
-
767
- def _log_event(self, event: dict[str, Any]) -> None:
768
- """Log an A2AEvent for verbose debug output.
769
-
770
- Args:
771
- event: The A2AEvent dictionary to log.
772
- """
773
- event_type = event.get("event_type", "unknown")
774
- content = event.get("content", "")
775
- is_final = event.get("is_final", False)
776
-
777
- # Truncate long content for readability
778
- content_str = str(content) if content else ""
779
- content_preview = content_str[:100] + "..." if len(content_str) > 100 else content_str
780
-
781
- final_marker = "(final)" if is_final else ""
782
- logger.info("[%s] %s %s", event_type, final_marker, content_preview)