glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 (161) hide show
  1. glaip_sdk/__init__.py +6 -3
  2. glaip_sdk/_version.py +12 -5
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1126 -0
  5. glaip_sdk/branding.py +79 -15
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +699 -0
  9. glaip_sdk/cli/commands/__init__.py +2 -2
  10. glaip_sdk/cli/commands/accounts.py +746 -0
  11. glaip_sdk/cli/commands/agents.py +503 -183
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +774 -137
  14. glaip_sdk/cli/commands/mcps.py +1124 -181
  15. glaip_sdk/cli/commands/models.py +25 -10
  16. glaip_sdk/cli/commands/tools.py +144 -92
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +61 -0
  19. glaip_sdk/cli/config.py +95 -0
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +150 -0
  22. glaip_sdk/cli/core/__init__.py +79 -0
  23. glaip_sdk/cli/core/context.py +124 -0
  24. glaip_sdk/cli/core/output.py +846 -0
  25. glaip_sdk/cli/core/prompting.py +649 -0
  26. glaip_sdk/cli/core/rendering.py +187 -0
  27. glaip_sdk/cli/display.py +143 -53
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +24 -18
  30. glaip_sdk/cli/main.py +420 -145
  31. glaip_sdk/cli/masking.py +136 -0
  32. glaip_sdk/cli/mcp_validators.py +287 -0
  33. glaip_sdk/cli/pager.py +266 -0
  34. glaip_sdk/cli/parsers/__init__.py +7 -0
  35. glaip_sdk/cli/parsers/json_input.py +177 -0
  36. glaip_sdk/cli/resolution.py +28 -21
  37. glaip_sdk/cli/rich_helpers.py +27 -0
  38. glaip_sdk/cli/slash/__init__.py +15 -0
  39. glaip_sdk/cli/slash/accounts_controller.py +500 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +282 -0
  42. glaip_sdk/cli/slash/prompt.py +245 -0
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +1679 -0
  45. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  46. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  47. glaip_sdk/cli/slash/tui/accounts_app.py +872 -0
  48. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  49. glaip_sdk/cli/slash/tui/loading.py +58 -0
  50. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  51. glaip_sdk/cli/transcript/__init__.py +31 -0
  52. glaip_sdk/cli/transcript/cache.py +536 -0
  53. glaip_sdk/cli/transcript/capture.py +329 -0
  54. glaip_sdk/cli/transcript/export.py +38 -0
  55. glaip_sdk/cli/transcript/history.py +815 -0
  56. glaip_sdk/cli/transcript/launcher.py +77 -0
  57. glaip_sdk/cli/transcript/viewer.py +372 -0
  58. glaip_sdk/cli/update_notifier.py +290 -0
  59. glaip_sdk/cli/utils.py +247 -1238
  60. glaip_sdk/cli/validators.py +16 -18
  61. glaip_sdk/client/__init__.py +2 -1
  62. glaip_sdk/client/_agent_payloads.py +520 -0
  63. glaip_sdk/client/agent_runs.py +147 -0
  64. glaip_sdk/client/agents.py +940 -574
  65. glaip_sdk/client/base.py +163 -48
  66. glaip_sdk/client/main.py +35 -12
  67. glaip_sdk/client/mcps.py +126 -18
  68. glaip_sdk/client/run_rendering.py +415 -0
  69. glaip_sdk/client/shared.py +21 -0
  70. glaip_sdk/client/tools.py +195 -37
  71. glaip_sdk/client/validators.py +20 -48
  72. glaip_sdk/config/constants.py +15 -5
  73. glaip_sdk/exceptions.py +16 -9
  74. glaip_sdk/icons.py +25 -0
  75. glaip_sdk/mcps/__init__.py +21 -0
  76. glaip_sdk/mcps/base.py +345 -0
  77. glaip_sdk/models/__init__.py +90 -0
  78. glaip_sdk/models/agent.py +47 -0
  79. glaip_sdk/models/agent_runs.py +116 -0
  80. glaip_sdk/models/common.py +42 -0
  81. glaip_sdk/models/mcp.py +33 -0
  82. glaip_sdk/models/tool.py +33 -0
  83. glaip_sdk/payload_schemas/__init__.py +7 -0
  84. glaip_sdk/payload_schemas/agent.py +85 -0
  85. glaip_sdk/registry/__init__.py +55 -0
  86. glaip_sdk/registry/agent.py +164 -0
  87. glaip_sdk/registry/base.py +139 -0
  88. glaip_sdk/registry/mcp.py +253 -0
  89. glaip_sdk/registry/tool.py +231 -0
  90. glaip_sdk/rich_components.py +98 -2
  91. glaip_sdk/runner/__init__.py +59 -0
  92. glaip_sdk/runner/base.py +84 -0
  93. glaip_sdk/runner/deps.py +115 -0
  94. glaip_sdk/runner/langgraph.py +597 -0
  95. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  96. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  97. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +158 -0
  98. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  99. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  100. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  101. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +177 -0
  102. glaip_sdk/tools/__init__.py +22 -0
  103. glaip_sdk/tools/base.py +435 -0
  104. glaip_sdk/utils/__init__.py +59 -13
  105. glaip_sdk/utils/a2a/__init__.py +34 -0
  106. glaip_sdk/utils/a2a/event_processor.py +188 -0
  107. glaip_sdk/utils/agent_config.py +53 -40
  108. glaip_sdk/utils/bundler.py +267 -0
  109. glaip_sdk/utils/client.py +111 -0
  110. glaip_sdk/utils/client_utils.py +58 -26
  111. glaip_sdk/utils/datetime_helpers.py +58 -0
  112. glaip_sdk/utils/discovery.py +78 -0
  113. glaip_sdk/utils/display.py +65 -32
  114. glaip_sdk/utils/export.py +143 -0
  115. glaip_sdk/utils/general.py +1 -36
  116. glaip_sdk/utils/import_export.py +20 -25
  117. glaip_sdk/utils/import_resolver.py +492 -0
  118. glaip_sdk/utils/instructions.py +101 -0
  119. glaip_sdk/utils/rendering/__init__.py +115 -1
  120. glaip_sdk/utils/rendering/formatting.py +85 -43
  121. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  122. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
  123. glaip_sdk/utils/rendering/layout/progress.py +202 -0
  124. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  125. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  126. glaip_sdk/utils/rendering/models.py +39 -7
  127. glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
  128. glaip_sdk/utils/rendering/renderer/base.py +672 -759
  129. glaip_sdk/utils/rendering/renderer/config.py +4 -10
  130. glaip_sdk/utils/rendering/renderer/debug.py +75 -22
  131. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  132. glaip_sdk/utils/rendering/renderer/stream.py +13 -54
  133. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  134. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  135. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  136. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  137. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  138. glaip_sdk/utils/rendering/state.py +204 -0
  139. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  140. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  141. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  142. glaip_sdk/utils/rendering/steps/format.py +176 -0
  143. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  144. glaip_sdk/utils/rendering/timing.py +36 -0
  145. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  146. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  147. glaip_sdk/utils/resource_refs.py +29 -26
  148. glaip_sdk/utils/runtime_config.py +422 -0
  149. glaip_sdk/utils/serialization.py +184 -51
  150. glaip_sdk/utils/sync.py +142 -0
  151. glaip_sdk/utils/tool_detection.py +33 -0
  152. glaip_sdk/utils/validation.py +21 -30
  153. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
  154. glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
  155. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
  156. glaip_sdk/models.py +0 -250
  157. glaip_sdk/utils/rendering/renderer/progress.py +0 -118
  158. glaip_sdk/utils/rendering/steps.py +0 -232
  159. glaip_sdk/utils/rich_utils.py +0 -29
  160. glaip_sdk-0.0.7.dist-info/RECORD +0 -55
  161. {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
@@ -4,15 +4,27 @@ Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  import json
8
10
  import os
11
+ from collections.abc import Mapping
12
+ from copy import deepcopy
9
13
  from pathlib import Path
10
14
  from typing import Any
11
15
 
12
16
  import click
13
17
  from rich.console import Console
14
- from rich.text import Text
15
18
 
19
+ from glaip_sdk.branding import (
20
+ ACCENT_STYLE,
21
+ ERROR_STYLE,
22
+ HINT_PREFIX_STYLE,
23
+ INFO,
24
+ SUCCESS,
25
+ SUCCESS_STYLE,
26
+ WARNING_STYLE,
27
+ )
16
28
  from glaip_sdk.cli.agent_config import (
17
29
  merge_agent_config_with_cli_args as merge_import_with_cli_args,
18
30
  )
@@ -22,6 +34,8 @@ from glaip_sdk.cli.agent_config import (
22
34
  from glaip_sdk.cli.agent_config import (
23
35
  sanitize_agent_config_for_cli as sanitize_agent_config,
24
36
  )
37
+ from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
38
+ from glaip_sdk.cli.context import get_ctx_value, output_flags
25
39
  from glaip_sdk.cli.display import (
26
40
  build_resource_result_data,
27
41
  display_agent_run_suggestions,
@@ -33,9 +47,7 @@ from glaip_sdk.cli.display import (
33
47
  handle_rich_output,
34
48
  print_api_error,
35
49
  )
36
- from glaip_sdk.cli.io import (
37
- export_resource_to_file_with_validation as export_resource_to_file,
38
- )
50
+ from glaip_sdk.cli.hints import in_slash_mode
39
51
  from glaip_sdk.cli.io import (
40
52
  fetch_raw_resource_details,
41
53
  )
@@ -43,16 +55,21 @@ from glaip_sdk.cli.io import (
43
55
  load_resource_from_file_with_validation as load_resource_from_file,
44
56
  )
45
57
  from glaip_sdk.cli.resolution import resolve_resource_reference
58
+ from glaip_sdk.cli.rich_helpers import markup_text, print_markup
59
+ from glaip_sdk.cli.transcript import (
60
+ maybe_launch_post_run_viewer,
61
+ store_transcript_for_session,
62
+ )
46
63
  from glaip_sdk.cli.utils import (
47
64
  _fuzzy_pick_for_resources,
48
65
  build_renderer,
49
66
  coerce_to_row,
50
- detect_export_format,
51
67
  get_client,
52
- get_ctx_value,
53
- output_flags,
68
+ handle_resource_export,
54
69
  output_list,
55
70
  output_result,
71
+ spinner_context,
72
+ with_client_and_spinner,
56
73
  )
57
74
  from glaip_sdk.cli.validators import (
58
75
  validate_agent_instruction_cli as validate_agent_instruction,
@@ -63,11 +80,13 @@ from glaip_sdk.cli.validators import (
63
80
  from glaip_sdk.cli.validators import (
64
81
  validate_timeout_cli as validate_timeout,
65
82
  )
66
- from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
83
+ from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS, DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
67
84
  from glaip_sdk.exceptions import AgentTimeoutError
85
+ from glaip_sdk.icons import ICON_AGENT
68
86
  from glaip_sdk.utils import format_datetime, is_uuid
69
87
  from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
70
88
  from glaip_sdk.utils.import_export import convert_export_to_import_format
89
+ from glaip_sdk.utils.rendering.renderer.toggle import TranscriptToggleController
71
90
  from glaip_sdk.utils.validation import coerce_timeout
72
91
 
73
92
  console = Console()
@@ -75,6 +94,85 @@ console = Console()
75
94
  # Error message constants
76
95
  AGENT_NOT_FOUND_ERROR = "Agent not found"
77
96
 
97
+ # Instruction preview controls
98
+
99
+
100
+ def _safe_agent_attribute(agent: Any, name: str) -> Any:
101
+ """Return attribute value for ``name`` while filtering Mock sentinels."""
102
+ try:
103
+ value = getattr(agent, name)
104
+ except Exception:
105
+ return None
106
+
107
+ if hasattr(value, "_mock_name"):
108
+ return None
109
+ return value
110
+
111
+
112
+ def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
113
+ """Convert a mapping-like candidate to a plain dict when possible."""
114
+ if candidate is None:
115
+ return None
116
+ if isinstance(candidate, Mapping):
117
+ return dict(candidate)
118
+ return None
119
+
120
+
121
+ def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
122
+ """Attempt to call the named method and coerce its output to a dict."""
123
+ method = getattr(agent, method_name, None)
124
+ if not callable(method):
125
+ return None
126
+ try:
127
+ candidate = method()
128
+ except Exception:
129
+ return None
130
+ return _coerce_mapping_candidate(candidate)
131
+
132
+
133
+ def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
134
+ """Try standard serialisation helpers to produce a mapping."""
135
+ for attr in ("model_dump", "dict", "to_dict"):
136
+ mapping = _call_agent_method(agent, attr)
137
+ if mapping is not None:
138
+ return mapping
139
+ return None
140
+
141
+
142
+ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
143
+ """Construct a minimal mapping from well-known agent attributes."""
144
+ fallback_fields = (
145
+ "id",
146
+ "name",
147
+ "instruction",
148
+ "description",
149
+ "model",
150
+ "agent_config",
151
+ *[field for field in AGENT_CONFIG_FIELDS if field not in ("name", "instruction", "model")],
152
+ "tool_configs",
153
+ )
154
+
155
+ fallback: dict[str, Any] = {}
156
+ for field in fallback_fields:
157
+ value = _safe_agent_attribute(agent, field)
158
+ if value is not None:
159
+ fallback[field] = value
160
+
161
+ return fallback or {"name": str(agent)}
162
+
163
+
164
+ def _prepare_agent_output(agent: Any) -> dict[str, Any]:
165
+ """Build a JSON-serialisable mapping for CLI output."""
166
+ method_mapping = _coerce_agent_via_methods(agent)
167
+ if method_mapping is not None:
168
+ return method_mapping
169
+
170
+ intrinsic = _coerce_mapping_candidate(agent)
171
+ if intrinsic is not None:
172
+ return intrinsic
173
+
174
+ return _build_fallback_agent_mapping(agent)
175
+
78
176
 
79
177
  def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
80
178
  """Fetch full agent details by ID to ensure all fields are populated."""
@@ -88,17 +186,41 @@ def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
88
186
  return agent
89
187
 
90
188
 
189
+ def _normalise_model_name(value: Any) -> str | None:
190
+ """Return a cleaned model name or None when not usable."""
191
+ if value is None:
192
+ return None
193
+ if isinstance(value, str):
194
+ cleaned = value.strip()
195
+ return cleaned or None
196
+ if isinstance(value, bool):
197
+ return None
198
+ return str(value)
199
+
200
+
201
+ def _model_from_config(agent: Any) -> str | None:
202
+ """Extract a usable model name from an agent's configuration mapping."""
203
+ config = getattr(agent, "agent_config", None)
204
+ if not config or not isinstance(config, dict):
205
+ return None
206
+
207
+ for key in ("lm_name", "model"):
208
+ normalised = _normalise_model_name(config.get(key))
209
+ if normalised:
210
+ return normalised
211
+ return None
212
+
213
+
91
214
  def _get_agent_model_name(agent: Any) -> str | None:
92
215
  """Extract model name from agent configuration."""
93
- # Try different possible locations for model name
94
- if hasattr(agent, "agent_config") and agent.agent_config:
95
- if isinstance(agent.agent_config, dict):
96
- return agent.agent_config.get("lm_name") or agent.agent_config.get("model")
216
+ config_model = _model_from_config(agent)
217
+ if config_model:
218
+ return config_model
97
219
 
98
- if hasattr(agent, "model") and agent.model:
99
- return agent.model
220
+ normalised_attr = _normalise_model_name(getattr(agent, "model", None))
221
+ if normalised_attr:
222
+ return normalised_attr
100
223
 
101
- # Default fallback
102
224
  return DEFAULT_MODEL
103
225
 
104
226
 
@@ -118,7 +240,7 @@ def _resolve_resources_by_name(
118
240
  List of resolved resource IDs
119
241
  """
120
242
  out = []
121
- for ref in list(items or ()):
243
+ for ref in items or ():
122
244
  if is_uuid(ref):
123
245
  out.append(ref)
124
246
  continue
@@ -127,9 +249,7 @@ def _resolve_resources_by_name(
127
249
  if not matches:
128
250
  raise click.ClickException(f"{label} not found: {ref}")
129
251
  if len(matches) > 1:
130
- raise click.ClickException(
131
- f"Multiple {resource_type}s named '{ref}'. Use ID instead."
132
- )
252
+ raise click.ClickException(f"Multiple {resource_type}s named '{ref}'. Use ID instead.")
133
253
  out.append(str(matches[0].id))
134
254
  return out
135
255
 
@@ -171,11 +291,11 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
171
291
  "metadata",
172
292
  "language_model_id",
173
293
  "agent_config",
174
- "tool_configs",
175
294
  "tools",
176
295
  "agents",
177
296
  "mcps",
178
297
  "a2a_profile",
298
+ "tool_configs",
179
299
  ]
180
300
 
181
301
  result_data = build_resource_result_data(full_agent, fields)
@@ -192,40 +312,143 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
192
312
  return result_data
193
313
 
194
314
 
195
- def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
315
+ def _clamp_instruction_preview_limit(limit: int | None) -> int:
316
+ """Normalise preview limit; 0 disables trimming."""
317
+ default = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
318
+ if limit is None: # pragma: no cover
319
+ return default
320
+ try:
321
+ limit_value = int(limit)
322
+ except (TypeError, ValueError): # pragma: no cover - defensive parsing
323
+ return default
324
+
325
+ if limit_value <= 0:
326
+ return 0
327
+
328
+ return limit_value
329
+
330
+
331
+ def _build_instruction_preview(value: Any, limit: int) -> tuple[Any, bool]:
332
+ """Return a trimmed preview for long instruction strings."""
333
+ if not isinstance(value, str) or limit <= 0: # pragma: no cover
334
+ return value, False
335
+
336
+ if len(value) <= limit:
337
+ return value, False
338
+
339
+ trimmed_value = value[:limit].rstrip()
340
+ preview = f"{trimmed_value}\n\n... (preview trimmed)"
341
+ return preview, True
342
+
343
+
344
+ def _prepare_agent_details_payload(
345
+ data: dict[str, Any],
346
+ *,
347
+ instruction_preview_limit: int,
348
+ ) -> tuple[dict[str, Any], bool]:
349
+ """Return payload ready for rendering plus trim indicator."""
350
+ payload = deepcopy(data)
351
+ trimmed = False
352
+ if instruction_preview_limit > 0:
353
+ preview, trimmed = _build_instruction_preview(payload.get("instruction"), instruction_preview_limit)
354
+ if trimmed:
355
+ payload["instruction"] = preview
356
+ return payload, trimmed
357
+
358
+
359
+ def _show_instruction_trim_hint(
360
+ ctx: Any,
361
+ *,
362
+ trimmed: bool,
363
+ preview_limit: int,
364
+ ) -> None:
365
+ """Render hint describing how to expand or collapse the instruction preview."""
366
+ if not trimmed or preview_limit <= 0:
367
+ return
368
+
369
+ view = get_ctx_value(ctx, "view", "rich") if ctx is not None else "rich"
370
+ if view != "rich": # pragma: no cover - non-rich view handling
371
+ return
372
+
373
+ suffix = f"[dim](preview: {preview_limit:,} chars)[/]"
374
+ if in_slash_mode(ctx):
375
+ console.print(
376
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] Use '/details' again to toggle between trimmed and full prompts {suffix}"
377
+ )
378
+ return
379
+
380
+ console.print( # pragma: no cover - fallback hint rendering
381
+ f"[{HINT_PREFIX_STYLE}]Tip:[/] Run 'aip agents get <agent> --instruction-preview <n>' "
382
+ f"to control prompt preview length {suffix}"
383
+ )
384
+
385
+
386
+ def _display_agent_details(
387
+ ctx: Any,
388
+ client: Any,
389
+ agent: Any,
390
+ *,
391
+ instruction_preview_limit: int | None = None,
392
+ ) -> None:
196
393
  """Display full agent details using raw API data to preserve ALL fields."""
197
394
  if agent is None:
198
- handle_rich_output(ctx, Text("[red]❌ No agent provided[/red]"))
395
+ handle_rich_output(ctx, markup_text(f"[{ERROR_STYLE}]❌ No agent provided[/]"))
199
396
  return
200
397
 
398
+ preview_limit = _clamp_instruction_preview_limit(instruction_preview_limit)
399
+ trimmed_instruction = False
400
+
201
401
  # Try to fetch and format raw agent data first
202
- formatted_data = _fetch_and_format_raw_agent_data(client, agent)
402
+ with spinner_context(
403
+ ctx,
404
+ "[bold blue]Loading agent details…[/bold blue]",
405
+ console_override=console,
406
+ ):
407
+ formatted_data = _fetch_and_format_raw_agent_data(client, agent)
203
408
 
204
409
  if formatted_data:
205
410
  # Use raw API data - this preserves ALL fields including account_id
206
- panel_title = f"🤖 {formatted_data.get('name', 'Unknown')}"
411
+ panel_title = f"{ICON_AGENT} {formatted_data.get('name', 'Unknown')}"
412
+ payload, trimmed_instruction = _prepare_agent_details_payload(
413
+ formatted_data,
414
+ instruction_preview_limit=preview_limit,
415
+ )
207
416
  output_result(
208
417
  ctx,
209
- formatted_data,
210
- title="Agent Details",
211
- panel_title=panel_title,
418
+ payload,
419
+ title=panel_title,
212
420
  )
213
421
  else:
214
422
  # Fall back to Pydantic model data if raw fetch fails
215
423
  handle_rich_output(
216
- ctx, Text("[yellow]Falling back to Pydantic model data[/yellow]")
424
+ ctx,
425
+ markup_text(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]"),
217
426
  )
218
427
 
219
- result_data = _format_fallback_agent_data(client, agent)
428
+ with spinner_context(
429
+ ctx,
430
+ "[bold blue]Preparing fallback agent details…[/bold blue]",
431
+ console_override=console,
432
+ ):
433
+ result_data = _format_fallback_agent_data(client, agent)
220
434
 
221
435
  # Display using output_result
436
+ payload, trimmed_instruction = _prepare_agent_details_payload(
437
+ result_data,
438
+ instruction_preview_limit=preview_limit,
439
+ )
222
440
  output_result(
223
441
  ctx,
224
- result_data,
442
+ payload,
225
443
  title="Agent Details",
226
- panel_title=f"🤖 {result_data.get('name', 'Unknown')}",
227
444
  )
228
445
 
446
+ _show_instruction_trim_hint(
447
+ ctx,
448
+ trimmed=trimmed_instruction,
449
+ preview_limit=preview_limit,
450
+ )
451
+
229
452
 
230
453
  @click.group(name="agents", no_args_is_help=True)
231
454
  def agents_group() -> None:
@@ -240,34 +463,47 @@ def _resolve_agent(
240
463
  select: int | None = None,
241
464
  interface_preference: str = "fuzzy",
242
465
  ) -> Any | None:
243
- """Resolve agent reference (ID or name) with ambiguity handling.
466
+ """Resolve an agent by ID or name, supporting fuzzy and questionary interfaces.
467
+
468
+ This function provides agent-specific resolution with flexible UI options.
469
+ It wraps resolve_resource_reference with agent-specific configuration, allowing
470
+ users to choose between fuzzy search and traditional questionary selection.
244
471
 
245
472
  Args:
246
- interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list
473
+ ctx: Click context for CLI command execution.
474
+ client: AIP SDK client instance.
475
+ ref: Agent identifier (UUID or name string).
476
+ select: Pre-selected index for non-interactive resolution (1-based).
477
+ interface_preference: UI preference - "fuzzy" for search or "questionary" for list.
478
+
479
+ Returns:
480
+ Agent object when found, None when resolution fails.
247
481
  """
482
+ # Configure agent-specific resolution parameters
483
+ resolution_config = {
484
+ "resource_type": "agent",
485
+ "get_by_id": client.agents.get_agent_by_id,
486
+ "find_by_name": client.agents.find_agents,
487
+ "label": "Agent",
488
+ }
489
+ # Use agent-specific resolution with flexible interface preference
248
490
  return resolve_resource_reference(
249
491
  ctx,
250
492
  client,
251
493
  ref,
252
- "agent",
253
- client.agents.get_agent_by_id,
254
- client.agents.find_agents,
255
- "Agent",
494
+ resolution_config["resource_type"],
495
+ resolution_config["get_by_id"],
496
+ resolution_config["find_by_name"],
497
+ resolution_config["label"],
256
498
  select=select,
257
499
  interface_preference=interface_preference,
258
500
  )
259
501
 
260
502
 
261
503
  @agents_group.command(name="list")
262
- @click.option(
263
- "--simple", is_flag=True, help="Show simple table without interactive picker"
264
- )
265
- @click.option(
266
- "--type", "agent_type", help="Filter by agent type (config, code, a2a, langflow)"
267
- )
268
- @click.option(
269
- "--framework", help="Filter by framework (langchain, langgraph, google_adk)"
270
- )
504
+ @click.option("--simple", is_flag=True, help="Show simple table without interactive picker")
505
+ @click.option("--type", "agent_type", help="Filter by agent type (config, code, a2a, langflow)")
506
+ @click.option("--framework", help="Filter by framework (langchain, langgraph, google_adk)")
271
507
  @click.option("--name", help="Filter by partial name match (case-insensitive)")
272
508
  @click.option("--version", help="Filter by exact version match")
273
509
  @click.option(
@@ -288,34 +524,62 @@ def list_agents(
288
524
  ) -> None:
289
525
  """List agents with optional filtering."""
290
526
  try:
291
- client = get_client(ctx)
292
- agents = client.agents.list_agents(
293
- agent_type=agent_type,
294
- framework=framework,
295
- name=name,
296
- version=version,
297
- sync_langflow_agents=sync_langflow,
298
- )
527
+ with with_client_and_spinner(
528
+ ctx,
529
+ "[bold blue]Fetching agents…[/bold blue]",
530
+ console_override=console,
531
+ ) as client:
532
+ # Query agents with specified filters
533
+ filter_params = {
534
+ "agent_type": agent_type,
535
+ "framework": framework,
536
+ "name": name,
537
+ "version": version,
538
+ "sync_langflow_agents": sync_langflow,
539
+ }
540
+ agents = client.agents.list_agents(**filter_params)
299
541
 
300
542
  # Define table columns: (data_key, header, style, width)
301
543
  columns = [
302
544
  ("id", "ID", "dim", 36),
303
- ("name", "Name", "cyan", None),
304
- ("type", "Type", "yellow", None),
305
- ("framework", "Framework", "blue", None),
306
- ("version", "Version", "green", None),
545
+ ("name", "Name", ACCENT_STYLE, None),
546
+ ("type", "Type", WARNING_STYLE, None),
547
+ ("framework", "Framework", INFO, None),
548
+ ("version", "Version", SUCCESS, None),
307
549
  ]
308
550
 
309
551
  # Transform function for safe attribute access
310
552
  def transform_agent(agent: Any) -> dict[str, Any]:
553
+ """Transform an agent object to a display row dictionary.
554
+
555
+ Args:
556
+ agent: Agent object to transform.
557
+
558
+ Returns:
559
+ Dictionary with id, name, type, framework, and version fields.
560
+ """
311
561
  row = coerce_to_row(agent, ["id", "name", "type", "framework", "version"])
312
562
  # Ensure id is always a string
313
563
  row["id"] = str(row["id"])
314
564
  return row
315
565
 
316
566
  # Use fuzzy picker for interactive agent selection and details (default behavior)
317
- # Skip if --simple flag is used
318
- if not simple and console.is_terminal and os.isatty(1) and len(agents) > 0:
567
+ # Skip if --simple flag is used, a name filter is applied, or non-rich output is requested
568
+ ctx_obj = ctx.obj if isinstance(getattr(ctx, "obj", None), dict) else {}
569
+ current_view = ctx_obj.get("view")
570
+ interactive_enabled = (
571
+ not simple
572
+ and name is None
573
+ and current_view not in {"json", "plain", "md"}
574
+ and console.is_terminal
575
+ and os.isatty(1)
576
+ and len(agents) > 0
577
+ )
578
+
579
+ # Track picker attempt so the fallback table doesn't re-open the palette
580
+ picker_attempted = False
581
+ if interactive_enabled:
582
+ picker_attempted = True
319
583
  picked_agent = _fuzzy_pick_for_resources(agents, "agent", "")
320
584
  if picked_agent:
321
585
  _display_agent_details(ctx, client, picked_agent)
@@ -324,10 +588,23 @@ def list_agents(
324
588
  return
325
589
 
326
590
  # Show simple table (either --simple flag or non-interactive)
327
- output_list(ctx, agents, "🤖 Available Agents", columns, transform_agent)
591
+ output_list(
592
+ ctx,
593
+ agents,
594
+ f"{ICON_AGENT} Available Agents",
595
+ columns,
596
+ transform_agent,
597
+ skip_picker=(
598
+ not interactive_enabled
599
+ or picker_attempted
600
+ or simple
601
+ or any(param is not None for param in (agent_type, framework, name, version))
602
+ ),
603
+ use_pager=False,
604
+ )
328
605
 
329
606
  except Exception as e:
330
- raise click.ClickException(str(e))
607
+ raise click.ClickException(str(e)) from e
331
608
 
332
609
 
333
610
  @agents_group.command()
@@ -338,60 +615,66 @@ def list_agents(
338
615
  type=click.Path(dir_okay=False, writable=True),
339
616
  help="Export complete agent configuration to file (format auto-detected from .json/.yaml extension)",
340
617
  )
618
+ @click.option(
619
+ "--instruction-preview",
620
+ type=int,
621
+ default=0,
622
+ show_default=True,
623
+ help="Instruction preview length when printing instructions (0 shows full prompt).",
624
+ )
341
625
  @output_flags()
342
626
  @click.pass_context
343
- def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> None:
344
- """Get agent details.
627
+ def get(
628
+ ctx: Any,
629
+ agent_ref: str,
630
+ select: int | None,
631
+ export: str | None,
632
+ instruction_preview: int,
633
+ ) -> None:
634
+ r"""Get agent details.
345
635
 
636
+ \b
346
637
  Examples:
347
638
  aip agents get my-agent
348
639
  aip agents get my-agent --export agent.json # Exports complete configuration as JSON
349
640
  aip agents get my-agent --export agent.yaml # Exports complete configuration as YAML
350
641
  """
351
642
  try:
352
- client = get_client(ctx)
643
+ # Initialize API client for agent retrieval
644
+ api_client = get_client(ctx)
353
645
 
354
- # Resolve agent with ambiguity handling - use questionary interface for traditional UX
355
- agent = _resolve_agent(
356
- ctx, client, agent_ref, select, interface_preference="questionary"
357
- )
646
+ # Resolve agent reference using questionary interface for better UX
647
+ agent = _resolve_agent(ctx, api_client, agent_ref, select, interface_preference="questionary")
358
648
 
359
- # Handle export option
649
+ if not agent:
650
+ raise click.ClickException(f"Agent '{agent_ref}' not found")
651
+
652
+ # Handle export option if requested
360
653
  if export:
361
- export_path = Path(export)
362
- # Auto-detect format from file extension
363
- detected_format = detect_export_format(export_path)
364
-
365
- # Always export comprehensive data - re-fetch agent with full details
366
- try:
367
- agent = client.agents.get_agent_by_id(agent.id)
368
- except Exception as e:
369
- handle_rich_output(
370
- ctx,
371
- Text(
372
- f"[yellow]⚠️ Could not fetch full agent details: {e}[/yellow]"
373
- ),
374
- )
375
- handle_rich_output(
376
- ctx, Text("[yellow]⚠️ Proceeding with available data[/yellow]")
377
- )
378
-
379
- export_resource_to_file(agent, export_path, detected_format)
380
- handle_rich_output(
654
+ handle_resource_export(
381
655
  ctx,
382
- Text(
383
- f"[green]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/green]"
384
- ),
656
+ agent,
657
+ Path(export),
658
+ resource_type="agent",
659
+ get_by_id_func=api_client.agents.get_agent_by_id,
660
+ console_override=console,
385
661
  )
386
662
 
387
663
  # Display full agent details using the standardized helper
388
- _display_agent_details(ctx, client, agent)
664
+ _display_agent_details(
665
+ ctx,
666
+ api_client,
667
+ agent,
668
+ instruction_preview_limit=instruction_preview,
669
+ )
389
670
 
390
671
  # Show run suggestions via centralized display helper
391
672
  handle_rich_output(ctx, display_agent_run_suggestions(agent))
392
673
 
674
+ except click.ClickException:
675
+ raise
393
676
  except Exception as e:
394
- raise click.ClickException(str(e))
677
+ raise click.ClickException(str(e)) from e
395
678
 
396
679
 
397
680
  def _validate_run_input(input_option: str | None, input_text: str | None) -> str:
@@ -399,9 +682,7 @@ def _validate_run_input(input_option: str | None, input_text: str | None) -> str
399
682
  final_input_text = input_option if input_option else input_text
400
683
 
401
684
  if not final_input_text:
402
- raise click.ClickException(
403
- "Input text is required. Use either positional argument or --input option."
404
- )
685
+ raise click.ClickException("Input text is required. Use either positional argument or --input option.")
405
686
 
406
687
  return final_input_text
407
688
 
@@ -413,8 +694,8 @@ def _parse_chat_history(chat_history: str | None) -> list[dict[str, Any]] | None
413
694
 
414
695
  try:
415
696
  return json.loads(chat_history)
416
- except json.JSONDecodeError:
417
- raise click.ClickException("Invalid JSON in chat history")
697
+ except json.JSONDecodeError as err:
698
+ raise click.ClickException("Invalid JSON in chat history") from err
418
699
 
419
700
 
420
701
  def _setup_run_renderer(ctx: Any, save: str | None, verbose: bool) -> Any:
@@ -428,6 +709,23 @@ def _setup_run_renderer(ctx: Any, save: str | None, verbose: bool) -> Any:
428
709
  )
429
710
 
430
711
 
712
+ def _maybe_attach_transcript_toggle(ctx: Any, renderer: Any) -> None:
713
+ """Attach transcript toggle controller when interactive TTY is available."""
714
+ if renderer is None:
715
+ return
716
+
717
+ console_obj = getattr(renderer, "console", None)
718
+ if console_obj is None or not getattr(console_obj, "is_terminal", False):
719
+ return
720
+
721
+ tty_enabled = bool(get_ctx_value(ctx, "tty", True))
722
+ if not tty_enabled:
723
+ return
724
+
725
+ controller = TranscriptToggleController(enabled=True)
726
+ renderer.transcript_controller = controller
727
+
728
+
431
729
  def _prepare_run_kwargs(
432
730
  agent: Any,
433
731
  final_input_text: str,
@@ -477,9 +775,7 @@ def _save_run_transcript(save: str | None, result: Any, working_console: Any) ->
477
775
  if ext == "json":
478
776
  save_data = {
479
777
  "output": result or "",
480
- "full_debug_output": getattr(
481
- working_console, "get_captured_output", lambda: ""
482
- )(),
778
+ "full_debug_output": getattr(working_console, "get_captured_output", lambda: "")(),
483
779
  "timestamp": "captured during agent execution",
484
780
  }
485
781
  content = json.dumps(save_data, indent=2)
@@ -492,7 +788,7 @@ def _save_run_transcript(save: str | None, result: Any, working_console: Any) ->
492
788
 
493
789
  with open(save, "w", encoding="utf-8") as f:
494
790
  f.write(content)
495
- console.print(Text(f"[green]Full debug output saved to: {save}[/green]"))
791
+ print_markup(f"[{SUCCESS_STYLE}]Full debug output saved to: {save}[/]", console=console)
496
792
 
497
793
 
498
794
  @agents_group.command()
@@ -538,10 +834,11 @@ def run(
538
834
  files: tuple[str, ...] | None,
539
835
  verbose: bool,
540
836
  ) -> None:
541
- """Run an agent with input text.
837
+ r"""Run an agent with input text.
542
838
 
543
839
  Usage: aip agents run <agent_ref> <input_text> [OPTIONS]
544
840
 
841
+ \b
545
842
  Examples:
546
843
  aip agents run my-agent "Hello world"
547
844
  aip agents run agent-123 "Process this data" --timeout 600
@@ -549,14 +846,17 @@ def run(
549
846
  """
550
847
  final_input_text = _validate_run_input(input_option, input_text)
551
848
 
849
+ if verbose:
850
+ _emit_verbose_guidance(ctx)
851
+ return
852
+
552
853
  try:
553
854
  client = get_client(ctx)
554
- agent = _resolve_agent(
555
- ctx, client, agent_ref, select, interface_preference="fuzzy"
556
- )
855
+ agent = _resolve_agent(ctx, client, agent_ref, select, interface_preference="fuzzy")
557
856
 
558
857
  parsed_chat_history = _parse_chat_history(chat_history)
559
858
  renderer, working_console = _setup_run_renderer(ctx, save, verbose)
859
+ _maybe_attach_transcript_toggle(ctx, renderer)
560
860
 
561
861
  try:
562
862
  client.timeout = float(timeout)
@@ -574,17 +874,59 @@ def run(
574
874
 
575
875
  result = client.agents.run_agent(**run_kwargs, timeout=timeout)
576
876
 
877
+ slash_mode = _running_in_slash_mode(ctx)
878
+ agent_id = str(_safe_agent_attribute(agent, "id") or "") or None
879
+ agent_name = _safe_agent_attribute(agent, "name")
880
+ model_hint = _get_agent_model_name(agent)
881
+
882
+ transcript_context = store_transcript_for_session(
883
+ ctx,
884
+ renderer,
885
+ final_result=result,
886
+ agent_id=agent_id,
887
+ agent_name=agent_name,
888
+ model=model_hint,
889
+ source="slash" if slash_mode else "cli",
890
+ )
891
+
577
892
  _handle_run_output(ctx, result, renderer)
578
893
  _save_run_transcript(save, result, working_console)
894
+ maybe_launch_post_run_viewer(
895
+ ctx,
896
+ transcript_context,
897
+ console=console,
898
+ slash_mode=slash_mode,
899
+ )
579
900
 
580
901
  except AgentTimeoutError as e:
581
902
  error_msg = str(e)
582
903
  handle_json_output(ctx, error=Exception(error_msg))
583
- raise click.ClickException(error_msg)
904
+ raise click.ClickException(error_msg) from e
584
905
  except Exception as e:
585
906
  _handle_command_exception(ctx, e)
586
907
 
587
908
 
909
+ def _running_in_slash_mode(ctx: Any) -> bool:
910
+ """Return True if the command is executing inside the slash session."""
911
+ ctx_obj = getattr(ctx, "obj", None)
912
+ return isinstance(ctx_obj, dict) and bool(ctx_obj.get("_slash_session"))
913
+
914
+
915
+ def _emit_verbose_guidance(ctx: Any) -> None:
916
+ """Explain the modern alternative to the deprecated --verbose flag."""
917
+ if _running_in_slash_mode(ctx):
918
+ message = (
919
+ "[dim]Tip:[/] Verbose streaming has been retired in the command palette. Run the agent normally and open "
920
+ "the post-run viewer (Ctrl+T) to inspect the transcript."
921
+ )
922
+ else:
923
+ message = (
924
+ "[dim]Tip:[/] `--verbose` is no longer supported. Re-run without the flag and toggle the post-run viewer "
925
+ "(Ctrl+T) for detailed output."
926
+ )
927
+ handle_rich_output(ctx, markup_text(message))
928
+
929
+
588
930
  def _handle_import_file_logic(
589
931
  import_file: str,
590
932
  model: str | None,
@@ -650,16 +992,12 @@ def _extract_and_validate_fields(
650
992
  if not name:
651
993
  raise click.ClickException("Agent name is required (--name or --import)")
652
994
  if not instruction:
653
- raise click.ClickException(
654
- "Agent instruction is required (--instruction or --import)"
655
- )
995
+ raise click.ClickException("Agent instruction is required (--instruction or --import)")
656
996
 
657
997
  return name, instruction, model, tools, agents, mcps, timeout
658
998
 
659
999
 
660
- def _validate_and_coerce_fields(
661
- name: str, instruction: str, timeout: Any
662
- ) -> tuple[str, str, Any]:
1000
+ def _validate_and_coerce_fields(name: str, instruction: str, timeout: Any) -> tuple[str, str, Any]:
663
1001
  """Validate and coerce field values."""
664
1002
  name = validate_agent_name(name)
665
1003
  instruction = validate_agent_instruction(instruction)
@@ -670,19 +1008,11 @@ def _validate_and_coerce_fields(
670
1008
  return name, instruction, timeout
671
1009
 
672
1010
 
673
- def _resolve_resources(
674
- client: Any, tools: tuple, agents: tuple, mcps: tuple
675
- ) -> tuple[list, list, list]:
1011
+ def _resolve_resources(client: Any, tools: tuple, agents: tuple, mcps: tuple) -> tuple[list, list, list]:
676
1012
  """Resolve tool, agent, and MCP references."""
677
- resolved_tools = _resolve_resources_by_name(
678
- client, tools, "tool", client.find_tools, "Tool"
679
- )
680
- resolved_agents = _resolve_resources_by_name(
681
- client, agents, "agent", client.find_agents, "Agent"
682
- )
683
- resolved_mcps = _resolve_resources_by_name(
684
- client, mcps, "mcp", client.find_mcps, "MCP"
685
- )
1013
+ resolved_tools = _resolve_resources_by_name(client, tools, "tool", client.find_tools, "Tool")
1014
+ resolved_agents = _resolve_resources_by_name(client, agents, "agent", client.find_agents, "Agent")
1015
+ resolved_mcps = _resolve_resources_by_name(client, mcps, "mcp", client.find_mcps, "MCP")
686
1016
 
687
1017
  return resolved_tools, resolved_agents, resolved_mcps
688
1018
 
@@ -709,16 +1039,12 @@ def _build_create_kwargs(
709
1039
  }
710
1040
 
711
1041
  # Handle language model selection
712
- lm_selection_dict, should_strip_lm_identity = resolve_language_model_selection(
713
- merged_data, model
714
- )
1042
+ lm_selection_dict, should_strip_lm_identity = resolve_language_model_selection(merged_data, model)
715
1043
  create_kwargs.update(lm_selection_dict)
716
1044
 
717
1045
  # Handle import file specific logic
718
1046
  if import_file:
719
- _add_import_file_attributes(
720
- create_kwargs, merged_data, should_strip_lm_identity
721
- )
1047
+ _add_import_file_attributes(create_kwargs, merged_data, should_strip_lm_identity)
722
1048
 
723
1049
  return create_kwargs
724
1050
 
@@ -751,7 +1077,6 @@ def _add_import_file_attributes(
751
1077
  "type",
752
1078
  "framework",
753
1079
  "version",
754
- "tool_configs",
755
1080
  "mcps",
756
1081
  "a2a_profile",
757
1082
  }
@@ -765,18 +1090,13 @@ def _get_language_model_display_name(agent: Any, model: str | None) -> str:
765
1090
  lm_display = getattr(agent, "model", None)
766
1091
  if not lm_display:
767
1092
  cfg = getattr(agent, "agent_config", {}) or {}
768
- lm_display = (
769
- cfg.get("lm_name")
770
- or cfg.get("model")
771
- or model
772
- or f"{DEFAULT_MODEL} (backend default)"
773
- )
1093
+ lm_display = cfg.get("lm_name") or cfg.get("model") or model or f"{DEFAULT_MODEL} (backend default)"
774
1094
  return lm_display
775
1095
 
776
1096
 
777
1097
  def _handle_successful_creation(ctx: Any, agent: Any, model: str | None) -> None:
778
1098
  """Handle successful agent creation output."""
779
- handle_json_output(ctx, agent.model_dump())
1099
+ handle_json_output(ctx, _prepare_agent_output(agent))
780
1100
 
781
1101
  lm_display = _get_language_model_display_name(agent, model)
782
1102
 
@@ -805,7 +1125,7 @@ def _handle_command_exception(ctx: Any, e: Exception) -> None:
805
1125
  handle_json_output(ctx, error=e)
806
1126
  if get_ctx_value(ctx, "view") != "json":
807
1127
  print_api_error(e)
808
- raise click.ClickException(str(e))
1128
+ raise click.exceptions.Exit(1) from e
809
1129
 
810
1130
 
811
1131
  def _handle_creation_exception(ctx: Any, e: Exception) -> None:
@@ -848,8 +1168,9 @@ def create(
848
1168
  timeout: float | None,
849
1169
  import_file: str | None,
850
1170
  ) -> None:
851
- """Create a new agent.
1171
+ r"""Create a new agent.
852
1172
 
1173
+ \b
853
1174
  Examples:
854
1175
  aip agents create --name "My Agent" --instruction "You are a helpful assistant"
855
1176
  aip agents create --import agent.json
@@ -859,13 +1180,9 @@ def create(
859
1180
 
860
1181
  # Handle import file or CLI args
861
1182
  if import_file:
862
- merged_data = _handle_import_file_logic(
863
- import_file, model, name, instruction, tools, agents, mcps, timeout
864
- )
1183
+ merged_data = _handle_import_file_logic(import_file, model, name, instruction, tools, agents, mcps, timeout)
865
1184
  else:
866
- merged_data = _build_cli_args_data(
867
- name, instruction, model, tools, agents, mcps, timeout
868
- )
1185
+ merged_data = _build_cli_args_data(name, instruction, model, tools, agents, mcps, timeout)
869
1186
 
870
1187
  # Extract and validate fields
871
1188
  (
@@ -877,14 +1194,10 @@ def create(
877
1194
  mcps,
878
1195
  timeout,
879
1196
  ) = _extract_and_validate_fields(merged_data)
880
- name, instruction, timeout = _validate_and_coerce_fields(
881
- name, instruction, timeout
882
- )
1197
+ name, instruction, timeout = _validate_and_coerce_fields(name, instruction, timeout)
883
1198
 
884
1199
  # Resolve resources
885
- resolved_tools, resolved_agents, resolved_mcps = _resolve_resources(
886
- client, tools, agents, mcps
887
- )
1200
+ resolved_tools, resolved_agents, resolved_mcps = _resolve_resources(client, tools, agents, mcps)
888
1201
 
889
1202
  # Build create kwargs
890
1203
  create_kwargs = _build_create_kwargs(
@@ -914,7 +1227,7 @@ def _get_agent_for_update(client: Any, agent_id: str) -> Any:
914
1227
  try:
915
1228
  return client.agents.get_agent_by_id(agent_id)
916
1229
  except Exception as e:
917
- raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
1230
+ raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}") from e
918
1231
 
919
1232
 
920
1233
  def _handle_update_import_file(
@@ -923,6 +1236,7 @@ def _handle_update_import_file(
923
1236
  instruction: str | None,
924
1237
  tools: tuple[str, ...] | None,
925
1238
  agents: tuple[str, ...] | None,
1239
+ mcps: tuple[str, ...] | None,
926
1240
  timeout: float | None,
927
1241
  ) -> tuple[
928
1242
  Any | None,
@@ -930,11 +1244,12 @@ def _handle_update_import_file(
930
1244
  str | None,
931
1245
  tuple[str, ...] | None,
932
1246
  tuple[str, ...] | None,
1247
+ tuple[str, ...] | None,
933
1248
  float | None,
934
1249
  ]:
935
1250
  """Handle import file processing for agent update."""
936
1251
  if not import_file:
937
- return None, name, instruction, tools, agents, timeout
1252
+ return None, name, instruction, tools, agents, mcps, timeout
938
1253
 
939
1254
  import_data = load_resource_from_file(Path(import_file), "agent")
940
1255
  import_data = convert_export_to_import_format(import_data)
@@ -945,6 +1260,7 @@ def _handle_update_import_file(
945
1260
  "instruction": instruction,
946
1261
  "tools": tools or (),
947
1262
  "agents": agents or (),
1263
+ "mcps": mcps or (),
948
1264
  "timeout": timeout,
949
1265
  }
950
1266
 
@@ -956,6 +1272,7 @@ def _handle_update_import_file(
956
1272
  merged_data.get("instruction"),
957
1273
  tuple(merged_data.get("tools", ())),
958
1274
  tuple(merged_data.get("agents", ())),
1275
+ tuple(merged_data.get("mcps", ())),
959
1276
  coerce_timeout(merged_data.get("timeout")),
960
1277
  )
961
1278
 
@@ -965,6 +1282,7 @@ def _build_update_data(
965
1282
  instruction: str | None,
966
1283
  tools: tuple[str, ...] | None,
967
1284
  agents: tuple[str, ...] | None,
1285
+ mcps: tuple[str, ...] | None,
968
1286
  timeout: float | None,
969
1287
  ) -> dict[str, Any]:
970
1288
  """Build the update data dictionary from provided parameters."""
@@ -977,6 +1295,8 @@ def _build_update_data(
977
1295
  update_data["tools"] = list(tools)
978
1296
  if agents:
979
1297
  update_data["agents"] = list(agents)
1298
+ if mcps:
1299
+ update_data["mcps"] = list(mcps)
980
1300
  if timeout is not None:
981
1301
  update_data["timeout"] = timeout
982
1302
  return update_data
@@ -989,16 +1309,12 @@ def _handle_update_import_config(
989
1309
  if not import_file:
990
1310
  return
991
1311
 
992
- lm_selection, should_strip_lm_identity = resolve_language_model_selection(
993
- merged_data, None
994
- )
1312
+ lm_selection, should_strip_lm_identity = resolve_language_model_selection(merged_data, None)
995
1313
  update_data.update(lm_selection)
996
1314
 
997
1315
  raw_cfg = merged_data.get("agent_config") if isinstance(merged_data, dict) else None
998
1316
  if isinstance(raw_cfg, dict):
999
- update_data["agent_config"] = sanitize_agent_config(
1000
- raw_cfg, strip_lm_identity=should_strip_lm_identity
1001
- )
1317
+ update_data["agent_config"] = sanitize_agent_config(raw_cfg, strip_lm_identity=should_strip_lm_identity)
1002
1318
 
1003
1319
  excluded_fields = {
1004
1320
  "name",
@@ -1014,8 +1330,6 @@ def _handle_update_import_config(
1014
1330
  "type",
1015
1331
  "framework",
1016
1332
  "version",
1017
- "tool_configs",
1018
- "mcps",
1019
1333
  "a2a_profile",
1020
1334
  }
1021
1335
  for key, value in merged_data.items():
@@ -1029,6 +1343,7 @@ def _handle_update_import_config(
1029
1343
  @click.option("--instruction", help="New instruction")
1030
1344
  @click.option("--tools", multiple=True, help="New tool names or IDs")
1031
1345
  @click.option("--agents", multiple=True, help="New sub-agent names")
1346
+ @click.option("--mcps", multiple=True, help="New MCP names or IDs")
1032
1347
  @click.option("--timeout", type=int, help="New timeout value")
1033
1348
  @click.option(
1034
1349
  "--import",
@@ -1045,11 +1360,13 @@ def update(
1045
1360
  instruction: str | None,
1046
1361
  tools: tuple[str, ...] | None,
1047
1362
  agents: tuple[str, ...] | None,
1363
+ mcps: tuple[str, ...] | None,
1048
1364
  timeout: float | None,
1049
1365
  import_file: str | None,
1050
1366
  ) -> None:
1051
- """Update an existing agent.
1367
+ r"""Update an existing agent.
1052
1368
 
1369
+ \b
1053
1370
  Examples:
1054
1371
  aip agents update my-agent --instruction "New instruction"
1055
1372
  aip agents update my-agent --import agent.json
@@ -1065,22 +1382,27 @@ def update(
1065
1382
  instruction,
1066
1383
  tools,
1067
1384
  agents,
1385
+ mcps,
1068
1386
  timeout,
1069
- ) = _handle_update_import_file(
1070
- import_file, name, instruction, tools, agents, timeout
1071
- )
1387
+ ) = _handle_update_import_file(import_file, name, instruction, tools, agents, mcps, timeout)
1072
1388
 
1073
- update_data = _build_update_data(name, instruction, tools, agents, timeout)
1389
+ update_data = _build_update_data(name, instruction, tools, agents, mcps, timeout)
1074
1390
 
1075
1391
  if merged_data:
1076
1392
  _handle_update_import_config(import_file, merged_data, update_data)
1393
+ # Ensure instruction from import file is included if not already set via CLI
1394
+ # This handles the case where instruction is None in CLI args but exists in import file
1395
+ if import_file and (instruction is None or "instruction" not in update_data):
1396
+ import_instruction = merged_data.get("instruction")
1397
+ if import_instruction is not None:
1398
+ update_data["instruction"] = import_instruction
1077
1399
 
1078
1400
  if not update_data:
1079
1401
  raise click.ClickException("No update fields specified")
1080
1402
 
1081
1403
  updated_agent = client.agents.update_agent(agent.id, **update_data)
1082
1404
 
1083
- handle_json_output(ctx, updated_agent.model_dump())
1405
+ handle_json_output(ctx, _prepare_agent_output(updated_agent))
1084
1406
  handle_rich_output(ctx, display_update_success("Agent", updated_agent.name))
1085
1407
  handle_rich_output(ctx, display_agent_run_suggestions(updated_agent))
1086
1408
 
@@ -1108,7 +1430,7 @@ def delete(ctx: Any, agent_id: str, yes: bool) -> None:
1108
1430
  try:
1109
1431
  agent = client.agents.get_agent_by_id(agent_id)
1110
1432
  except Exception as e:
1111
- raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
1433
+ raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}") from e
1112
1434
 
1113
1435
  # Confirm deletion when not forced
1114
1436
  if not yes and not display_confirmation_prompt("Agent", agent.name):
@@ -1140,13 +1462,11 @@ def delete(ctx: Any, agent_id: str, yes: bool) -> None:
1140
1462
  "--base-url",
1141
1463
  help="Custom LangFlow server base URL (overrides LANGFLOW_BASE_URL env var)",
1142
1464
  )
1143
- @click.option(
1144
- "--api-key", help="Custom LangFlow API key (overrides LANGFLOW_API_KEY env var)"
1145
- )
1465
+ @click.option("--api-key", help="Custom LangFlow API key (overrides LANGFLOW_API_KEY env var)")
1146
1466
  @output_flags()
1147
1467
  @click.pass_context
1148
1468
  def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1149
- """Sync agents with LangFlow server flows.
1469
+ r"""Sync agents with LangFlow server flows.
1150
1470
 
1151
1471
  This command fetches all flows from the configured LangFlow server and
1152
1472
  creates/updates corresponding agents in the platform.
@@ -1155,6 +1475,7 @@ def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1155
1475
  - Command options (--base-url, --api-key)
1156
1476
  - Environment variables (LANGFLOW_BASE_URL, LANGFLOW_API_KEY)
1157
1477
 
1478
+ \b
1158
1479
  Examples:
1159
1480
  aip agents sync-langflow
1160
1481
  aip agents sync-langflow --base-url https://my-langflow.com --api-key my-key
@@ -1170,18 +1491,17 @@ def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
1170
1491
 
1171
1492
  # Show success message for non-JSON output
1172
1493
  if get_ctx_value(ctx, "view") != "json":
1173
- from rich.text import Text
1174
-
1175
1494
  # Extract some useful info from the result
1176
- success_count = result.get("data", {}).get("created_count", 0) + result.get(
1177
- "data", {}
1178
- ).get("updated_count", 0)
1495
+ success_count = result.get("data", {}).get("created_count", 0) + result.get("data", {}).get(
1496
+ "updated_count", 0
1497
+ )
1179
1498
  total_count = result.get("data", {}).get("total_processed", 0)
1180
1499
 
1181
1500
  handle_rich_output(
1182
1501
  ctx,
1183
- Text(
1184
- f"[green]✅ Successfully synced {success_count} LangFlow agents ({total_count} total processed)[/green]"
1502
+ markup_text(
1503
+ f"[{SUCCESS_STYLE}]✅ Successfully synced {success_count} LangFlow agents "
1504
+ f"({total_count} total processed)[/]"
1185
1505
  ),
1186
1506
  )
1187
1507