glaip-sdk 0.0.20__py3-none-any.whl → 0.7.7__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 (216) hide show
  1. glaip_sdk/__init__.py +44 -4
  2. glaip_sdk/_version.py +10 -3
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1250 -0
  5. glaip_sdk/branding.py +15 -6
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +271 -45
  9. glaip_sdk/cli/commands/__init__.py +2 -2
  10. glaip_sdk/cli/commands/accounts.py +746 -0
  11. glaip_sdk/cli/commands/agents/__init__.py +119 -0
  12. glaip_sdk/cli/commands/agents/_common.py +561 -0
  13. glaip_sdk/cli/commands/agents/create.py +151 -0
  14. glaip_sdk/cli/commands/agents/delete.py +64 -0
  15. glaip_sdk/cli/commands/agents/get.py +89 -0
  16. glaip_sdk/cli/commands/agents/list.py +129 -0
  17. glaip_sdk/cli/commands/agents/run.py +264 -0
  18. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  19. glaip_sdk/cli/commands/agents/update.py +112 -0
  20. glaip_sdk/cli/commands/common_config.py +104 -0
  21. glaip_sdk/cli/commands/configure.py +734 -143
  22. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  23. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  24. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  25. glaip_sdk/cli/commands/mcps/create.py +152 -0
  26. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  27. glaip_sdk/cli/commands/mcps/get.py +212 -0
  28. glaip_sdk/cli/commands/mcps/list.py +69 -0
  29. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  30. glaip_sdk/cli/commands/mcps/update.py +190 -0
  31. glaip_sdk/cli/commands/models.py +14 -12
  32. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  33. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  34. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  35. glaip_sdk/cli/commands/tools/_common.py +80 -0
  36. glaip_sdk/cli/commands/tools/create.py +228 -0
  37. glaip_sdk/cli/commands/tools/delete.py +61 -0
  38. glaip_sdk/cli/commands/tools/get.py +103 -0
  39. glaip_sdk/cli/commands/tools/list.py +69 -0
  40. glaip_sdk/cli/commands/tools/script.py +49 -0
  41. glaip_sdk/cli/commands/tools/update.py +102 -0
  42. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  43. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  44. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  45. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  46. glaip_sdk/cli/commands/transcripts_original.py +756 -0
  47. glaip_sdk/cli/commands/update.py +164 -23
  48. glaip_sdk/cli/config.py +49 -7
  49. glaip_sdk/cli/constants.py +38 -0
  50. glaip_sdk/cli/context.py +8 -0
  51. glaip_sdk/cli/core/__init__.py +79 -0
  52. glaip_sdk/cli/core/context.py +124 -0
  53. glaip_sdk/cli/core/output.py +851 -0
  54. glaip_sdk/cli/core/prompting.py +649 -0
  55. glaip_sdk/cli/core/rendering.py +187 -0
  56. glaip_sdk/cli/display.py +45 -32
  57. glaip_sdk/cli/entrypoint.py +20 -0
  58. glaip_sdk/cli/hints.py +57 -0
  59. glaip_sdk/cli/io.py +14 -17
  60. glaip_sdk/cli/main.py +344 -167
  61. glaip_sdk/cli/masking.py +21 -33
  62. glaip_sdk/cli/mcp_validators.py +5 -15
  63. glaip_sdk/cli/pager.py +15 -22
  64. glaip_sdk/cli/parsers/__init__.py +1 -3
  65. glaip_sdk/cli/parsers/json_input.py +11 -22
  66. glaip_sdk/cli/resolution.py +5 -10
  67. glaip_sdk/cli/rich_helpers.py +1 -3
  68. glaip_sdk/cli/slash/__init__.py +0 -9
  69. glaip_sdk/cli/slash/accounts_controller.py +580 -0
  70. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  71. glaip_sdk/cli/slash/agent_session.py +65 -29
  72. glaip_sdk/cli/slash/prompt.py +24 -10
  73. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  74. glaip_sdk/cli/slash/session.py +827 -232
  75. glaip_sdk/cli/slash/tui/__init__.py +34 -0
  76. glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
  77. glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
  78. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  79. glaip_sdk/cli/slash/tui/clipboard.py +147 -0
  80. glaip_sdk/cli/slash/tui/context.py +59 -0
  81. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  82. glaip_sdk/cli/slash/tui/loading.py +58 -0
  83. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  84. glaip_sdk/cli/slash/tui/terminal.py +402 -0
  85. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  86. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  87. glaip_sdk/cli/slash/tui/theme/manager.py +86 -0
  88. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  89. glaip_sdk/cli/slash/tui/toast.py +123 -0
  90. glaip_sdk/cli/transcript/__init__.py +12 -52
  91. glaip_sdk/cli/transcript/cache.py +258 -60
  92. glaip_sdk/cli/transcript/capture.py +72 -21
  93. glaip_sdk/cli/transcript/history.py +815 -0
  94. glaip_sdk/cli/transcript/launcher.py +1 -3
  95. glaip_sdk/cli/transcript/viewer.py +79 -329
  96. glaip_sdk/cli/update_notifier.py +385 -24
  97. glaip_sdk/cli/validators.py +16 -18
  98. glaip_sdk/client/__init__.py +3 -1
  99. glaip_sdk/client/_schedule_payloads.py +89 -0
  100. glaip_sdk/client/agent_runs.py +147 -0
  101. glaip_sdk/client/agents.py +370 -100
  102. glaip_sdk/client/base.py +78 -35
  103. glaip_sdk/client/hitl.py +136 -0
  104. glaip_sdk/client/main.py +25 -10
  105. glaip_sdk/client/mcps.py +166 -27
  106. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  107. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +65 -74
  108. glaip_sdk/client/payloads/agent/responses.py +43 -0
  109. glaip_sdk/client/run_rendering.py +583 -79
  110. glaip_sdk/client/schedules.py +439 -0
  111. glaip_sdk/client/shared.py +21 -0
  112. glaip_sdk/client/tools.py +214 -56
  113. glaip_sdk/client/validators.py +20 -48
  114. glaip_sdk/config/constants.py +11 -0
  115. glaip_sdk/exceptions.py +1 -3
  116. glaip_sdk/hitl/__init__.py +48 -0
  117. glaip_sdk/hitl/base.py +64 -0
  118. glaip_sdk/hitl/callback.py +43 -0
  119. glaip_sdk/hitl/local.py +121 -0
  120. glaip_sdk/hitl/remote.py +523 -0
  121. glaip_sdk/icons.py +9 -3
  122. glaip_sdk/mcps/__init__.py +21 -0
  123. glaip_sdk/mcps/base.py +345 -0
  124. glaip_sdk/models/__init__.py +107 -0
  125. glaip_sdk/models/agent.py +47 -0
  126. glaip_sdk/models/agent_runs.py +117 -0
  127. glaip_sdk/models/common.py +42 -0
  128. glaip_sdk/models/mcp.py +33 -0
  129. glaip_sdk/models/schedule.py +224 -0
  130. glaip_sdk/models/tool.py +33 -0
  131. glaip_sdk/payload_schemas/__init__.py +1 -13
  132. glaip_sdk/payload_schemas/agent.py +1 -3
  133. glaip_sdk/registry/__init__.py +55 -0
  134. glaip_sdk/registry/agent.py +164 -0
  135. glaip_sdk/registry/base.py +139 -0
  136. glaip_sdk/registry/mcp.py +253 -0
  137. glaip_sdk/registry/tool.py +445 -0
  138. glaip_sdk/rich_components.py +58 -2
  139. glaip_sdk/runner/__init__.py +76 -0
  140. glaip_sdk/runner/base.py +84 -0
  141. glaip_sdk/runner/deps.py +112 -0
  142. glaip_sdk/runner/langgraph.py +872 -0
  143. glaip_sdk/runner/logging_config.py +77 -0
  144. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  145. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  146. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  147. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  148. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  149. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  150. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
  151. glaip_sdk/schedules/__init__.py +22 -0
  152. glaip_sdk/schedules/base.py +291 -0
  153. glaip_sdk/tools/__init__.py +22 -0
  154. glaip_sdk/tools/base.py +468 -0
  155. glaip_sdk/utils/__init__.py +59 -12
  156. glaip_sdk/utils/a2a/__init__.py +34 -0
  157. glaip_sdk/utils/a2a/event_processor.py +188 -0
  158. glaip_sdk/utils/agent_config.py +4 -14
  159. glaip_sdk/utils/bundler.py +403 -0
  160. glaip_sdk/utils/client.py +111 -0
  161. glaip_sdk/utils/client_utils.py +46 -28
  162. glaip_sdk/utils/datetime_helpers.py +58 -0
  163. glaip_sdk/utils/discovery.py +78 -0
  164. glaip_sdk/utils/display.py +25 -21
  165. glaip_sdk/utils/export.py +143 -0
  166. glaip_sdk/utils/general.py +1 -36
  167. glaip_sdk/utils/import_export.py +15 -16
  168. glaip_sdk/utils/import_resolver.py +524 -0
  169. glaip_sdk/utils/instructions.py +101 -0
  170. glaip_sdk/utils/rendering/__init__.py +115 -1
  171. glaip_sdk/utils/rendering/formatting.py +38 -23
  172. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  173. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
  174. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
  175. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  176. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  177. glaip_sdk/utils/rendering/models.py +18 -8
  178. glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
  179. glaip_sdk/utils/rendering/renderer/base.py +534 -882
  180. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  181. glaip_sdk/utils/rendering/renderer/debug.py +30 -34
  182. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  183. glaip_sdk/utils/rendering/renderer/stream.py +13 -54
  184. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  185. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  186. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  187. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  188. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  189. glaip_sdk/utils/rendering/state.py +204 -0
  190. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  191. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  192. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  193. glaip_sdk/utils/rendering/steps/format.py +176 -0
  194. glaip_sdk/utils/rendering/{steps.py → steps/manager.py} +122 -26
  195. glaip_sdk/utils/rendering/timing.py +36 -0
  196. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  197. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  198. glaip_sdk/utils/resource_refs.py +29 -26
  199. glaip_sdk/utils/runtime_config.py +425 -0
  200. glaip_sdk/utils/serialization.py +32 -46
  201. glaip_sdk/utils/sync.py +162 -0
  202. glaip_sdk/utils/tool_detection.py +301 -0
  203. glaip_sdk/utils/tool_storage_provider.py +140 -0
  204. glaip_sdk/utils/validation.py +20 -28
  205. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/METADATA +78 -23
  206. glaip_sdk-0.7.7.dist-info/RECORD +213 -0
  207. {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/WHEEL +2 -1
  208. glaip_sdk-0.7.7.dist-info/entry_points.txt +2 -0
  209. glaip_sdk-0.7.7.dist-info/top_level.txt +1 -0
  210. glaip_sdk/cli/commands/agents.py +0 -1412
  211. glaip_sdk/cli/commands/mcps.py +0 -1225
  212. glaip_sdk/cli/commands/tools.py +0 -597
  213. glaip_sdk/cli/utils.py +0 -1330
  214. glaip_sdk/models.py +0 -259
  215. glaip_sdk-0.0.20.dist-info/RECORD +0 -80
  216. glaip_sdk-0.0.20.dist-info/entry_points.txt +0 -3
@@ -1,1412 +0,0 @@
1
- """Agent CLI commands for AIP SDK.
2
-
3
- Authors:
4
- Raymond Christopher (raymond.christopher@gdplabs.id)
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import json
10
- import os
11
- from collections.abc import Mapping
12
- from pathlib import Path
13
- from typing import Any
14
-
15
- import click
16
- from rich.console import Console
17
-
18
- from glaip_sdk.branding import (
19
- ACCENT_STYLE,
20
- ERROR_STYLE,
21
- INFO,
22
- SUCCESS,
23
- SUCCESS_STYLE,
24
- WARNING_STYLE,
25
- )
26
- from glaip_sdk.cli.agent_config import (
27
- merge_agent_config_with_cli_args as merge_import_with_cli_args,
28
- )
29
- from glaip_sdk.cli.agent_config import (
30
- resolve_agent_language_model_selection as resolve_language_model_selection,
31
- )
32
- from glaip_sdk.cli.agent_config import (
33
- sanitize_agent_config_for_cli as sanitize_agent_config,
34
- )
35
- from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
36
- from glaip_sdk.cli.display import (
37
- build_resource_result_data,
38
- display_agent_run_suggestions,
39
- display_confirmation_prompt,
40
- display_creation_success,
41
- display_deletion_success,
42
- display_update_success,
43
- handle_json_output,
44
- handle_rich_output,
45
- print_api_error,
46
- )
47
- from glaip_sdk.cli.io import (
48
- export_resource_to_file_with_validation as export_resource_to_file,
49
- )
50
- from glaip_sdk.cli.io import (
51
- fetch_raw_resource_details,
52
- )
53
- from glaip_sdk.cli.io import (
54
- load_resource_from_file_with_validation as load_resource_from_file,
55
- )
56
- from glaip_sdk.cli.resolution import resolve_resource_reference
57
- from glaip_sdk.cli.rich_helpers import markup_text, print_markup
58
- from glaip_sdk.cli.transcript import (
59
- maybe_launch_post_run_viewer,
60
- store_transcript_for_session,
61
- )
62
- from glaip_sdk.cli.utils import (
63
- _fuzzy_pick_for_resources,
64
- build_renderer,
65
- coerce_to_row,
66
- get_client,
67
- output_list,
68
- output_result,
69
- spinner_context,
70
- )
71
- from glaip_sdk.cli.validators import (
72
- validate_agent_instruction_cli as validate_agent_instruction,
73
- )
74
- from glaip_sdk.cli.validators import (
75
- validate_agent_name_cli as validate_agent_name,
76
- )
77
- from glaip_sdk.cli.validators import (
78
- validate_timeout_cli as validate_timeout,
79
- )
80
- from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
81
- from glaip_sdk.exceptions import AgentTimeoutError
82
- from glaip_sdk.icons import ICON_AGENT
83
- from glaip_sdk.utils import format_datetime, is_uuid
84
- from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
85
- from glaip_sdk.utils.import_export import convert_export_to_import_format
86
- from glaip_sdk.utils.validation import coerce_timeout
87
-
88
- console = Console()
89
-
90
- # Error message constants
91
- AGENT_NOT_FOUND_ERROR = "Agent not found"
92
-
93
-
94
- def _safe_agent_attribute(agent: Any, name: str) -> Any:
95
- """Return attribute value for ``name`` while filtering Mock sentinels."""
96
- try:
97
- value = getattr(agent, name)
98
- except Exception:
99
- return None
100
-
101
- if hasattr(value, "_mock_name"):
102
- return None
103
- return value
104
-
105
-
106
- def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
107
- """Convert a mapping-like candidate to a plain dict when possible."""
108
- if candidate is None:
109
- return None
110
- if isinstance(candidate, Mapping):
111
- return dict(candidate)
112
- return None
113
-
114
-
115
- def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
116
- """Attempt to call the named method and coerce its output to a dict."""
117
- method = getattr(agent, method_name, None)
118
- if not callable(method):
119
- return None
120
- try:
121
- candidate = method()
122
- except Exception:
123
- return None
124
- return _coerce_mapping_candidate(candidate)
125
-
126
-
127
- def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
128
- """Try standard serialisation helpers to produce a mapping."""
129
- for attr in ("model_dump", "dict", "to_dict"):
130
- mapping = _call_agent_method(agent, attr)
131
- if mapping is not None:
132
- return mapping
133
- return None
134
-
135
-
136
- def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
137
- """Construct a minimal mapping from well-known agent attributes."""
138
- fallback_fields = (
139
- "id",
140
- "name",
141
- "instruction",
142
- "description",
143
- "model",
144
- "agent_config",
145
- "tools",
146
- "agents",
147
- "mcps",
148
- "timeout",
149
- "tool_configs",
150
- )
151
-
152
- fallback: dict[str, Any] = {}
153
- for field in fallback_fields:
154
- value = _safe_agent_attribute(agent, field)
155
- if value is not None:
156
- fallback[field] = value
157
-
158
- return fallback or {"name": str(agent)}
159
-
160
-
161
- def _prepare_agent_output(agent: Any) -> dict[str, Any]:
162
- """Build a JSON-serialisable mapping for CLI output."""
163
- method_mapping = _coerce_agent_via_methods(agent)
164
- if method_mapping is not None:
165
- return method_mapping
166
-
167
- intrinsic = _coerce_mapping_candidate(agent)
168
- if intrinsic is not None:
169
- return intrinsic
170
-
171
- return _build_fallback_agent_mapping(agent)
172
-
173
-
174
- def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
175
- """Fetch full agent details by ID to ensure all fields are populated."""
176
- try:
177
- agent_id = str(getattr(agent, "id", "")).strip()
178
- if agent_id:
179
- return client.agents.get_agent_by_id(agent_id)
180
- except Exception:
181
- # If fetching full details fails, continue with the resolved object
182
- pass
183
- return agent
184
-
185
-
186
- def _normalise_model_name(value: Any) -> str | None:
187
- """Return a cleaned model name or None when not usable."""
188
- if value is None:
189
- return None
190
- if isinstance(value, str):
191
- cleaned = value.strip()
192
- return cleaned or None
193
- if isinstance(value, bool):
194
- return None
195
- return str(value)
196
-
197
-
198
- def _model_from_config(agent: Any) -> str | None:
199
- """Extract a usable model name from an agent's configuration mapping."""
200
- config = getattr(agent, "agent_config", None)
201
- if not config or not isinstance(config, dict):
202
- return None
203
-
204
- for key in ("lm_name", "model"):
205
- normalised = _normalise_model_name(config.get(key))
206
- if normalised:
207
- return normalised
208
- return None
209
-
210
-
211
- def _get_agent_model_name(agent: Any) -> str | None:
212
- """Extract model name from agent configuration."""
213
- config_model = _model_from_config(agent)
214
- if config_model:
215
- return config_model
216
-
217
- normalised_attr = _normalise_model_name(getattr(agent, "model", None))
218
- if normalised_attr:
219
- return normalised_attr
220
-
221
- return DEFAULT_MODEL
222
-
223
-
224
- def _resolve_resources_by_name(
225
- _client: Any, items: tuple[str, ...], resource_type: str, find_func: Any, label: str
226
- ) -> list[str]:
227
- """Resolve resource names/IDs to IDs, handling ambiguity.
228
-
229
- Args:
230
- client: API client
231
- items: Tuple of resource names/IDs
232
- resource_type: Type of resource ("tool" or "agent")
233
- find_func: Function to find resources by name
234
- label: Label for error messages
235
-
236
- Returns:
237
- List of resolved resource IDs
238
- """
239
- out = []
240
- for ref in items or ():
241
- if is_uuid(ref):
242
- out.append(ref)
243
- continue
244
-
245
- matches = find_func(name=ref)
246
- if not matches:
247
- raise click.ClickException(f"{label} not found: {ref}")
248
- if len(matches) > 1:
249
- raise click.ClickException(
250
- f"Multiple {resource_type}s named '{ref}'. Use ID instead."
251
- )
252
- out.append(str(matches[0].id))
253
- return out
254
-
255
-
256
- def _fetch_and_format_raw_agent_data(client: Any, agent: Any) -> dict | None:
257
- """Fetch raw agent data and format it for display."""
258
- try:
259
- raw_agent_data = fetch_raw_resource_details(client, agent, "agents")
260
- if not raw_agent_data:
261
- return None
262
-
263
- # Format dates for better display
264
- formatted_data = raw_agent_data.copy()
265
- if "created_at" in formatted_data:
266
- formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
267
- if "updated_at" in formatted_data:
268
- formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
269
-
270
- return formatted_data
271
- except Exception:
272
- return None
273
-
274
-
275
- def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
276
- """Format fallback agent data using Pydantic model."""
277
- full_agent = _fetch_full_agent_details(client, agent)
278
-
279
- # Define fields to extract
280
- fields = [
281
- "id",
282
- "name",
283
- "type",
284
- "framework",
285
- "version",
286
- "description",
287
- "instruction",
288
- "created_at",
289
- "updated_at",
290
- "metadata",
291
- "language_model_id",
292
- "agent_config",
293
- "tools",
294
- "agents",
295
- "mcps",
296
- "a2a_profile",
297
- "tool_configs",
298
- ]
299
-
300
- result_data = build_resource_result_data(full_agent, fields)
301
-
302
- # Handle missing instruction
303
- if result_data.get("instruction") in ["N/A", None, ""]:
304
- result_data["instruction"] = "-"
305
-
306
- # Format dates for better display
307
- for date_field in ["created_at", "updated_at"]:
308
- if result_data.get(date_field) and result_data[date_field] not in ["N/A", None]:
309
- result_data[date_field] = format_datetime(result_data[date_field])
310
-
311
- return result_data
312
-
313
-
314
- def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
315
- """Display full agent details using raw API data to preserve ALL fields."""
316
- if agent is None:
317
- handle_rich_output(ctx, markup_text(f"[{ERROR_STYLE}]❌ No agent provided[/]"))
318
- return
319
-
320
- # Try to fetch and format raw agent data first
321
- with spinner_context(
322
- ctx,
323
- "[bold blue]Loading agent details…[/bold blue]",
324
- console_override=console,
325
- ):
326
- formatted_data = _fetch_and_format_raw_agent_data(client, agent)
327
-
328
- if formatted_data:
329
- # Use raw API data - this preserves ALL fields including account_id
330
- panel_title = f"{ICON_AGENT} {formatted_data.get('name', 'Unknown')}"
331
- output_result(
332
- ctx,
333
- formatted_data,
334
- title=panel_title,
335
- )
336
- else:
337
- # Fall back to Pydantic model data if raw fetch fails
338
- handle_rich_output(
339
- ctx,
340
- markup_text(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]"),
341
- )
342
-
343
- with spinner_context(
344
- ctx,
345
- "[bold blue]Preparing fallback agent details…[/bold blue]",
346
- console_override=console,
347
- ):
348
- result_data = _format_fallback_agent_data(client, agent)
349
-
350
- # Display using output_result
351
- output_result(
352
- ctx,
353
- result_data,
354
- title="Agent Details",
355
- )
356
-
357
-
358
- @click.group(name="agents", no_args_is_help=True)
359
- def agents_group() -> None:
360
- """Agent management operations."""
361
- pass
362
-
363
-
364
- def _resolve_agent(
365
- ctx: Any,
366
- client: Any,
367
- ref: str,
368
- select: int | None = None,
369
- interface_preference: str = "fuzzy",
370
- ) -> Any | None:
371
- """Resolve agent reference (ID or name) with ambiguity handling.
372
-
373
- Args:
374
- ctx: Click context object for CLI operations.
375
- client: AIP client instance for API operations.
376
- ref: Agent reference (ID or name) to resolve.
377
- select: Pre-selected agent index for non-interactive mode.
378
- interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list.
379
-
380
- Returns:
381
- Resolved agent object or None if not found.
382
- """
383
- return resolve_resource_reference(
384
- ctx,
385
- client,
386
- ref,
387
- "agent",
388
- client.agents.get_agent_by_id,
389
- client.agents.find_agents,
390
- "Agent",
391
- select=select,
392
- interface_preference=interface_preference,
393
- )
394
-
395
-
396
- @agents_group.command(name="list")
397
- @click.option(
398
- "--simple", is_flag=True, help="Show simple table without interactive picker"
399
- )
400
- @click.option(
401
- "--type", "agent_type", help="Filter by agent type (config, code, a2a, langflow)"
402
- )
403
- @click.option(
404
- "--framework", help="Filter by framework (langchain, langgraph, google_adk)"
405
- )
406
- @click.option("--name", help="Filter by partial name match (case-insensitive)")
407
- @click.option("--version", help="Filter by exact version match")
408
- @click.option(
409
- "--sync-langflow",
410
- is_flag=True,
411
- help="Sync with LangFlow server before listing (only applies when filtering by langflow type)",
412
- )
413
- @output_flags()
414
- @click.pass_context
415
- def list_agents(
416
- ctx: Any,
417
- simple: bool,
418
- agent_type: str | None,
419
- framework: str | None,
420
- name: str | None,
421
- version: str | None,
422
- sync_langflow: bool,
423
- ) -> None:
424
- """List agents with optional filtering."""
425
- try:
426
- client = get_client(ctx)
427
- with spinner_context(
428
- ctx,
429
- "[bold blue]Fetching agents…[/bold blue]",
430
- console_override=console,
431
- ):
432
- agents = client.agents.list_agents(
433
- agent_type=agent_type,
434
- framework=framework,
435
- name=name,
436
- version=version,
437
- sync_langflow_agents=sync_langflow,
438
- )
439
-
440
- # Define table columns: (data_key, header, style, width)
441
- columns = [
442
- ("id", "ID", "dim", 36),
443
- ("name", "Name", ACCENT_STYLE, None),
444
- ("type", "Type", WARNING_STYLE, None),
445
- ("framework", "Framework", INFO, None),
446
- ("version", "Version", SUCCESS, None),
447
- ]
448
-
449
- # Transform function for safe attribute access
450
- def transform_agent(agent: Any) -> dict[str, Any]:
451
- row = coerce_to_row(agent, ["id", "name", "type", "framework", "version"])
452
- # Ensure id is always a string
453
- row["id"] = str(row["id"])
454
- return row
455
-
456
- # Use fuzzy picker for interactive agent selection and details (default behavior)
457
- # Skip if --simple flag is used, a name filter is applied, or non-rich output is requested
458
- ctx_obj = ctx.obj if isinstance(getattr(ctx, "obj", None), dict) else {}
459
- current_view = ctx_obj.get("view")
460
- interactive_enabled = (
461
- not simple
462
- and name is None
463
- and current_view not in {"json", "plain", "md"}
464
- and console.is_terminal
465
- and os.isatty(1)
466
- and len(agents) > 0
467
- )
468
-
469
- if interactive_enabled:
470
- picked_agent = _fuzzy_pick_for_resources(agents, "agent", "")
471
- if picked_agent:
472
- _display_agent_details(ctx, client, picked_agent)
473
- # Show run suggestions via centralized display helper
474
- handle_rich_output(ctx, display_agent_run_suggestions(picked_agent))
475
- return
476
-
477
- # Show simple table (either --simple flag or non-interactive)
478
- output_list(
479
- ctx,
480
- agents,
481
- f"{ICON_AGENT} Available Agents",
482
- columns,
483
- transform_agent,
484
- skip_picker=simple
485
- or any(
486
- param is not None for param in (agent_type, framework, name, version)
487
- ),
488
- use_pager=False,
489
- )
490
-
491
- except Exception as e:
492
- raise click.ClickException(str(e))
493
-
494
-
495
- @agents_group.command()
496
- @click.argument("agent_ref")
497
- @click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
498
- @click.option(
499
- "--export",
500
- type=click.Path(dir_okay=False, writable=True),
501
- help="Export complete agent configuration to file (format auto-detected from .json/.yaml extension)",
502
- )
503
- @output_flags()
504
- @click.pass_context
505
- def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> None:
506
- """Get agent details.
507
-
508
- Examples:
509
- aip agents get my-agent
510
- aip agents get my-agent --export agent.json # Exports complete configuration as JSON
511
- aip agents get my-agent --export agent.yaml # Exports complete configuration as YAML
512
- """
513
- try:
514
- client = get_client(ctx)
515
-
516
- # Resolve agent with ambiguity handling - use questionary interface for traditional UX
517
- agent = _resolve_agent(
518
- ctx, client, agent_ref, select, interface_preference="questionary"
519
- )
520
-
521
- # Handle export option
522
- if export:
523
- export_path = Path(export)
524
- # Auto-detect format from file extension
525
- detected_format = detect_export_format(export_path)
526
-
527
- # Always export comprehensive data - re-fetch agent with full details
528
- try:
529
- with spinner_context(
530
- ctx,
531
- "[bold blue]Fetching complete agent data…[/bold blue]",
532
- console_override=console,
533
- ):
534
- agent = client.agents.get_agent_by_id(agent.id)
535
- except Exception as e:
536
- handle_rich_output(
537
- ctx,
538
- markup_text(
539
- f"[{WARNING_STYLE}]⚠️ Could not fetch full agent details: {e}[/]"
540
- ),
541
- )
542
- handle_rich_output(
543
- ctx,
544
- markup_text(
545
- f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]"
546
- ),
547
- )
548
-
549
- export_resource_to_file(agent, export_path, detected_format)
550
- handle_rich_output(
551
- ctx,
552
- markup_text(
553
- f"[{SUCCESS_STYLE}]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/]"
554
- ),
555
- )
556
-
557
- # Display full agent details using the standardized helper
558
- _display_agent_details(ctx, client, agent)
559
-
560
- # Show run suggestions via centralized display helper
561
- handle_rich_output(ctx, display_agent_run_suggestions(agent))
562
-
563
- except Exception as e:
564
- raise click.ClickException(str(e))
565
-
566
-
567
- def _validate_run_input(input_option: str | None, input_text: str | None) -> str:
568
- """Validate and determine the final input text for agent run."""
569
- final_input_text = input_option if input_option else input_text
570
-
571
- if not final_input_text:
572
- raise click.ClickException(
573
- "Input text is required. Use either positional argument or --input option."
574
- )
575
-
576
- return final_input_text
577
-
578
-
579
- def _parse_chat_history(chat_history: str | None) -> list[dict[str, Any]] | None:
580
- """Parse chat history JSON if provided."""
581
- if not chat_history:
582
- return None
583
-
584
- try:
585
- return json.loads(chat_history)
586
- except json.JSONDecodeError:
587
- raise click.ClickException("Invalid JSON in chat history")
588
-
589
-
590
- def _setup_run_renderer(ctx: Any, save: str | None, verbose: bool) -> Any:
591
- """Set up renderer and working console for agent run."""
592
- tty_enabled = bool(get_ctx_value(ctx, "tty", True))
593
- return build_renderer(
594
- ctx,
595
- save_path=save,
596
- verbose=verbose,
597
- _tty_enabled=tty_enabled,
598
- )
599
-
600
-
601
- def _prepare_run_kwargs(
602
- agent: Any,
603
- final_input_text: str,
604
- files: list[str] | None,
605
- parsed_chat_history: list[dict[str, Any]] | None,
606
- renderer: Any,
607
- tty_enabled: bool,
608
- ) -> dict[str, Any]:
609
- """Prepare kwargs for agent run."""
610
- run_kwargs = {
611
- "agent_id": agent.id,
612
- "message": final_input_text,
613
- "files": list(files),
614
- "agent_name": agent.name,
615
- "tty": tty_enabled,
616
- }
617
-
618
- if parsed_chat_history:
619
- run_kwargs["chat_history"] = parsed_chat_history
620
-
621
- if renderer is not None:
622
- run_kwargs["renderer"] = renderer
623
-
624
- return run_kwargs
625
-
626
-
627
- def _handle_run_output(ctx: Any, result: Any, renderer: Any) -> None:
628
- """Handle output formatting for agent run results."""
629
- printed_by_renderer = bool(renderer)
630
- selected_view = get_ctx_value(ctx, "view", "rich")
631
-
632
- if not printed_by_renderer:
633
- if selected_view == "json":
634
- handle_json_output(ctx, {"output": result})
635
- elif selected_view == "md":
636
- click.echo(f"# Assistant\n\n{result}")
637
- elif selected_view == "plain":
638
- click.echo(result)
639
-
640
-
641
- def _save_run_transcript(save: str | None, result: Any, working_console: Any) -> None:
642
- """Save transcript to file if requested."""
643
- if not save:
644
- return
645
-
646
- ext = (save.rsplit(".", 1)[-1] or "").lower()
647
- if ext == "json":
648
- save_data = {
649
- "output": result or "",
650
- "full_debug_output": getattr(
651
- working_console, "get_captured_output", lambda: ""
652
- )(),
653
- "timestamp": "captured during agent execution",
654
- }
655
- content = json.dumps(save_data, indent=2)
656
- else:
657
- full_output = getattr(working_console, "get_captured_output", lambda: "")()
658
- if full_output:
659
- content = f"# Agent Debug Log\n\n{full_output}\n\n---\n\n## Final Result\n\n{result or ''}\n"
660
- else:
661
- content = f"# Assistant\n\n{result or ''}\n"
662
-
663
- with open(save, "w", encoding="utf-8") as f:
664
- f.write(content)
665
- print_markup(
666
- f"[{SUCCESS_STYLE}]Full debug output saved to: {save}[/]", console=console
667
- )
668
-
669
-
670
- @agents_group.command()
671
- @click.argument("agent_ref")
672
- @click.argument("input_text", required=False)
673
- @click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
674
- @click.option("--input", "input_option", help="Input text for the agent")
675
- @click.option("--chat-history", help="JSON string of chat history")
676
- @click.option(
677
- "--timeout",
678
- default=DEFAULT_AGENT_RUN_TIMEOUT,
679
- type=int,
680
- help="Agent execution timeout in seconds (default: 300s)",
681
- )
682
- @click.option(
683
- "--save",
684
- type=click.Path(dir_okay=False, writable=True),
685
- help="Save transcript to file (md or json)",
686
- )
687
- @click.option(
688
- "--file",
689
- "files",
690
- multiple=True,
691
- type=click.Path(exists=True),
692
- help="Attach file(s)",
693
- )
694
- @click.option(
695
- "--verbose/--no-verbose",
696
- default=False,
697
- help="Show detailed SSE events during streaming",
698
- )
699
- @output_flags()
700
- @click.pass_context
701
- def run(
702
- ctx: Any,
703
- agent_ref: str,
704
- select: int | None,
705
- input_text: str | None,
706
- input_option: str | None,
707
- chat_history: str | None,
708
- timeout: float | None,
709
- save: str | None,
710
- files: tuple[str, ...] | None,
711
- verbose: bool,
712
- ) -> None:
713
- """Run an agent with input text.
714
-
715
- Usage: aip agents run <agent_ref> <input_text> [OPTIONS]
716
-
717
- Examples:
718
- aip agents run my-agent "Hello world"
719
- aip agents run agent-123 "Process this data" --timeout 600
720
- aip agents run my-agent --input "Hello world" # Legacy style
721
- """
722
- final_input_text = _validate_run_input(input_option, input_text)
723
-
724
- if verbose:
725
- _emit_verbose_guidance(ctx)
726
- return
727
-
728
- try:
729
- client = get_client(ctx)
730
- agent = _resolve_agent(
731
- ctx, client, agent_ref, select, interface_preference="fuzzy"
732
- )
733
-
734
- parsed_chat_history = _parse_chat_history(chat_history)
735
- renderer, working_console = _setup_run_renderer(ctx, save, verbose)
736
-
737
- try:
738
- client.timeout = float(timeout)
739
- except Exception:
740
- pass
741
-
742
- run_kwargs = _prepare_run_kwargs(
743
- agent,
744
- final_input_text,
745
- files,
746
- parsed_chat_history,
747
- renderer,
748
- bool(get_ctx_value(ctx, "tty", True)),
749
- )
750
-
751
- result = client.agents.run_agent(**run_kwargs, timeout=timeout)
752
-
753
- slash_mode = _running_in_slash_mode(ctx)
754
- agent_id = str(_safe_agent_attribute(agent, "id") or "") or None
755
- agent_name = _safe_agent_attribute(agent, "name")
756
- model_hint = _get_agent_model_name(agent)
757
-
758
- transcript_context = store_transcript_for_session(
759
- ctx,
760
- renderer,
761
- final_result=result,
762
- agent_id=agent_id,
763
- agent_name=agent_name,
764
- model=model_hint,
765
- source="slash" if slash_mode else "cli",
766
- )
767
-
768
- _handle_run_output(ctx, result, renderer)
769
- _save_run_transcript(save, result, working_console)
770
- maybe_launch_post_run_viewer(
771
- ctx,
772
- transcript_context,
773
- console=console,
774
- slash_mode=slash_mode,
775
- )
776
-
777
- except AgentTimeoutError as e:
778
- error_msg = str(e)
779
- handle_json_output(ctx, error=Exception(error_msg))
780
- raise click.ClickException(error_msg)
781
- except Exception as e:
782
- _handle_command_exception(ctx, e)
783
-
784
-
785
- def _running_in_slash_mode(ctx: Any) -> bool:
786
- ctx_obj = getattr(ctx, "obj", None)
787
- return isinstance(ctx_obj, dict) and bool(ctx_obj.get("_slash_session"))
788
-
789
-
790
- def _emit_verbose_guidance(ctx: Any) -> None:
791
- if _running_in_slash_mode(ctx):
792
- message = (
793
- "[dim]Tip:[/] Verbose streaming has been retired in the command palette. Run the agent normally and open "
794
- "the post-run viewer (Ctrl+T) to inspect the transcript."
795
- )
796
- else:
797
- message = (
798
- "[dim]Tip:[/] `--verbose` is no longer supported. Re-run without the flag and toggle the post-run viewer "
799
- "(Ctrl+T) for detailed output."
800
- )
801
- handle_rich_output(ctx, markup_text(message))
802
-
803
-
804
- def _handle_import_file_logic(
805
- import_file: str,
806
- model: str | None,
807
- name: str,
808
- instruction: str,
809
- tools: tuple[str, ...],
810
- agents: tuple[str, ...],
811
- mcps: tuple[str, ...],
812
- timeout: float | None,
813
- ) -> dict[str, Any]:
814
- """Handle import file logic and merge with CLI args."""
815
- import_data = load_resource_from_file(Path(import_file), "agent")
816
- import_data = convert_export_to_import_format(import_data)
817
- import_data = normalize_agent_config_for_import(import_data, model)
818
-
819
- cli_args = {
820
- "name": name,
821
- "instruction": instruction,
822
- "model": model,
823
- "tools": tools or (),
824
- "agents": agents or (),
825
- "mcps": mcps or (),
826
- "timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
827
- }
828
-
829
- return merge_import_with_cli_args(import_data, cli_args)
830
-
831
-
832
- def _build_cli_args_data(
833
- name: str,
834
- instruction: str,
835
- model: str | None,
836
- tools: tuple[str, ...],
837
- agents: tuple[str, ...],
838
- mcps: tuple[str, ...],
839
- timeout: float | None,
840
- ) -> dict[str, Any]:
841
- """Build merged data from CLI arguments."""
842
- return {
843
- "name": name,
844
- "instruction": instruction,
845
- "model": model,
846
- "tools": tools or (),
847
- "agents": agents or (),
848
- "mcps": mcps or (),
849
- "timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
850
- }
851
-
852
-
853
- def _extract_and_validate_fields(
854
- merged_data: dict[str, Any],
855
- ) -> tuple[str, str, str | None, tuple, tuple, tuple, Any]:
856
- """Extract and validate required fields from merged data."""
857
- name = merged_data.get("name")
858
- instruction = merged_data.get("instruction")
859
- model = merged_data.get("model")
860
- tools = tuple(merged_data.get("tools", ()))
861
- agents = tuple(merged_data.get("agents", ()))
862
- mcps = tuple(merged_data.get("mcps", ()))
863
- timeout = merged_data.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
864
-
865
- # Validate required fields
866
- if not name:
867
- raise click.ClickException("Agent name is required (--name or --import)")
868
- if not instruction:
869
- raise click.ClickException(
870
- "Agent instruction is required (--instruction or --import)"
871
- )
872
-
873
- return name, instruction, model, tools, agents, mcps, timeout
874
-
875
-
876
- def _validate_and_coerce_fields(
877
- name: str, instruction: str, timeout: Any
878
- ) -> tuple[str, str, Any]:
879
- """Validate and coerce field values."""
880
- name = validate_agent_name(name)
881
- instruction = validate_agent_instruction(instruction)
882
- timeout = coerce_timeout(timeout)
883
- if timeout is not None:
884
- timeout = validate_timeout(timeout)
885
-
886
- return name, instruction, timeout
887
-
888
-
889
- def _resolve_resources(
890
- client: Any, tools: tuple, agents: tuple, mcps: tuple
891
- ) -> tuple[list, list, list]:
892
- """Resolve tool, agent, and MCP references."""
893
- resolved_tools = _resolve_resources_by_name(
894
- client, tools, "tool", client.find_tools, "Tool"
895
- )
896
- resolved_agents = _resolve_resources_by_name(
897
- client, agents, "agent", client.find_agents, "Agent"
898
- )
899
- resolved_mcps = _resolve_resources_by_name(
900
- client, mcps, "mcp", client.find_mcps, "MCP"
901
- )
902
-
903
- return resolved_tools, resolved_agents, resolved_mcps
904
-
905
-
906
- def _build_create_kwargs(
907
- name: str,
908
- instruction: str,
909
- resolved_tools: list,
910
- resolved_agents: list,
911
- resolved_mcps: list,
912
- timeout: Any,
913
- merged_data: dict[str, Any],
914
- model: str | None,
915
- import_file: str | None,
916
- ) -> dict[str, Any]:
917
- """Build create_agent kwargs with all necessary parameters."""
918
- create_kwargs = {
919
- "name": name,
920
- "instruction": instruction,
921
- "tools": resolved_tools or None,
922
- "agents": resolved_agents or None,
923
- "mcps": resolved_mcps or None,
924
- "timeout": timeout,
925
- }
926
-
927
- # Handle language model selection
928
- lm_selection_dict, should_strip_lm_identity = resolve_language_model_selection(
929
- merged_data, model
930
- )
931
- create_kwargs.update(lm_selection_dict)
932
-
933
- # Handle import file specific logic
934
- if import_file:
935
- _add_import_file_attributes(
936
- create_kwargs, merged_data, should_strip_lm_identity
937
- )
938
-
939
- return create_kwargs
940
-
941
-
942
- def _add_import_file_attributes(
943
- create_kwargs: dict[str, Any],
944
- merged_data: dict[str, Any],
945
- should_strip_lm_identity: bool,
946
- ) -> None:
947
- """Add import file specific attributes to create_kwargs."""
948
- agent_config_raw = merged_data.get("agent_config")
949
- if isinstance(agent_config_raw, dict):
950
- create_kwargs["agent_config"] = sanitize_agent_config(
951
- agent_config_raw, strip_lm_identity=should_strip_lm_identity
952
- )
953
-
954
- # Add other attributes from import data
955
- excluded_fields = {
956
- "name",
957
- "instruction",
958
- "model",
959
- "language_model_id",
960
- "tools",
961
- "agents",
962
- "timeout",
963
- "agent_config",
964
- "id",
965
- "created_at",
966
- "updated_at",
967
- "type",
968
- "framework",
969
- "version",
970
- "mcps",
971
- "a2a_profile",
972
- }
973
- for key, value in merged_data.items():
974
- if key not in excluded_fields and value is not None:
975
- create_kwargs[key] = value
976
-
977
-
978
- def _get_language_model_display_name(agent: Any, model: str | None) -> str:
979
- """Get display name for the language model."""
980
- lm_display = getattr(agent, "model", None)
981
- if not lm_display:
982
- cfg = getattr(agent, "agent_config", {}) or {}
983
- lm_display = (
984
- cfg.get("lm_name")
985
- or cfg.get("model")
986
- or model
987
- or f"{DEFAULT_MODEL} (backend default)"
988
- )
989
- return lm_display
990
-
991
-
992
- def _handle_successful_creation(ctx: Any, agent: Any, model: str | None) -> None:
993
- """Handle successful agent creation output."""
994
- handle_json_output(ctx, _prepare_agent_output(agent))
995
-
996
- lm_display = _get_language_model_display_name(agent, model)
997
-
998
- handle_rich_output(
999
- ctx,
1000
- display_creation_success(
1001
- "Agent",
1002
- agent.name,
1003
- agent.id,
1004
- Model=lm_display,
1005
- Type=getattr(agent, "type", "config"),
1006
- Framework=getattr(agent, "framework", "langchain"),
1007
- Version=getattr(agent, "version", "1.0"),
1008
- ),
1009
- )
1010
- handle_rich_output(ctx, display_agent_run_suggestions(agent))
1011
-
1012
-
1013
- def _handle_command_exception(ctx: Any, e: Exception) -> None:
1014
- """Handle exceptions during command execution with consistent error handling."""
1015
- if isinstance(e, click.ClickException):
1016
- if get_ctx_value(ctx, "view") == "json":
1017
- handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
1018
- raise
1019
-
1020
- handle_json_output(ctx, error=e)
1021
- if get_ctx_value(ctx, "view") != "json":
1022
- print_api_error(e)
1023
- raise click.ClickException(str(e))
1024
-
1025
-
1026
- def _handle_creation_exception(ctx: Any, e: Exception) -> None:
1027
- """Handle exceptions during agent creation."""
1028
- _handle_command_exception(ctx, e)
1029
-
1030
-
1031
- @agents_group.command()
1032
- @click.option("--name", help="Agent name")
1033
- @click.option("--instruction", help="Agent instruction (prompt)")
1034
- @click.option(
1035
- "--model",
1036
- help=f"Language model to use (e.g., {DEFAULT_MODEL}, default: {DEFAULT_MODEL})",
1037
- )
1038
- @click.option("--tools", multiple=True, help="Tool names or IDs to attach")
1039
- @click.option("--agents", multiple=True, help="Sub-agent names or IDs to attach")
1040
- @click.option("--mcps", multiple=True, help="MCP names or IDs to attach")
1041
- @click.option(
1042
- "--timeout",
1043
- default=DEFAULT_AGENT_RUN_TIMEOUT,
1044
- type=int,
1045
- help="Agent execution timeout in seconds (default: 300s)",
1046
- )
1047
- @click.option(
1048
- "--import",
1049
- "import_file",
1050
- type=click.Path(exists=True, dir_okay=False),
1051
- help="Import agent configuration from JSON file",
1052
- )
1053
- @output_flags()
1054
- @click.pass_context
1055
- def create(
1056
- ctx: Any,
1057
- name: str,
1058
- instruction: str,
1059
- model: str | None,
1060
- tools: tuple[str, ...] | None,
1061
- agents: tuple[str, ...] | None,
1062
- mcps: tuple[str, ...] | None,
1063
- timeout: float | None,
1064
- import_file: str | None,
1065
- ) -> None:
1066
- """Create a new agent.
1067
-
1068
- Examples:
1069
- aip agents create --name "My Agent" --instruction "You are a helpful assistant"
1070
- aip agents create --import agent.json
1071
- """
1072
- try:
1073
- client = get_client(ctx)
1074
-
1075
- # Handle import file or CLI args
1076
- if import_file:
1077
- merged_data = _handle_import_file_logic(
1078
- import_file, model, name, instruction, tools, agents, mcps, timeout
1079
- )
1080
- else:
1081
- merged_data = _build_cli_args_data(
1082
- name, instruction, model, tools, agents, mcps, timeout
1083
- )
1084
-
1085
- # Extract and validate fields
1086
- (
1087
- name,
1088
- instruction,
1089
- model,
1090
- tools,
1091
- agents,
1092
- mcps,
1093
- timeout,
1094
- ) = _extract_and_validate_fields(merged_data)
1095
- name, instruction, timeout = _validate_and_coerce_fields(
1096
- name, instruction, timeout
1097
- )
1098
-
1099
- # Resolve resources
1100
- resolved_tools, resolved_agents, resolved_mcps = _resolve_resources(
1101
- client, tools, agents, mcps
1102
- )
1103
-
1104
- # Build create kwargs
1105
- create_kwargs = _build_create_kwargs(
1106
- name,
1107
- instruction,
1108
- resolved_tools,
1109
- resolved_agents,
1110
- resolved_mcps,
1111
- timeout,
1112
- merged_data,
1113
- model,
1114
- import_file,
1115
- )
1116
-
1117
- # Create agent
1118
- agent = client.agents.create_agent(**create_kwargs)
1119
-
1120
- # Handle successful creation
1121
- _handle_successful_creation(ctx, agent, model)
1122
-
1123
- except Exception as e:
1124
- _handle_creation_exception(ctx, e)
1125
-
1126
-
1127
- def _get_agent_for_update(client: Any, agent_id: str) -> Any:
1128
- """Retrieve agent by ID for update operation."""
1129
- try:
1130
- return client.agents.get_agent_by_id(agent_id)
1131
- except Exception as e:
1132
- raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
1133
-
1134
-
1135
- def _handle_update_import_file(
1136
- import_file: str | None,
1137
- name: str | None,
1138
- instruction: str | None,
1139
- tools: tuple[str, ...] | None,
1140
- agents: tuple[str, ...] | None,
1141
- mcps: tuple[str, ...] | None,
1142
- timeout: float | None,
1143
- ) -> tuple[
1144
- Any | None,
1145
- str | None,
1146
- str | None,
1147
- tuple[str, ...] | None,
1148
- tuple[str, ...] | None,
1149
- tuple[str, ...] | None,
1150
- float | None,
1151
- ]:
1152
- """Handle import file processing for agent update."""
1153
- if not import_file:
1154
- return None, name, instruction, tools, agents, mcps, timeout
1155
-
1156
- import_data = load_resource_from_file(Path(import_file), "agent")
1157
- import_data = convert_export_to_import_format(import_data)
1158
- import_data = normalize_agent_config_for_import(import_data, None)
1159
-
1160
- cli_args = {
1161
- "name": name,
1162
- "instruction": instruction,
1163
- "tools": tools or (),
1164
- "agents": agents or (),
1165
- "mcps": mcps or (),
1166
- "timeout": timeout,
1167
- }
1168
-
1169
- merged_data = merge_import_with_cli_args(import_data, cli_args)
1170
-
1171
- return (
1172
- merged_data,
1173
- merged_data.get("name"),
1174
- merged_data.get("instruction"),
1175
- tuple(merged_data.get("tools", ())),
1176
- tuple(merged_data.get("agents", ())),
1177
- tuple(merged_data.get("mcps", ())),
1178
- coerce_timeout(merged_data.get("timeout")),
1179
- )
1180
-
1181
-
1182
- def _build_update_data(
1183
- name: str | None,
1184
- instruction: str | None,
1185
- tools: tuple[str, ...] | None,
1186
- agents: tuple[str, ...] | None,
1187
- mcps: tuple[str, ...] | None,
1188
- timeout: float | None,
1189
- ) -> dict[str, Any]:
1190
- """Build the update data dictionary from provided parameters."""
1191
- update_data = {}
1192
- if name is not None:
1193
- update_data["name"] = name
1194
- if instruction is not None:
1195
- update_data["instruction"] = instruction
1196
- if tools:
1197
- update_data["tools"] = list(tools)
1198
- if agents:
1199
- update_data["agents"] = list(agents)
1200
- if mcps:
1201
- update_data["mcps"] = list(mcps)
1202
- if timeout is not None:
1203
- update_data["timeout"] = timeout
1204
- return update_data
1205
-
1206
-
1207
- def _handle_update_import_config(
1208
- import_file: str | None, merged_data: dict[str, Any], update_data: dict[str, Any]
1209
- ) -> None:
1210
- """Handle agent config and additional attributes for import-based updates."""
1211
- if not import_file:
1212
- return
1213
-
1214
- lm_selection, should_strip_lm_identity = resolve_language_model_selection(
1215
- merged_data, None
1216
- )
1217
- update_data.update(lm_selection)
1218
-
1219
- raw_cfg = merged_data.get("agent_config") if isinstance(merged_data, dict) else None
1220
- if isinstance(raw_cfg, dict):
1221
- update_data["agent_config"] = sanitize_agent_config(
1222
- raw_cfg, strip_lm_identity=should_strip_lm_identity
1223
- )
1224
-
1225
- excluded_fields = {
1226
- "name",
1227
- "instruction",
1228
- "tools",
1229
- "agents",
1230
- "timeout",
1231
- "agent_config",
1232
- "language_model_id",
1233
- "id",
1234
- "created_at",
1235
- "updated_at",
1236
- "type",
1237
- "framework",
1238
- "version",
1239
- "a2a_profile",
1240
- }
1241
- for key, value in merged_data.items():
1242
- if key not in excluded_fields and value is not None:
1243
- update_data[key] = value
1244
-
1245
-
1246
- @agents_group.command()
1247
- @click.argument("agent_id")
1248
- @click.option("--name", help="New agent name")
1249
- @click.option("--instruction", help="New instruction")
1250
- @click.option("--tools", multiple=True, help="New tool names or IDs")
1251
- @click.option("--agents", multiple=True, help="New sub-agent names")
1252
- @click.option("--mcps", multiple=True, help="New MCP names or IDs")
1253
- @click.option("--timeout", type=int, help="New timeout value")
1254
- @click.option(
1255
- "--import",
1256
- "import_file",
1257
- type=click.Path(exists=True, dir_okay=False),
1258
- help="Import agent configuration from JSON file",
1259
- )
1260
- @output_flags()
1261
- @click.pass_context
1262
- def update(
1263
- ctx: Any,
1264
- agent_id: str,
1265
- name: str | None,
1266
- instruction: str | None,
1267
- tools: tuple[str, ...] | None,
1268
- agents: tuple[str, ...] | None,
1269
- mcps: tuple[str, ...] | None,
1270
- timeout: float | None,
1271
- import_file: str | None,
1272
- ) -> None:
1273
- """Update an existing agent.
1274
-
1275
- Examples:
1276
- aip agents update my-agent --instruction "New instruction"
1277
- aip agents update my-agent --import agent.json
1278
- """
1279
- try:
1280
- client = get_client(ctx)
1281
- agent = _get_agent_for_update(client, agent_id)
1282
-
1283
- # Handle import file processing
1284
- (
1285
- merged_data,
1286
- name,
1287
- instruction,
1288
- tools,
1289
- agents,
1290
- mcps,
1291
- timeout,
1292
- ) = _handle_update_import_file(
1293
- import_file, name, instruction, tools, agents, mcps, timeout
1294
- )
1295
-
1296
- update_data = _build_update_data(
1297
- name, instruction, tools, agents, mcps, timeout
1298
- )
1299
-
1300
- if merged_data:
1301
- _handle_update_import_config(import_file, merged_data, update_data)
1302
-
1303
- if not update_data:
1304
- raise click.ClickException("No update fields specified")
1305
-
1306
- updated_agent = client.agents.update_agent(agent.id, **update_data)
1307
-
1308
- handle_json_output(ctx, _prepare_agent_output(updated_agent))
1309
- handle_rich_output(ctx, display_update_success("Agent", updated_agent.name))
1310
- handle_rich_output(ctx, display_agent_run_suggestions(updated_agent))
1311
-
1312
- except click.ClickException:
1313
- # Handle JSON output for ClickExceptions if view is JSON
1314
- if get_ctx_value(ctx, "view") == "json":
1315
- handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
1316
- # Re-raise ClickExceptions without additional processing
1317
- raise
1318
- except Exception as e:
1319
- _handle_command_exception(ctx, e)
1320
-
1321
-
1322
- @agents_group.command()
1323
- @click.argument("agent_id")
1324
- @click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
1325
- @output_flags()
1326
- @click.pass_context
1327
- def delete(ctx: Any, agent_id: str, yes: bool) -> None:
1328
- """Delete an agent."""
1329
- try:
1330
- client = get_client(ctx)
1331
-
1332
- # Get agent by ID (no ambiguity handling needed)
1333
- try:
1334
- agent = client.agents.get_agent_by_id(agent_id)
1335
- except Exception as e:
1336
- raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
1337
-
1338
- # Confirm deletion when not forced
1339
- if not yes and not display_confirmation_prompt("Agent", agent.name):
1340
- return
1341
-
1342
- client.agents.delete_agent(agent.id)
1343
-
1344
- handle_json_output(
1345
- ctx,
1346
- {
1347
- "success": True,
1348
- "message": f"Agent '{agent.name}' deleted",
1349
- },
1350
- )
1351
- handle_rich_output(ctx, display_deletion_success("Agent", agent.name))
1352
-
1353
- except click.ClickException:
1354
- # Handle JSON output for ClickExceptions if view is JSON
1355
- if get_ctx_value(ctx, "view") == "json":
1356
- handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
1357
- # Re-raise ClickExceptions without additional processing
1358
- raise
1359
- except Exception as e:
1360
- _handle_command_exception(ctx, e)
1361
-
1362
-
1363
- @agents_group.command()
1364
- @click.option(
1365
- "--base-url",
1366
- help="Custom LangFlow server base URL (overrides LANGFLOW_BASE_URL env var)",
1367
- )
1368
- @click.option(
1369
- "--api-key", help="Custom LangFlow API key (overrides LANGFLOW_API_KEY env var)"
1370
- )
1371
- @output_flags()
1372
- @click.pass_context
1373
- def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1374
- """Sync agents with LangFlow server flows.
1375
-
1376
- This command fetches all flows from the configured LangFlow server and
1377
- creates/updates corresponding agents in the platform.
1378
-
1379
- The LangFlow server configuration can be provided via:
1380
- - Command options (--base-url, --api-key)
1381
- - Environment variables (LANGFLOW_BASE_URL, LANGFLOW_API_KEY)
1382
-
1383
- Examples:
1384
- aip agents sync-langflow
1385
- aip agents sync-langflow --base-url https://my-langflow.com --api-key my-key
1386
- """
1387
- try:
1388
- client = get_client(ctx)
1389
-
1390
- # Perform the sync
1391
- result = client.sync_langflow_agents(base_url=base_url, api_key=api_key)
1392
-
1393
- # Handle output format
1394
- handle_json_output(ctx, result)
1395
-
1396
- # Show success message for non-JSON output
1397
- if get_ctx_value(ctx, "view") != "json":
1398
- # Extract some useful info from the result
1399
- success_count = result.get("data", {}).get("created_count", 0) + result.get(
1400
- "data", {}
1401
- ).get("updated_count", 0)
1402
- total_count = result.get("data", {}).get("total_processed", 0)
1403
-
1404
- handle_rich_output(
1405
- ctx,
1406
- markup_text(
1407
- f"[{SUCCESS_STYLE}]✅ Successfully synced {success_count} LangFlow agents ({total_count} total processed)[/]"
1408
- ),
1409
- )
1410
-
1411
- except Exception as e:
1412
- _handle_command_exception(ctx, e)