glaip-sdk 0.0.4__py3-none-any.whl → 0.0.5__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.
- glaip_sdk/__init__.py +5 -5
- glaip_sdk/branding.py +18 -17
- glaip_sdk/cli/__init__.py +1 -1
- glaip_sdk/cli/agent_config.py +82 -0
- glaip_sdk/cli/commands/__init__.py +3 -3
- glaip_sdk/cli/commands/agents.py +570 -673
- glaip_sdk/cli/commands/configure.py +2 -2
- glaip_sdk/cli/commands/mcps.py +148 -143
- glaip_sdk/cli/commands/models.py +1 -1
- glaip_sdk/cli/commands/tools.py +250 -179
- glaip_sdk/cli/display.py +244 -0
- glaip_sdk/cli/io.py +106 -0
- glaip_sdk/cli/main.py +14 -18
- glaip_sdk/cli/resolution.py +59 -0
- glaip_sdk/cli/utils.py +305 -264
- glaip_sdk/cli/validators.py +235 -0
- glaip_sdk/client/__init__.py +3 -224
- glaip_sdk/client/agents.py +631 -191
- glaip_sdk/client/base.py +66 -4
- glaip_sdk/client/main.py +226 -0
- glaip_sdk/client/mcps.py +143 -18
- glaip_sdk/client/tools.py +146 -11
- glaip_sdk/config/constants.py +10 -1
- glaip_sdk/models.py +42 -2
- glaip_sdk/rich_components.py +29 -0
- glaip_sdk/utils/__init__.py +18 -171
- glaip_sdk/utils/agent_config.py +181 -0
- glaip_sdk/utils/client_utils.py +159 -79
- glaip_sdk/utils/display.py +100 -0
- glaip_sdk/utils/general.py +94 -0
- glaip_sdk/utils/import_export.py +140 -0
- glaip_sdk/utils/rendering/formatting.py +6 -1
- glaip_sdk/utils/rendering/renderer/__init__.py +67 -8
- glaip_sdk/utils/rendering/renderer/base.py +340 -247
- glaip_sdk/utils/rendering/renderer/debug.py +3 -2
- glaip_sdk/utils/rendering/renderer/panels.py +11 -10
- glaip_sdk/utils/rendering/steps.py +1 -1
- glaip_sdk/utils/resource_refs.py +192 -0
- glaip_sdk/utils/rich_utils.py +29 -0
- glaip_sdk/utils/serialization.py +285 -0
- glaip_sdk/utils/validation.py +273 -0
- {glaip_sdk-0.0.4.dist-info → glaip_sdk-0.0.5.dist-info}/METADATA +6 -5
- glaip_sdk-0.0.5.dist-info/RECORD +55 -0
- glaip_sdk/cli/commands/init.py +0 -93
- glaip_sdk-0.0.4.dist-info/RECORD +0 -41
- {glaip_sdk-0.0.4.dist-info → glaip_sdk-0.0.5.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.4.dist-info → glaip_sdk-0.0.5.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/agents.py
CHANGED
|
@@ -6,20 +6,43 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
|
-
from datetime import datetime
|
|
10
9
|
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
12
10
|
|
|
13
11
|
import click
|
|
14
12
|
from rich.console import Console
|
|
15
|
-
from rich.panel import Panel
|
|
16
13
|
from rich.text import Text
|
|
17
14
|
|
|
18
|
-
from glaip_sdk.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
from glaip_sdk.cli.agent_config import (
|
|
16
|
+
merge_agent_config_with_cli_args as merge_import_with_cli_args,
|
|
17
|
+
)
|
|
18
|
+
from glaip_sdk.cli.agent_config import (
|
|
19
|
+
resolve_agent_language_model_selection as resolve_language_model_selection,
|
|
20
|
+
)
|
|
21
|
+
from glaip_sdk.cli.agent_config import (
|
|
22
|
+
sanitize_agent_config_for_cli as sanitize_agent_config,
|
|
23
|
+
)
|
|
24
|
+
from glaip_sdk.cli.display import (
|
|
25
|
+
build_resource_result_data,
|
|
26
|
+
display_agent_run_suggestions,
|
|
27
|
+
display_confirmation_prompt,
|
|
28
|
+
display_creation_success,
|
|
29
|
+
display_deletion_success,
|
|
30
|
+
display_update_success,
|
|
31
|
+
handle_json_output,
|
|
32
|
+
handle_rich_output,
|
|
33
|
+
print_api_error,
|
|
34
|
+
)
|
|
35
|
+
from glaip_sdk.cli.io import (
|
|
36
|
+
export_resource_to_file_with_validation as export_resource_to_file,
|
|
37
|
+
)
|
|
38
|
+
from glaip_sdk.cli.io import (
|
|
39
|
+
fetch_raw_resource_details,
|
|
40
|
+
)
|
|
41
|
+
from glaip_sdk.cli.io import (
|
|
42
|
+
load_resource_from_file_with_validation as load_resource_from_file,
|
|
43
|
+
)
|
|
44
|
+
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
45
|
+
from glaip_sdk.cli.utils import (
|
|
23
46
|
_fuzzy_pick_for_resources,
|
|
24
47
|
build_renderer,
|
|
25
48
|
coerce_to_row,
|
|
@@ -27,44 +50,27 @@ from ..utils import (
|
|
|
27
50
|
output_flags,
|
|
28
51
|
output_list,
|
|
29
52
|
output_result,
|
|
30
|
-
resolve_resource,
|
|
31
53
|
)
|
|
54
|
+
from glaip_sdk.cli.validators import (
|
|
55
|
+
validate_agent_instruction_cli as validate_agent_instruction,
|
|
56
|
+
)
|
|
57
|
+
from glaip_sdk.cli.validators import (
|
|
58
|
+
validate_agent_name_cli as validate_agent_name,
|
|
59
|
+
)
|
|
60
|
+
from glaip_sdk.cli.validators import (
|
|
61
|
+
validate_timeout_cli as validate_timeout,
|
|
62
|
+
)
|
|
63
|
+
from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
|
|
64
|
+
from glaip_sdk.exceptions import AgentTimeoutError
|
|
65
|
+
from glaip_sdk.utils import format_datetime, is_uuid
|
|
66
|
+
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
67
|
+
from glaip_sdk.utils.import_export import convert_export_to_import_format
|
|
68
|
+
from glaip_sdk.utils.validation import coerce_timeout
|
|
32
69
|
|
|
33
70
|
console = Console()
|
|
34
71
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"""Display helpful suggestions for running the agent."""
|
|
38
|
-
console.print()
|
|
39
|
-
console.print(
|
|
40
|
-
Panel(
|
|
41
|
-
f"[bold blue]💡 Next Steps:[/bold blue]\n\n"
|
|
42
|
-
f"🚀 Run this agent:\n"
|
|
43
|
-
f' [green]aip agents run {agent.id} "Your message here"[/green]\n\n'
|
|
44
|
-
f"📋 Or use the agent name:\n"
|
|
45
|
-
f' [green]aip agents run "{agent.name}" "Your message here"[/green]\n\n'
|
|
46
|
-
f"🔧 Available options:\n"
|
|
47
|
-
f" [dim]--chat-history[/dim] Include previous conversation\n"
|
|
48
|
-
f" [dim]--file[/dim] Attach files\n"
|
|
49
|
-
f" [dim]--input[/dim] Alternative input method\n"
|
|
50
|
-
f" [dim]--timeout[/dim] Set execution timeout\n"
|
|
51
|
-
f" [dim]--save[/dim] Save transcript to file\n"
|
|
52
|
-
f" [dim]--verbose[/dim] Show detailed execution\n\n"
|
|
53
|
-
f"💡 [dim]Input text can be positional OR use --input flag (both work!)[/dim]",
|
|
54
|
-
title="🤖 Ready to Run Agent",
|
|
55
|
-
border_style="blue",
|
|
56
|
-
padding=(0, 1),
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _format_datetime(dt):
|
|
62
|
-
"""Format datetime object to readable string."""
|
|
63
|
-
if isinstance(dt, datetime):
|
|
64
|
-
return dt.strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
65
|
-
elif dt is None:
|
|
66
|
-
return "N/A"
|
|
67
|
-
return dt
|
|
72
|
+
# Error message constants
|
|
73
|
+
AGENT_NOT_FOUND_ERROR = "Agent not found"
|
|
68
74
|
|
|
69
75
|
|
|
70
76
|
def _fetch_full_agent_details(client, agent):
|
|
@@ -79,113 +85,6 @@ def _fetch_full_agent_details(client, agent):
|
|
|
79
85
|
return agent
|
|
80
86
|
|
|
81
87
|
|
|
82
|
-
def _build_agent_result_data(agent):
|
|
83
|
-
"""Build standardized result data for agent display."""
|
|
84
|
-
return {
|
|
85
|
-
"id": str(getattr(agent, "id", "N/A")),
|
|
86
|
-
"name": getattr(agent, "name", "N/A"),
|
|
87
|
-
"type": getattr(agent, "type", "N/A"),
|
|
88
|
-
"framework": getattr(agent, "framework", "N/A"),
|
|
89
|
-
"version": getattr(agent, "version", "N/A"),
|
|
90
|
-
"description": getattr(agent, "description", "N/A"),
|
|
91
|
-
"instruction": getattr(agent, "instruction", "") or "-",
|
|
92
|
-
"created_at": _format_datetime(getattr(agent, "created_at", "N/A")),
|
|
93
|
-
"updated_at": _format_datetime(getattr(agent, "updated_at", "N/A")),
|
|
94
|
-
"metadata": getattr(agent, "metadata", "N/A"),
|
|
95
|
-
"language_model_id": getattr(agent, "language_model_id", "N/A"),
|
|
96
|
-
"agent_config": getattr(agent, "agent_config", "N/A"),
|
|
97
|
-
"tool_configs": getattr(agent, "tool_configs", {}),
|
|
98
|
-
"tools": getattr(agent, "tools", []),
|
|
99
|
-
"agents": getattr(agent, "agents", []),
|
|
100
|
-
"mcps": getattr(agent, "mcps", []),
|
|
101
|
-
"a2a_profile": getattr(agent, "a2a_profile", "N/A"),
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _get_agent_attributes(agent):
|
|
106
|
-
"""Dynamically get all relevant agent attributes for export."""
|
|
107
|
-
# Exclude these attributes from export (methods, private attrs, computed properties)
|
|
108
|
-
exclude_attrs = {
|
|
109
|
-
"id",
|
|
110
|
-
"created_at",
|
|
111
|
-
"updated_at", # System-managed fields
|
|
112
|
-
"_client",
|
|
113
|
-
"_raw_data", # Internal fields
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
# Methods and callable attributes to exclude
|
|
117
|
-
exclude_callables = {
|
|
118
|
-
"model_dump",
|
|
119
|
-
"dict",
|
|
120
|
-
"json", # Pydantic methods
|
|
121
|
-
"get",
|
|
122
|
-
"post",
|
|
123
|
-
"put",
|
|
124
|
-
"delete", # HTTP methods
|
|
125
|
-
"save",
|
|
126
|
-
"refresh",
|
|
127
|
-
"update", # ORM methods
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export_data = {}
|
|
131
|
-
|
|
132
|
-
# Method 1: Try Pydantic model_dump() if available (best for structured data)
|
|
133
|
-
if hasattr(agent, "model_dump") and callable(agent.model_dump):
|
|
134
|
-
try:
|
|
135
|
-
# Get all model fields
|
|
136
|
-
all_data = agent.model_dump()
|
|
137
|
-
# Filter out excluded attributes
|
|
138
|
-
for key, value in all_data.items():
|
|
139
|
-
if key not in exclude_attrs and key not in exclude_callables:
|
|
140
|
-
export_data[key] = value
|
|
141
|
-
return export_data
|
|
142
|
-
except Exception:
|
|
143
|
-
# Fall back to manual attribute detection
|
|
144
|
-
pass
|
|
145
|
-
|
|
146
|
-
# Method 2: Manual attribute inspection with filtering
|
|
147
|
-
# Get all non-private, non-method attributes
|
|
148
|
-
all_attrs = []
|
|
149
|
-
if hasattr(agent, "__dict__"):
|
|
150
|
-
all_attrs.extend(agent.__dict__.keys())
|
|
151
|
-
if hasattr(agent, "__annotations__"):
|
|
152
|
-
all_attrs.extend(agent.__annotations__.keys())
|
|
153
|
-
|
|
154
|
-
# Remove duplicates and filter
|
|
155
|
-
all_attrs = list(set(all_attrs))
|
|
156
|
-
|
|
157
|
-
for attr in all_attrs:
|
|
158
|
-
# Skip excluded attributes
|
|
159
|
-
if (
|
|
160
|
-
attr in exclude_attrs
|
|
161
|
-
or attr.startswith("_") # Private attributes
|
|
162
|
-
or attr in exclude_callables
|
|
163
|
-
):
|
|
164
|
-
continue
|
|
165
|
-
|
|
166
|
-
# Skip callable attributes (methods, functions)
|
|
167
|
-
attr_value = getattr(agent, attr, None)
|
|
168
|
-
if callable(attr_value):
|
|
169
|
-
continue
|
|
170
|
-
|
|
171
|
-
# Add the attribute to export data
|
|
172
|
-
export_data[attr] = attr_value
|
|
173
|
-
|
|
174
|
-
return export_data
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _build_agent_export_data(agent):
|
|
178
|
-
"""Build comprehensive export data for agent (always includes all fields)."""
|
|
179
|
-
# Get all available agent attributes dynamically
|
|
180
|
-
all_agent_data = _get_agent_attributes(agent)
|
|
181
|
-
|
|
182
|
-
# Always include all detected attributes for comprehensive export
|
|
183
|
-
# Add default timeout if not present
|
|
184
|
-
if "timeout" not in all_agent_data:
|
|
185
|
-
all_agent_data["timeout"] = DEFAULT_AGENT_RUN_TIMEOUT
|
|
186
|
-
return all_agent_data
|
|
187
|
-
|
|
188
|
-
|
|
189
88
|
def _get_agent_model_name(agent):
|
|
190
89
|
"""Extract model name from agent configuration."""
|
|
191
90
|
# Try different possible locations for model name
|
|
@@ -200,231 +99,8 @@ def _get_agent_model_name(agent):
|
|
|
200
99
|
return DEFAULT_MODEL
|
|
201
100
|
|
|
202
101
|
|
|
203
|
-
def _extract_tool_ids(agent):
|
|
204
|
-
"""Extract tool IDs from agent tools list for import compatibility."""
|
|
205
|
-
tools = getattr(agent, "tools", [])
|
|
206
|
-
if not tools:
|
|
207
|
-
return []
|
|
208
|
-
|
|
209
|
-
ids = []
|
|
210
|
-
for tool in tools:
|
|
211
|
-
if isinstance(tool, dict):
|
|
212
|
-
tool_id = tool.get("id")
|
|
213
|
-
if tool_id:
|
|
214
|
-
ids.append(tool_id)
|
|
215
|
-
else:
|
|
216
|
-
# Fallback to name if ID not available
|
|
217
|
-
name = tool.get("name", "")
|
|
218
|
-
if name:
|
|
219
|
-
ids.append(name)
|
|
220
|
-
elif hasattr(tool, "id"):
|
|
221
|
-
ids.append(tool.id)
|
|
222
|
-
elif hasattr(tool, "name"):
|
|
223
|
-
ids.append(tool.name)
|
|
224
|
-
else:
|
|
225
|
-
ids.append(str(tool))
|
|
226
|
-
return ids
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def _extract_agent_ids(agent):
|
|
230
|
-
"""Extract agent IDs from agent agents list for import compatibility."""
|
|
231
|
-
agents = getattr(agent, "agents", [])
|
|
232
|
-
if not agents:
|
|
233
|
-
return []
|
|
234
|
-
|
|
235
|
-
ids = []
|
|
236
|
-
for sub_agent in agents:
|
|
237
|
-
if isinstance(sub_agent, dict):
|
|
238
|
-
agent_id = sub_agent.get("id")
|
|
239
|
-
if agent_id:
|
|
240
|
-
ids.append(agent_id)
|
|
241
|
-
else:
|
|
242
|
-
# Fallback to name if ID not available
|
|
243
|
-
name = sub_agent.get("name", "")
|
|
244
|
-
if name:
|
|
245
|
-
ids.append(name)
|
|
246
|
-
elif hasattr(sub_agent, "id"):
|
|
247
|
-
ids.append(sub_agent.id)
|
|
248
|
-
elif hasattr(sub_agent, "name"):
|
|
249
|
-
ids.append(sub_agent.name)
|
|
250
|
-
else:
|
|
251
|
-
ids.append(str(sub_agent))
|
|
252
|
-
return ids
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def _export_agent_to_file(agent, file_path: Path, format: str = "json"):
|
|
256
|
-
"""Export agent to file (JSON or YAML) with comprehensive data."""
|
|
257
|
-
export_data = _build_agent_export_data(agent)
|
|
258
|
-
|
|
259
|
-
if format.lower() == "yaml" or file_path.suffix.lower() in [".yaml", ".yml"]:
|
|
260
|
-
_write_yaml_to_file(file_path, export_data)
|
|
261
|
-
else:
|
|
262
|
-
_write_json_to_file(file_path, export_data)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def _load_agent_from_file(file_path: Path) -> dict[str, Any]:
|
|
266
|
-
"""Load agent data from JSON or YAML file."""
|
|
267
|
-
if file_path.suffix.lower() in [".yaml", ".yml"]:
|
|
268
|
-
return _read_yaml_from_file(file_path)
|
|
269
|
-
else:
|
|
270
|
-
return _read_json_from_file(file_path)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def _read_yaml_from_file(file_path: Path) -> dict[str, Any]:
|
|
274
|
-
"""Read YAML data from file."""
|
|
275
|
-
try:
|
|
276
|
-
import yaml
|
|
277
|
-
except ImportError:
|
|
278
|
-
raise click.ClickException(
|
|
279
|
-
"PyYAML is required for YAML import. Install with: pip install PyYAML"
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
if not file_path.exists():
|
|
283
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
|
284
|
-
|
|
285
|
-
with open(file_path, encoding="utf-8") as f:
|
|
286
|
-
data = yaml.safe_load(f)
|
|
287
|
-
|
|
288
|
-
# Handle instruction_lines array format for user-friendly YAML
|
|
289
|
-
if "instruction_lines" in data and isinstance(data["instruction_lines"], list):
|
|
290
|
-
data["instruction"] = "\n\n".join(data["instruction_lines"])
|
|
291
|
-
del data["instruction_lines"]
|
|
292
|
-
|
|
293
|
-
# Handle instruction as list from YAML export (convert back to string)
|
|
294
|
-
if "instruction" in data and isinstance(data["instruction"], list):
|
|
295
|
-
data["instruction"] = "\n\n".join(data["instruction"])
|
|
296
|
-
|
|
297
|
-
return data
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def _extract_ids_from_export(items: list) -> list[str]:
|
|
301
|
-
"""Extract IDs from export format (list of dicts with id/name fields)."""
|
|
302
|
-
ids = []
|
|
303
|
-
for item in items:
|
|
304
|
-
if isinstance(item, dict):
|
|
305
|
-
item_id = item.get("id")
|
|
306
|
-
if item_id:
|
|
307
|
-
ids.append(item_id)
|
|
308
|
-
elif isinstance(item, str):
|
|
309
|
-
ids.append(item)
|
|
310
|
-
return ids
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
def _convert_export_to_import_format(data: dict[str, Any]) -> dict[str, Any]:
|
|
314
|
-
"""Convert export format to import-compatible format (extract IDs from objects)."""
|
|
315
|
-
import_data = data.copy()
|
|
316
|
-
|
|
317
|
-
# Convert tools from dicts to IDs
|
|
318
|
-
if "tools" in import_data and isinstance(import_data["tools"], list):
|
|
319
|
-
import_data["tools"] = _extract_ids_from_export(import_data["tools"])
|
|
320
|
-
|
|
321
|
-
# Convert agents from dicts to IDs
|
|
322
|
-
if "agents" in import_data and isinstance(import_data["agents"], list):
|
|
323
|
-
import_data["agents"] = _extract_ids_from_export(import_data["agents"])
|
|
324
|
-
|
|
325
|
-
return import_data
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
def _write_json_to_file(file_path: Path, data: dict[str, Any], indent: int = 2) -> None:
|
|
329
|
-
"""Write data to JSON file with consistent formatting."""
|
|
330
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
331
|
-
json.dump(data, f, indent=indent)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def _write_yaml_to_file(file_path: Path, data: dict[str, Any]) -> None:
|
|
335
|
-
"""Write data to YAML file with user-friendly formatting."""
|
|
336
|
-
try:
|
|
337
|
-
import yaml
|
|
338
|
-
except ImportError:
|
|
339
|
-
raise click.ClickException(
|
|
340
|
-
"PyYAML is required for YAML export. Install with: pip install PyYAML"
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Custom YAML dumper for user-friendly instruction formatting
|
|
344
|
-
class LiteralString(str):
|
|
345
|
-
pass
|
|
346
|
-
|
|
347
|
-
def literal_string_representer(dumper, data):
|
|
348
|
-
# Use literal block scalar (|) for multiline strings to preserve formatting
|
|
349
|
-
if "\n" in data:
|
|
350
|
-
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
|
|
351
|
-
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
|
|
352
|
-
|
|
353
|
-
# Add custom representer to the YAML dumper
|
|
354
|
-
yaml.add_representer(LiteralString, literal_string_representer)
|
|
355
|
-
|
|
356
|
-
# Convert instruction to LiteralString for proper formatting
|
|
357
|
-
if "instruction" in data and data["instruction"]:
|
|
358
|
-
data["instruction"] = LiteralString(data["instruction"])
|
|
359
|
-
|
|
360
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
361
|
-
yaml.dump(
|
|
362
|
-
data, f, default_flow_style=False, allow_unicode=True, sort_keys=False
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
def _read_json_from_file(file_path: Path) -> dict[str, Any]:
|
|
367
|
-
"""Read JSON data from file with validation."""
|
|
368
|
-
if not file_path.exists():
|
|
369
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
|
370
|
-
|
|
371
|
-
if file_path.suffix.lower() != ".json":
|
|
372
|
-
raise ValueError(
|
|
373
|
-
f"Unsupported file format: {file_path.suffix}. Only JSON files are supported."
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
with open(file_path, encoding="utf-8") as f:
|
|
377
|
-
return json.load(f)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def _merge_import_with_cli_args(
|
|
381
|
-
import_data: dict[str, Any],
|
|
382
|
-
cli_args: dict[str, Any],
|
|
383
|
-
array_fields: list[str] = None,
|
|
384
|
-
) -> dict[str, Any]:
|
|
385
|
-
"""Merge imported data with CLI arguments, preferring CLI args.
|
|
386
|
-
|
|
387
|
-
Args:
|
|
388
|
-
import_data: Data loaded from import file
|
|
389
|
-
cli_args: Arguments passed via CLI
|
|
390
|
-
array_fields: Fields that should be combined (merged) rather than replaced
|
|
391
|
-
|
|
392
|
-
Returns:
|
|
393
|
-
Merged data dictionary
|
|
394
|
-
"""
|
|
395
|
-
if array_fields is None:
|
|
396
|
-
array_fields = ["tools", "agents"]
|
|
397
|
-
|
|
398
|
-
merged = {}
|
|
399
|
-
|
|
400
|
-
for key, cli_value in cli_args.items():
|
|
401
|
-
if cli_value is not None and (
|
|
402
|
-
not isinstance(cli_value, list | tuple) or len(cli_value) > 0
|
|
403
|
-
):
|
|
404
|
-
# CLI value takes precedence (for non-empty values)
|
|
405
|
-
if key in array_fields and key in import_data:
|
|
406
|
-
# For array fields, combine CLI and imported values
|
|
407
|
-
import_value = import_data[key]
|
|
408
|
-
if isinstance(import_value, list):
|
|
409
|
-
merged[key] = list(cli_value) + import_value
|
|
410
|
-
else:
|
|
411
|
-
merged[key] = cli_value
|
|
412
|
-
else:
|
|
413
|
-
merged[key] = cli_value
|
|
414
|
-
elif key in import_data:
|
|
415
|
-
# Use imported value if no CLI value
|
|
416
|
-
merged[key] = import_data[key]
|
|
417
|
-
|
|
418
|
-
# Add any import-only fields
|
|
419
|
-
for key, import_value in import_data.items():
|
|
420
|
-
if key not in merged:
|
|
421
|
-
merged[key] = import_value
|
|
422
|
-
|
|
423
|
-
return merged
|
|
424
|
-
|
|
425
|
-
|
|
426
102
|
def _resolve_resources_by_name(
|
|
427
|
-
|
|
103
|
+
_client, items: tuple[str, ...], resource_type: str, find_func, label: str
|
|
428
104
|
) -> list[str]:
|
|
429
105
|
"""Resolve resource names/IDs to IDs, handling ambiguity.
|
|
430
106
|
|
|
@@ -455,52 +131,81 @@ def _resolve_resources_by_name(
|
|
|
455
131
|
return out
|
|
456
132
|
|
|
457
133
|
|
|
458
|
-
def _display_agent_creation_success(agent, model=None, default_model=DEFAULT_MODEL):
|
|
459
|
-
"""Display success message for agent creation/update."""
|
|
460
|
-
lm = getattr(agent, "model", None)
|
|
461
|
-
if not lm:
|
|
462
|
-
cfg = getattr(agent, "agent_config", {}) or {}
|
|
463
|
-
lm = (
|
|
464
|
-
cfg.get("lm_name")
|
|
465
|
-
or cfg.get("model")
|
|
466
|
-
or model # Use provided model if specified
|
|
467
|
-
or f"{default_model} (backend default)"
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
panel = Panel(
|
|
471
|
-
f"[green]✅ Agent '{agent.name}' created successfully![/green]\n\n"
|
|
472
|
-
f"ID: {agent.id}\n"
|
|
473
|
-
f"Model: {lm}\n"
|
|
474
|
-
f"Type: {getattr(agent, 'type', 'config')}\n"
|
|
475
|
-
f"Framework: {getattr(agent, 'framework', 'langchain')}\n"
|
|
476
|
-
f"Version: {getattr(agent, 'version', '1.0')}",
|
|
477
|
-
title="🤖 Agent Created",
|
|
478
|
-
border_style="green",
|
|
479
|
-
padding=(0, 1),
|
|
480
|
-
)
|
|
481
|
-
console.print(panel)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
def _display_agent_update_success(agent):
|
|
485
|
-
"""Display success message for agent update."""
|
|
486
|
-
console.print(Text(f"[green]✅ Agent '{agent.name}' updated successfully[/green]"))
|
|
487
|
-
|
|
488
|
-
|
|
489
134
|
def _display_agent_details(ctx, client, agent):
|
|
490
|
-
"""Display full agent details
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
135
|
+
"""Display full agent details using raw API data to preserve ALL fields."""
|
|
136
|
+
if agent is None:
|
|
137
|
+
handle_rich_output(ctx, Text("[red]❌ No agent provided[/red]"))
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
# Try to fetch raw API data first to preserve ALL fields
|
|
141
|
+
raw_agent_data = fetch_raw_resource_details(client, agent, "agents")
|
|
142
|
+
|
|
143
|
+
if raw_agent_data:
|
|
144
|
+
# Use raw API data - this preserves ALL fields including account_id
|
|
145
|
+
# Format dates for better display (minimal postprocessing)
|
|
146
|
+
formatted_data = raw_agent_data.copy()
|
|
147
|
+
if "created_at" in formatted_data:
|
|
148
|
+
formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
|
|
149
|
+
if "updated_at" in formatted_data:
|
|
150
|
+
formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
|
|
151
|
+
|
|
152
|
+
# Display using output_result with raw data
|
|
153
|
+
output_result(
|
|
154
|
+
ctx,
|
|
155
|
+
formatted_data,
|
|
156
|
+
title="Agent Details",
|
|
157
|
+
panel_title=f"🤖 {raw_agent_data.get('name', 'Unknown')}",
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
# Fall back to original method if raw fetch fails
|
|
161
|
+
handle_rich_output(
|
|
162
|
+
ctx, Text("[yellow]Falling back to Pydantic model data[/yellow]")
|
|
163
|
+
)
|
|
164
|
+
full_agent = _fetch_full_agent_details(client, agent)
|
|
165
|
+
|
|
166
|
+
# Build result data using standardized helper
|
|
167
|
+
fields = [
|
|
168
|
+
"id",
|
|
169
|
+
"name",
|
|
170
|
+
"type",
|
|
171
|
+
"framework",
|
|
172
|
+
"version",
|
|
173
|
+
"description",
|
|
174
|
+
"instruction",
|
|
175
|
+
"created_at",
|
|
176
|
+
"updated_at",
|
|
177
|
+
"metadata",
|
|
178
|
+
"language_model_id",
|
|
179
|
+
"agent_config",
|
|
180
|
+
"tool_configs",
|
|
181
|
+
"tools",
|
|
182
|
+
"agents",
|
|
183
|
+
"mcps",
|
|
184
|
+
"a2a_profile",
|
|
185
|
+
]
|
|
186
|
+
result_data = build_resource_result_data(full_agent, fields)
|
|
187
|
+
if not result_data.get("instruction"):
|
|
188
|
+
result_data["instruction"] = "-" # pragma: no cover - cosmetic fallback
|
|
189
|
+
|
|
190
|
+
# Format dates for better display
|
|
191
|
+
if "created_at" in result_data and result_data["created_at"] not in [
|
|
192
|
+
"N/A",
|
|
193
|
+
None,
|
|
194
|
+
]:
|
|
195
|
+
result_data["created_at"] = format_datetime(result_data["created_at"])
|
|
196
|
+
if "updated_at" in result_data and result_data["updated_at"] not in [
|
|
197
|
+
"N/A",
|
|
198
|
+
None,
|
|
199
|
+
]:
|
|
200
|
+
result_data["updated_at"] = format_datetime(result_data["updated_at"])
|
|
201
|
+
|
|
202
|
+
# Display using output_result
|
|
203
|
+
output_result(
|
|
204
|
+
ctx,
|
|
205
|
+
result_data,
|
|
206
|
+
title="Agent Details",
|
|
207
|
+
panel_title=f"🤖 {full_agent.name}",
|
|
208
|
+
)
|
|
504
209
|
|
|
505
210
|
|
|
506
211
|
@click.group(name="agents", no_args_is_help=True)
|
|
@@ -515,12 +220,14 @@ def _resolve_agent(ctx, client, ref, select=None, interface_preference="fuzzy"):
|
|
|
515
220
|
Args:
|
|
516
221
|
interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list
|
|
517
222
|
"""
|
|
518
|
-
return
|
|
223
|
+
return resolve_resource_reference(
|
|
519
224
|
ctx,
|
|
225
|
+
client,
|
|
520
226
|
ref,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
227
|
+
"agent",
|
|
228
|
+
client.agents.get_agent_by_id,
|
|
229
|
+
client.agents.find_agents,
|
|
230
|
+
"Agent",
|
|
524
231
|
select=select,
|
|
525
232
|
interface_preference=interface_preference,
|
|
526
233
|
)
|
|
@@ -530,13 +237,32 @@ def _resolve_agent(ctx, client, ref, select=None, interface_preference="fuzzy"):
|
|
|
530
237
|
@click.option(
|
|
531
238
|
"--simple", is_flag=True, help="Show simple table without interactive picker"
|
|
532
239
|
)
|
|
240
|
+
@click.option(
|
|
241
|
+
"--type", "agent_type", help="Filter by agent type (config, code, a2a, langflow)"
|
|
242
|
+
)
|
|
243
|
+
@click.option(
|
|
244
|
+
"--framework", help="Filter by framework (langchain, langgraph, google_adk)"
|
|
245
|
+
)
|
|
246
|
+
@click.option("--name", help="Filter by partial name match (case-insensitive)")
|
|
247
|
+
@click.option("--version", help="Filter by exact version match")
|
|
248
|
+
@click.option(
|
|
249
|
+
"--sync-langflow",
|
|
250
|
+
is_flag=True,
|
|
251
|
+
help="Sync with LangFlow server before listing (only applies when filtering by langflow type)",
|
|
252
|
+
)
|
|
533
253
|
@output_flags()
|
|
534
254
|
@click.pass_context
|
|
535
|
-
def list_agents(ctx, simple):
|
|
536
|
-
"""List
|
|
255
|
+
def list_agents(ctx, simple, agent_type, framework, name, version, sync_langflow):
|
|
256
|
+
"""List agents with optional filtering."""
|
|
537
257
|
try:
|
|
538
258
|
client = get_client(ctx)
|
|
539
|
-
agents = client.agents.list_agents(
|
|
259
|
+
agents = client.agents.list_agents(
|
|
260
|
+
agent_type=agent_type,
|
|
261
|
+
framework=framework,
|
|
262
|
+
name=name,
|
|
263
|
+
version=version,
|
|
264
|
+
sync_langflow_agents=sync_langflow,
|
|
265
|
+
)
|
|
540
266
|
|
|
541
267
|
# Define table columns: (data_key, header, style, width)
|
|
542
268
|
columns = [
|
|
@@ -560,6 +286,8 @@ def list_agents(ctx, simple):
|
|
|
560
286
|
picked_agent = _fuzzy_pick_for_resources(agents, "agent", "")
|
|
561
287
|
if picked_agent:
|
|
562
288
|
_display_agent_details(ctx, client, picked_agent)
|
|
289
|
+
# Show run suggestions via centralized display helper
|
|
290
|
+
handle_rich_output(ctx, display_agent_run_suggestions(picked_agent))
|
|
563
291
|
return
|
|
564
292
|
|
|
565
293
|
# Show simple table (either --simple flag or non-interactive)
|
|
@@ -596,7 +324,7 @@ def get(ctx, agent_ref, select, export):
|
|
|
596
324
|
)
|
|
597
325
|
|
|
598
326
|
# Handle export option
|
|
599
|
-
if export:
|
|
327
|
+
if export: # pragma: no cover - requires filesystem verification
|
|
600
328
|
export_path = Path(export)
|
|
601
329
|
# Auto-detect format from file extension
|
|
602
330
|
if export_path.suffix.lower() in [".yaml", ".yml"]:
|
|
@@ -607,32 +335,131 @@ def get(ctx, agent_ref, select, export):
|
|
|
607
335
|
# Always export comprehensive data - re-fetch agent with full details
|
|
608
336
|
try:
|
|
609
337
|
agent = client.agents.get_agent_by_id(agent.id)
|
|
610
|
-
except Exception as e:
|
|
611
|
-
|
|
612
|
-
|
|
338
|
+
except Exception as e: # pragma: no cover - best-effort fallback messaging
|
|
339
|
+
handle_rich_output(
|
|
340
|
+
ctx,
|
|
341
|
+
Text(
|
|
342
|
+
f"[yellow]⚠️ Could not fetch full agent details: {e}[/yellow]"
|
|
343
|
+
),
|
|
613
344
|
)
|
|
614
|
-
|
|
615
|
-
Text("[yellow]⚠️ Proceeding with available data[/yellow]")
|
|
345
|
+
handle_rich_output(
|
|
346
|
+
ctx, Text("[yellow]⚠️ Proceeding with available data[/yellow]")
|
|
616
347
|
)
|
|
617
348
|
|
|
618
|
-
|
|
619
|
-
|
|
349
|
+
export_resource_to_file(agent, export_path, detected_format)
|
|
350
|
+
handle_rich_output(
|
|
351
|
+
ctx,
|
|
620
352
|
Text(
|
|
621
353
|
f"[green]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/green]"
|
|
622
|
-
)
|
|
354
|
+
),
|
|
623
355
|
)
|
|
624
356
|
|
|
625
357
|
# Display full agent details using the standardized helper
|
|
626
358
|
_display_agent_details(ctx, client, agent)
|
|
627
359
|
|
|
628
|
-
# Show run suggestions
|
|
629
|
-
|
|
630
|
-
_display_run_suggestions(agent)
|
|
360
|
+
# Show run suggestions via centralized display helper
|
|
361
|
+
handle_rich_output(ctx, display_agent_run_suggestions(agent))
|
|
631
362
|
|
|
632
363
|
except Exception as e:
|
|
633
364
|
raise click.ClickException(str(e))
|
|
634
365
|
|
|
635
366
|
|
|
367
|
+
def _validate_run_input(input_option, input_text):
|
|
368
|
+
"""Validate and determine the final input text for agent run."""
|
|
369
|
+
final_input_text = input_option if input_option else input_text
|
|
370
|
+
|
|
371
|
+
if not final_input_text:
|
|
372
|
+
raise click.ClickException(
|
|
373
|
+
"Input text is required. Use either positional argument or --input option."
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return final_input_text
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _parse_chat_history(chat_history):
|
|
380
|
+
"""Parse chat history JSON if provided."""
|
|
381
|
+
if not chat_history:
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
return json.loads(chat_history)
|
|
386
|
+
except json.JSONDecodeError:
|
|
387
|
+
raise click.ClickException("Invalid JSON in chat history")
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _setup_run_renderer(ctx, save, verbose):
|
|
391
|
+
"""Set up renderer and working console for agent run."""
|
|
392
|
+
tty_enabled = bool((ctx.obj or {}).get("tty", True))
|
|
393
|
+
return build_renderer(
|
|
394
|
+
ctx,
|
|
395
|
+
save_path=save,
|
|
396
|
+
verbose=verbose,
|
|
397
|
+
_tty_enabled=tty_enabled,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _prepare_run_kwargs(
|
|
402
|
+
agent, final_input_text, files, parsed_chat_history, renderer, tty_enabled
|
|
403
|
+
):
|
|
404
|
+
"""Prepare kwargs for agent run."""
|
|
405
|
+
run_kwargs = {
|
|
406
|
+
"agent_id": agent.id,
|
|
407
|
+
"message": final_input_text,
|
|
408
|
+
"files": list(files),
|
|
409
|
+
"agent_name": agent.name,
|
|
410
|
+
"tty": tty_enabled,
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if parsed_chat_history:
|
|
414
|
+
run_kwargs["chat_history"] = parsed_chat_history
|
|
415
|
+
|
|
416
|
+
if renderer is not None:
|
|
417
|
+
run_kwargs["renderer"] = renderer
|
|
418
|
+
|
|
419
|
+
return run_kwargs
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _handle_run_output(ctx, result, renderer):
|
|
423
|
+
"""Handle output formatting for agent run results."""
|
|
424
|
+
printed_by_renderer = bool(renderer)
|
|
425
|
+
selected_view = (ctx.obj or {}).get("view", "rich")
|
|
426
|
+
|
|
427
|
+
if not printed_by_renderer:
|
|
428
|
+
if selected_view == "json":
|
|
429
|
+
handle_json_output(ctx, {"output": result})
|
|
430
|
+
elif selected_view == "md":
|
|
431
|
+
click.echo(f"# Assistant\n\n{result}")
|
|
432
|
+
elif selected_view == "plain":
|
|
433
|
+
click.echo(result)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _save_run_transcript(save, result, working_console):
|
|
437
|
+
"""Save transcript to file if requested."""
|
|
438
|
+
if not save:
|
|
439
|
+
return
|
|
440
|
+
|
|
441
|
+
ext = (save.rsplit(".", 1)[-1] or "").lower()
|
|
442
|
+
if ext == "json":
|
|
443
|
+
save_data = {
|
|
444
|
+
"output": result or "",
|
|
445
|
+
"full_debug_output": getattr(
|
|
446
|
+
working_console, "get_captured_output", lambda: ""
|
|
447
|
+
)(),
|
|
448
|
+
"timestamp": "captured during agent execution",
|
|
449
|
+
}
|
|
450
|
+
content = json.dumps(save_data, indent=2)
|
|
451
|
+
else:
|
|
452
|
+
full_output = getattr(working_console, "get_captured_output", lambda: "")()
|
|
453
|
+
if full_output:
|
|
454
|
+
content = f"# Agent Debug Log\n\n{full_output}\n\n---\n\n## Final Result\n\n{result or ''}\n"
|
|
455
|
+
else:
|
|
456
|
+
content = f"# Assistant\n\n{result or ''}\n"
|
|
457
|
+
|
|
458
|
+
with open(save, "w", encoding="utf-8") as f:
|
|
459
|
+
f.write(content)
|
|
460
|
+
console.print(Text(f"[green]Full debug output saved to: {save}[/green]"))
|
|
461
|
+
|
|
462
|
+
|
|
636
463
|
@agents_group.command()
|
|
637
464
|
@click.argument("agent_ref")
|
|
638
465
|
@click.argument("input_text", required=False)
|
|
@@ -685,126 +512,42 @@ def run(
|
|
|
685
512
|
aip agents run agent-123 "Process this data" --timeout 600
|
|
686
513
|
aip agents run my-agent --input "Hello world" # Legacy style
|
|
687
514
|
"""
|
|
688
|
-
|
|
689
|
-
final_input_text = input_option if input_option else input_text
|
|
690
|
-
|
|
691
|
-
# Validate that we have input text from either positional argument or --input option
|
|
692
|
-
if not final_input_text:
|
|
693
|
-
raise click.ClickException(
|
|
694
|
-
"Input text is required. Use either positional argument or --input option."
|
|
695
|
-
)
|
|
515
|
+
final_input_text = _validate_run_input(input_option, input_text)
|
|
696
516
|
|
|
697
517
|
try:
|
|
698
518
|
client = get_client(ctx)
|
|
699
|
-
|
|
700
|
-
# Resolve agent by ID or name (align with other commands) - use fuzzy interface
|
|
701
519
|
agent = _resolve_agent(
|
|
702
520
|
ctx, client, agent_ref, select, interface_preference="fuzzy"
|
|
703
521
|
)
|
|
704
522
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
if chat_history:
|
|
708
|
-
try:
|
|
709
|
-
parsed_chat_history = json.loads(chat_history)
|
|
710
|
-
except json.JSONDecodeError:
|
|
711
|
-
raise click.ClickException("Invalid JSON in chat history")
|
|
712
|
-
|
|
713
|
-
# Create custom renderer with CLI flags
|
|
714
|
-
tty_enabled = bool((ctx.obj or {}).get("tty", True))
|
|
715
|
-
|
|
716
|
-
# Build renderer and capturing console
|
|
717
|
-
renderer, working_console = build_renderer(
|
|
718
|
-
ctx,
|
|
719
|
-
save_path=save,
|
|
720
|
-
verbose=verbose,
|
|
721
|
-
tty_enabled=tty_enabled,
|
|
722
|
-
)
|
|
523
|
+
parsed_chat_history = _parse_chat_history(chat_history)
|
|
524
|
+
renderer, working_console = _setup_run_renderer(ctx, save, verbose)
|
|
723
525
|
|
|
724
|
-
# Set HTTP timeout to match agent timeout exactly
|
|
725
|
-
# This ensures the agent timeout controls the HTTP timeout
|
|
726
526
|
try:
|
|
727
527
|
client.timeout = float(timeout)
|
|
728
528
|
except Exception:
|
|
729
529
|
pass
|
|
730
530
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
"tty"
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
# Add optional parameters
|
|
741
|
-
if parsed_chat_history:
|
|
742
|
-
run_kwargs["chat_history"] = parsed_chat_history
|
|
743
|
-
|
|
744
|
-
# Pass custom renderer if available
|
|
745
|
-
if renderer is not None:
|
|
746
|
-
run_kwargs["renderer"] = renderer
|
|
531
|
+
run_kwargs = _prepare_run_kwargs(
|
|
532
|
+
agent,
|
|
533
|
+
final_input_text,
|
|
534
|
+
files,
|
|
535
|
+
parsed_chat_history,
|
|
536
|
+
renderer,
|
|
537
|
+
bool((ctx.obj or {}).get("tty", True)),
|
|
538
|
+
)
|
|
747
539
|
|
|
748
|
-
# Pass timeout to client (verbose mode is handled by the renderer)
|
|
749
540
|
result = client.agents.run_agent(**run_kwargs, timeout=timeout)
|
|
750
541
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
printed_by_renderer = bool(renderer)
|
|
754
|
-
|
|
755
|
-
# Resolve selected view from context (output_flags() stores it here)
|
|
756
|
-
selected_view = (ctx.obj or {}).get("view", "rich")
|
|
757
|
-
|
|
758
|
-
# Handle output format for fallback
|
|
759
|
-
# Only print here if nothing was printed by the renderer
|
|
760
|
-
if not printed_by_renderer:
|
|
761
|
-
if selected_view == "json":
|
|
762
|
-
click.echo(json.dumps({"output": result}, indent=2))
|
|
763
|
-
elif selected_view == "md":
|
|
764
|
-
click.echo(f"# Assistant\n\n{result}")
|
|
765
|
-
elif selected_view == "plain":
|
|
766
|
-
click.echo(result)
|
|
767
|
-
|
|
768
|
-
# Save transcript if requested
|
|
769
|
-
if save:
|
|
770
|
-
ext = (save.rsplit(".", 1)[-1] or "").lower()
|
|
771
|
-
if ext == "json":
|
|
772
|
-
# Save both the result and captured output
|
|
773
|
-
save_data = {
|
|
774
|
-
"output": result or "",
|
|
775
|
-
"full_debug_output": getattr(
|
|
776
|
-
working_console, "get_captured_output", lambda: ""
|
|
777
|
-
)(),
|
|
778
|
-
"timestamp": "captured during agent execution",
|
|
779
|
-
}
|
|
780
|
-
content = json.dumps(save_data, indent=2)
|
|
781
|
-
else:
|
|
782
|
-
# For markdown/text files, save the full captured output if available
|
|
783
|
-
# Get the full captured output including all tool panels and debug info (if available)
|
|
784
|
-
full_output = getattr(
|
|
785
|
-
working_console, "get_captured_output", lambda: ""
|
|
786
|
-
)()
|
|
787
|
-
if full_output:
|
|
788
|
-
content = f"# Agent Debug Log\n\n{full_output}\n\n---\n\n## Final Result\n\n{result or ''}\n"
|
|
789
|
-
else:
|
|
790
|
-
# Fallback to simple format
|
|
791
|
-
content = f"# Assistant\n\n{result or ''}\n"
|
|
792
|
-
|
|
793
|
-
with open(save, "w", encoding="utf-8") as f:
|
|
794
|
-
f.write(content)
|
|
795
|
-
console.print(Text(f"[green]Full debug output saved to: {save}[/green]"))
|
|
542
|
+
_handle_run_output(ctx, result, renderer)
|
|
543
|
+
_save_run_transcript(save, result, working_console)
|
|
796
544
|
|
|
797
545
|
except AgentTimeoutError as e:
|
|
798
|
-
# Handle agent timeout errors with specific messages
|
|
799
546
|
error_msg = str(e)
|
|
800
|
-
|
|
801
|
-
click.echo(json.dumps({"error": error_msg}, indent=2))
|
|
802
|
-
# Don't print the error message here - Click.ClickException will handle it
|
|
547
|
+
handle_json_output(ctx, error=Exception(error_msg))
|
|
803
548
|
raise click.ClickException(error_msg)
|
|
804
549
|
except Exception as e:
|
|
805
|
-
|
|
806
|
-
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
807
|
-
# Don't print the error message here - Click.ClickException will handle it
|
|
550
|
+
handle_json_output(ctx, error=e)
|
|
808
551
|
raise click.ClickException(str(e))
|
|
809
552
|
|
|
810
553
|
|
|
@@ -817,6 +560,7 @@ def run(
|
|
|
817
560
|
)
|
|
818
561
|
@click.option("--tools", multiple=True, help="Tool names or IDs to attach")
|
|
819
562
|
@click.option("--agents", multiple=True, help="Sub-agent names or IDs to attach")
|
|
563
|
+
@click.option("--mcps", multiple=True, help="MCP names or IDs to attach")
|
|
820
564
|
@click.option(
|
|
821
565
|
"--timeout",
|
|
822
566
|
default=DEFAULT_AGENT_RUN_TIMEOUT,
|
|
@@ -838,6 +582,7 @@ def create(
|
|
|
838
582
|
model,
|
|
839
583
|
tools,
|
|
840
584
|
agents,
|
|
585
|
+
mcps,
|
|
841
586
|
timeout,
|
|
842
587
|
import_file,
|
|
843
588
|
):
|
|
@@ -850,12 +595,20 @@ def create(
|
|
|
850
595
|
try:
|
|
851
596
|
client = get_client(ctx)
|
|
852
597
|
|
|
598
|
+
# Initialize merged_data for cases without import_file
|
|
599
|
+
merged_data = {}
|
|
600
|
+
|
|
853
601
|
# Handle import from file
|
|
854
|
-
if
|
|
855
|
-
|
|
602
|
+
if (
|
|
603
|
+
import_file
|
|
604
|
+
): # pragma: no cover - exercised in higher-level integration tests
|
|
605
|
+
import_data = load_resource_from_file(Path(import_file), "agent")
|
|
856
606
|
|
|
857
607
|
# Convert export format to import-compatible format
|
|
858
|
-
import_data =
|
|
608
|
+
import_data = convert_export_to_import_format(import_data)
|
|
609
|
+
|
|
610
|
+
# Auto-normalize agent config (extract LM settings from agent_config)
|
|
611
|
+
import_data = normalize_agent_config_for_import(import_data, model)
|
|
859
612
|
|
|
860
613
|
# Merge CLI args with imported data
|
|
861
614
|
cli_args = {
|
|
@@ -864,20 +617,35 @@ def create(
|
|
|
864
617
|
"model": model,
|
|
865
618
|
"tools": tools or (),
|
|
866
619
|
"agents": agents or (),
|
|
620
|
+
"mcps": mcps or (),
|
|
867
621
|
"timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
|
|
868
622
|
}
|
|
869
623
|
|
|
870
|
-
merged_data =
|
|
871
|
-
|
|
872
|
-
#
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
624
|
+
merged_data = merge_import_with_cli_args(import_data, cli_args)
|
|
625
|
+
else:
|
|
626
|
+
# No import file - use CLI args directly
|
|
627
|
+
merged_data = {
|
|
628
|
+
"name": name,
|
|
629
|
+
"instruction": instruction,
|
|
630
|
+
"model": model,
|
|
631
|
+
"tools": tools or (),
|
|
632
|
+
"agents": agents or (),
|
|
633
|
+
"mcps": mcps or (),
|
|
634
|
+
"timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
|
|
635
|
+
}
|
|
879
636
|
|
|
880
|
-
#
|
|
637
|
+
# Extract merged values
|
|
638
|
+
name = merged_data.get("name")
|
|
639
|
+
instruction = merged_data.get("instruction")
|
|
640
|
+
model = merged_data.get("model")
|
|
641
|
+
tools = tuple(merged_data.get("tools", ()))
|
|
642
|
+
agents = tuple(merged_data.get("agents", ()))
|
|
643
|
+
mcps = tuple(merged_data.get("mcps", ()))
|
|
644
|
+
timeout = merged_data.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
645
|
+
# Coerce timeout to proper integer type
|
|
646
|
+
timeout = coerce_timeout(timeout)
|
|
647
|
+
|
|
648
|
+
# Validate required fields using centralized validators
|
|
881
649
|
if not name:
|
|
882
650
|
raise click.ClickException("Agent name is required (--name or --import)")
|
|
883
651
|
if not instruction:
|
|
@@ -885,6 +653,12 @@ def create(
|
|
|
885
653
|
"Agent instruction is required (--instruction or --import)"
|
|
886
654
|
)
|
|
887
655
|
|
|
656
|
+
# Apply validation
|
|
657
|
+
name = validate_agent_name(name)
|
|
658
|
+
instruction = validate_agent_instruction(instruction)
|
|
659
|
+
if timeout is not None:
|
|
660
|
+
timeout = validate_timeout(timeout)
|
|
661
|
+
|
|
888
662
|
# Resolve tool and agent references: accept names or IDs
|
|
889
663
|
resolved_tools = _resolve_resources_by_name(
|
|
890
664
|
client, tools, "tool", client.find_tools, "Tool"
|
|
@@ -892,6 +666,9 @@ def create(
|
|
|
892
666
|
resolved_agents = _resolve_resources_by_name(
|
|
893
667
|
client, agents, "agent", client.find_agents, "Agent"
|
|
894
668
|
)
|
|
669
|
+
resolved_mcps = _resolve_resources_by_name(
|
|
670
|
+
client, mcps, "mcp", client.find_mcps, "MCP"
|
|
671
|
+
)
|
|
895
672
|
|
|
896
673
|
# Create agent with comprehensive attribute support
|
|
897
674
|
create_kwargs = {
|
|
@@ -899,21 +676,28 @@ def create(
|
|
|
899
676
|
"instruction": instruction,
|
|
900
677
|
"tools": resolved_tools or None,
|
|
901
678
|
"agents": resolved_agents or None,
|
|
679
|
+
"mcps": resolved_mcps or None,
|
|
902
680
|
"timeout": timeout,
|
|
903
681
|
}
|
|
904
682
|
|
|
905
|
-
#
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
683
|
+
# Handle language model selection using helper function
|
|
684
|
+
lm_selection_dict, should_strip_lm_identity = resolve_language_model_selection(
|
|
685
|
+
merged_data, model
|
|
686
|
+
)
|
|
687
|
+
create_kwargs.update(lm_selection_dict)
|
|
688
|
+
|
|
689
|
+
# If importing from file, include agent_config (pass-through minus credentials)
|
|
690
|
+
if import_file:
|
|
691
|
+
agent_config_raw = (
|
|
692
|
+
merged_data.get("agent_config")
|
|
693
|
+
if isinstance(merged_data, dict)
|
|
694
|
+
else None
|
|
695
|
+
)
|
|
696
|
+
if isinstance(agent_config_raw, dict):
|
|
697
|
+
# If language_model_id is used, strip LM identity keys from agent_config to avoid conflicts
|
|
698
|
+
create_kwargs["agent_config"] = sanitize_agent_config(
|
|
699
|
+
agent_config_raw, strip_lm_identity=should_strip_lm_identity
|
|
700
|
+
)
|
|
917
701
|
|
|
918
702
|
# If importing from file, include all other detected attributes
|
|
919
703
|
if import_file:
|
|
@@ -922,15 +706,15 @@ def create(
|
|
|
922
706
|
"name",
|
|
923
707
|
"instruction",
|
|
924
708
|
"model",
|
|
709
|
+
"language_model_id",
|
|
925
710
|
"tools",
|
|
926
711
|
"agents",
|
|
927
712
|
"timeout",
|
|
713
|
+
"agent_config", # handled explicitly above
|
|
928
714
|
# System-only fields that shouldn't be passed to create_agent
|
|
929
715
|
"id",
|
|
930
716
|
"created_at",
|
|
931
717
|
"updated_at",
|
|
932
|
-
"agent_config",
|
|
933
|
-
"language_model_id",
|
|
934
718
|
"type",
|
|
935
719
|
"framework",
|
|
936
720
|
"version",
|
|
@@ -944,24 +728,139 @@ def create(
|
|
|
944
728
|
|
|
945
729
|
agent = client.agents.create_agent(**create_kwargs)
|
|
946
730
|
|
|
947
|
-
|
|
948
|
-
click.echo(json.dumps(agent.model_dump(), indent=2))
|
|
949
|
-
else:
|
|
950
|
-
# Rich output
|
|
951
|
-
_display_agent_creation_success(agent, model)
|
|
731
|
+
handle_json_output(ctx, agent.model_dump())
|
|
952
732
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
733
|
+
lm_display = getattr(agent, "model", None)
|
|
734
|
+
if not lm_display:
|
|
735
|
+
cfg = getattr(agent, "agent_config", {}) or {}
|
|
736
|
+
lm_display = (
|
|
737
|
+
cfg.get("lm_name")
|
|
738
|
+
or cfg.get("model")
|
|
739
|
+
or model
|
|
740
|
+
or f"{DEFAULT_MODEL} (backend default)"
|
|
741
|
+
)
|
|
956
742
|
|
|
957
|
-
|
|
743
|
+
handle_rich_output(
|
|
744
|
+
ctx,
|
|
745
|
+
display_creation_success(
|
|
746
|
+
"Agent",
|
|
747
|
+
agent.name,
|
|
748
|
+
agent.id,
|
|
749
|
+
Model=lm_display,
|
|
750
|
+
Type=getattr(agent, "type", "config"),
|
|
751
|
+
Framework=getattr(agent, "framework", "langchain"),
|
|
752
|
+
Version=getattr(agent, "version", "1.0"),
|
|
753
|
+
),
|
|
754
|
+
)
|
|
755
|
+
handle_rich_output(ctx, display_agent_run_suggestions(agent))
|
|
756
|
+
|
|
757
|
+
except (
|
|
758
|
+
click.ClickException
|
|
759
|
+
): # pragma: no cover - error formatting verified elsewhere
|
|
760
|
+
# Handle JSON output for ClickExceptions if view is JSON
|
|
958
761
|
if ctx.obj.get("view") == "json":
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
762
|
+
handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
|
|
763
|
+
# Re-raise ClickExceptions without additional processing
|
|
764
|
+
raise
|
|
765
|
+
except Exception as e: # pragma: no cover - defensive logging path
|
|
766
|
+
handle_json_output(ctx, error=e)
|
|
767
|
+
if ctx.obj.get("view") != "json":
|
|
768
|
+
print_api_error(e)
|
|
962
769
|
raise click.ClickException(str(e))
|
|
963
770
|
|
|
964
771
|
|
|
772
|
+
def _get_agent_for_update(client, agent_id):
|
|
773
|
+
"""Retrieve agent by ID for update operation."""
|
|
774
|
+
try:
|
|
775
|
+
return client.agents.get_agent_by_id(agent_id)
|
|
776
|
+
except Exception as e:
|
|
777
|
+
raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def _handle_update_import_file(import_file, name, instruction, tools, agents, timeout):
|
|
781
|
+
"""Handle import file processing for agent update."""
|
|
782
|
+
if not import_file:
|
|
783
|
+
return None, name, instruction, tools, agents, timeout
|
|
784
|
+
|
|
785
|
+
import_data = load_resource_from_file(Path(import_file), "agent")
|
|
786
|
+
import_data = convert_export_to_import_format(import_data)
|
|
787
|
+
import_data = normalize_agent_config_for_import(import_data, None)
|
|
788
|
+
|
|
789
|
+
cli_args = {
|
|
790
|
+
"name": name,
|
|
791
|
+
"instruction": instruction,
|
|
792
|
+
"tools": tools or (),
|
|
793
|
+
"agents": agents or (),
|
|
794
|
+
"timeout": timeout,
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
merged_data = merge_import_with_cli_args(import_data, cli_args)
|
|
798
|
+
|
|
799
|
+
return (
|
|
800
|
+
merged_data,
|
|
801
|
+
merged_data.get("name"),
|
|
802
|
+
merged_data.get("instruction"),
|
|
803
|
+
tuple(merged_data.get("tools", ())),
|
|
804
|
+
tuple(merged_data.get("agents", ())),
|
|
805
|
+
coerce_timeout(merged_data.get("timeout")),
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def _build_update_data(name, instruction, tools, agents, timeout):
|
|
810
|
+
"""Build the update data dictionary from provided parameters."""
|
|
811
|
+
update_data = {}
|
|
812
|
+
if name is not None:
|
|
813
|
+
update_data["name"] = name
|
|
814
|
+
if instruction is not None:
|
|
815
|
+
update_data["instruction"] = instruction
|
|
816
|
+
if tools:
|
|
817
|
+
update_data["tools"] = list(tools)
|
|
818
|
+
if agents:
|
|
819
|
+
update_data["agents"] = list(agents)
|
|
820
|
+
if timeout is not None:
|
|
821
|
+
update_data["timeout"] = timeout
|
|
822
|
+
return update_data
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _handle_update_import_config(import_file, merged_data, update_data):
|
|
826
|
+
"""Handle agent config and additional attributes for import-based updates."""
|
|
827
|
+
if not import_file:
|
|
828
|
+
return
|
|
829
|
+
|
|
830
|
+
lm_selection, should_strip_lm_identity = resolve_language_model_selection(
|
|
831
|
+
merged_data, None
|
|
832
|
+
)
|
|
833
|
+
update_data.update(lm_selection)
|
|
834
|
+
|
|
835
|
+
raw_cfg = merged_data.get("agent_config") if isinstance(merged_data, dict) else None
|
|
836
|
+
if isinstance(raw_cfg, dict):
|
|
837
|
+
update_data["agent_config"] = sanitize_agent_config(
|
|
838
|
+
raw_cfg, strip_lm_identity=should_strip_lm_identity
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
excluded_fields = {
|
|
842
|
+
"name",
|
|
843
|
+
"instruction",
|
|
844
|
+
"tools",
|
|
845
|
+
"agents",
|
|
846
|
+
"timeout",
|
|
847
|
+
"agent_config",
|
|
848
|
+
"language_model_id",
|
|
849
|
+
"id",
|
|
850
|
+
"created_at",
|
|
851
|
+
"updated_at",
|
|
852
|
+
"type",
|
|
853
|
+
"framework",
|
|
854
|
+
"version",
|
|
855
|
+
"tool_configs",
|
|
856
|
+
"mcps",
|
|
857
|
+
"a2a_profile",
|
|
858
|
+
}
|
|
859
|
+
for key, value in merged_data.items():
|
|
860
|
+
if key not in excluded_fields and value is not None:
|
|
861
|
+
update_data[key] = value
|
|
862
|
+
|
|
863
|
+
|
|
965
864
|
@agents_group.command()
|
|
966
865
|
@click.argument("agent_id")
|
|
967
866
|
@click.option("--name", help="New agent name")
|
|
@@ -986,96 +885,39 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout, import_file
|
|
|
986
885
|
"""
|
|
987
886
|
try:
|
|
988
887
|
client = get_client(ctx)
|
|
888
|
+
agent = _get_agent_for_update(client, agent_id)
|
|
989
889
|
|
|
990
|
-
#
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
890
|
+
# Handle import file processing
|
|
891
|
+
merged_data, name, instruction, tools, agents, timeout = (
|
|
892
|
+
_handle_update_import_file(
|
|
893
|
+
import_file, name, instruction, tools, agents, timeout
|
|
894
|
+
)
|
|
895
|
+
)
|
|
995
896
|
|
|
996
|
-
|
|
997
|
-
if import_file:
|
|
998
|
-
import_data = _load_agent_from_file(Path(import_file))
|
|
897
|
+
update_data = _build_update_data(name, instruction, tools, agents, timeout)
|
|
999
898
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
# Merge CLI args with imported data
|
|
1004
|
-
cli_args = {
|
|
1005
|
-
"name": name,
|
|
1006
|
-
"instruction": instruction,
|
|
1007
|
-
"tools": tools or (),
|
|
1008
|
-
"agents": agents or (),
|
|
1009
|
-
"timeout": timeout,
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
merged_data = _merge_import_with_cli_args(import_data, cli_args)
|
|
1013
|
-
|
|
1014
|
-
# Extract merged values
|
|
1015
|
-
name = merged_data.get("name")
|
|
1016
|
-
instruction = merged_data.get("instruction")
|
|
1017
|
-
tools = tuple(merged_data.get("tools", ()))
|
|
1018
|
-
agents = tuple(merged_data.get("agents", ()))
|
|
1019
|
-
timeout = merged_data.get("timeout")
|
|
1020
|
-
|
|
1021
|
-
# Build update data with comprehensive attribute support
|
|
1022
|
-
update_data = {}
|
|
1023
|
-
if name is not None:
|
|
1024
|
-
update_data["name"] = name
|
|
1025
|
-
if instruction is not None:
|
|
1026
|
-
update_data["instruction"] = instruction
|
|
1027
|
-
if tools:
|
|
1028
|
-
update_data["tools"] = list(tools)
|
|
1029
|
-
if agents:
|
|
1030
|
-
update_data["agents"] = list(agents)
|
|
1031
|
-
if timeout is not None:
|
|
1032
|
-
update_data["timeout"] = timeout
|
|
1033
|
-
|
|
1034
|
-
# If importing from file, include all other detected attributes
|
|
1035
|
-
if import_file:
|
|
1036
|
-
# Add all other attributes from import data (excluding already handled ones and system-only fields)
|
|
1037
|
-
excluded_fields = {
|
|
1038
|
-
"name",
|
|
1039
|
-
"instruction",
|
|
1040
|
-
"tools",
|
|
1041
|
-
"agents",
|
|
1042
|
-
"timeout",
|
|
1043
|
-
# System-only fields that shouldn't be passed to update_agent
|
|
1044
|
-
"id",
|
|
1045
|
-
"created_at",
|
|
1046
|
-
"updated_at",
|
|
1047
|
-
"agent_config",
|
|
1048
|
-
"type",
|
|
1049
|
-
"framework",
|
|
1050
|
-
"version",
|
|
1051
|
-
"tool_configs",
|
|
1052
|
-
"mcps",
|
|
1053
|
-
"a2a_profile",
|
|
1054
|
-
}
|
|
1055
|
-
for key, value in merged_data.items():
|
|
1056
|
-
if key not in excluded_fields and value is not None:
|
|
1057
|
-
update_data[key] = value
|
|
899
|
+
if merged_data:
|
|
900
|
+
_handle_update_import_config(import_file, merged_data, update_data)
|
|
1058
901
|
|
|
1059
902
|
if not update_data:
|
|
1060
903
|
raise click.ClickException("No update fields specified")
|
|
1061
904
|
|
|
1062
|
-
# Update agent
|
|
1063
905
|
updated_agent = client.agents.update_agent(agent.id, **update_data)
|
|
1064
906
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
_display_agent_update_success(updated_agent)
|
|
907
|
+
handle_json_output(ctx, updated_agent.model_dump())
|
|
908
|
+
handle_rich_output(ctx, display_update_success("Agent", updated_agent.name))
|
|
909
|
+
handle_rich_output(ctx, display_agent_run_suggestions(updated_agent))
|
|
1069
910
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
_display_run_suggestions(updated_agent)
|
|
1073
|
-
|
|
1074
|
-
except Exception as e:
|
|
911
|
+
except click.ClickException:
|
|
912
|
+
# Handle JSON output for ClickExceptions if view is JSON
|
|
1075
913
|
if ctx.obj.get("view") == "json":
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
914
|
+
handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
|
|
915
|
+
# Re-raise ClickExceptions without additional processing
|
|
916
|
+
raise
|
|
917
|
+
except Exception as e:
|
|
918
|
+
handle_json_output(ctx, error=e)
|
|
919
|
+
if ctx.obj.get("view") != "json":
|
|
920
|
+
print_api_error(e)
|
|
1079
921
|
raise click.ClickException(str(e))
|
|
1080
922
|
|
|
1081
923
|
|
|
@@ -1095,31 +937,86 @@ def delete(ctx, agent_id, yes):
|
|
|
1095
937
|
except Exception as e:
|
|
1096
938
|
raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
|
|
1097
939
|
|
|
1098
|
-
# Confirm deletion
|
|
1099
|
-
if not yes and not
|
|
1100
|
-
f"Are you sure you want to delete agent '{agent.name}'?"
|
|
1101
|
-
):
|
|
1102
|
-
if ctx.obj.get("view") != "json":
|
|
1103
|
-
console.print(Text("Deletion cancelled."))
|
|
940
|
+
# Confirm deletion when not forced
|
|
941
|
+
if not yes and not display_confirmation_prompt("Agent", agent.name):
|
|
1104
942
|
return
|
|
1105
943
|
|
|
1106
944
|
client.agents.delete_agent(agent.id)
|
|
1107
945
|
|
|
946
|
+
handle_json_output(
|
|
947
|
+
ctx,
|
|
948
|
+
{
|
|
949
|
+
"success": True,
|
|
950
|
+
"message": f"Agent '{agent.name}' deleted",
|
|
951
|
+
},
|
|
952
|
+
)
|
|
953
|
+
handle_rich_output(ctx, display_deletion_success("Agent", agent.name))
|
|
954
|
+
|
|
955
|
+
except click.ClickException:
|
|
956
|
+
# Handle JSON output for ClickExceptions if view is JSON
|
|
1108
957
|
if ctx.obj.get("view") == "json":
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
958
|
+
handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
|
|
959
|
+
# Re-raise ClickExceptions without additional processing
|
|
960
|
+
raise
|
|
961
|
+
except Exception as e:
|
|
962
|
+
handle_json_output(ctx, error=e)
|
|
963
|
+
if ctx.obj.get("view") != "json":
|
|
964
|
+
print_api_error(e)
|
|
965
|
+
raise click.ClickException(str(e))
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
@agents_group.command()
|
|
969
|
+
@click.option(
|
|
970
|
+
"--base-url",
|
|
971
|
+
help="Custom LangFlow server base URL (overrides LANGFLOW_BASE_URL env var)",
|
|
972
|
+
)
|
|
973
|
+
@click.option(
|
|
974
|
+
"--api-key", help="Custom LangFlow API key (overrides LANGFLOW_API_KEY env var)"
|
|
975
|
+
)
|
|
976
|
+
@output_flags()
|
|
977
|
+
@click.pass_context
|
|
978
|
+
def sync_langflow(ctx, base_url, api_key): # pragma: no cover - integration-only path
|
|
979
|
+
"""Sync agents with LangFlow server flows.
|
|
980
|
+
|
|
981
|
+
This command fetches all flows from the configured LangFlow server and
|
|
982
|
+
creates/updates corresponding agents in the platform.
|
|
983
|
+
|
|
984
|
+
The LangFlow server configuration can be provided via:
|
|
985
|
+
- Command options (--base-url, --api-key)
|
|
986
|
+
- Environment variables (LANGFLOW_BASE_URL, LANGFLOW_API_KEY)
|
|
987
|
+
|
|
988
|
+
Examples:
|
|
989
|
+
aip agents sync-langflow
|
|
990
|
+
aip agents sync-langflow --base-url https://my-langflow.com --api-key my-key
|
|
991
|
+
"""
|
|
992
|
+
try:
|
|
993
|
+
client = get_client(ctx)
|
|
994
|
+
|
|
995
|
+
# Perform the sync
|
|
996
|
+
result = client.sync_langflow_agents(base_url=base_url, api_key=api_key)
|
|
997
|
+
|
|
998
|
+
# Handle output format
|
|
999
|
+
handle_json_output(ctx, result)
|
|
1000
|
+
|
|
1001
|
+
# Show success message for non-JSON output
|
|
1002
|
+
if ctx.obj.get("view") != "json":
|
|
1003
|
+
from rich.text import Text
|
|
1004
|
+
|
|
1005
|
+
# Extract some useful info from the result
|
|
1006
|
+
success_count = result.get("data", {}).get("created_count", 0) + result.get(
|
|
1007
|
+
"data", {}
|
|
1008
|
+
).get("updated_count", 0)
|
|
1009
|
+
total_count = result.get("data", {}).get("total_processed", 0)
|
|
1010
|
+
|
|
1011
|
+
handle_rich_output(
|
|
1012
|
+
ctx,
|
|
1013
|
+
Text(
|
|
1014
|
+
f"[green]✅ Successfully synced {success_count} LangFlow agents ({total_count} total processed)[/green]"
|
|
1015
|
+
),
|
|
1118
1016
|
)
|
|
1119
1017
|
|
|
1120
1018
|
except Exception as e:
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
console.print(Text(f"[red]Error deleting agent: {e}[/red]"))
|
|
1019
|
+
handle_json_output(ctx, error=e)
|
|
1020
|
+
if ctx.obj.get("view") != "json":
|
|
1021
|
+
print_api_error(e)
|
|
1125
1022
|
raise click.ClickException(str(e))
|