glaip-sdk 0.0.3__py3-none-any.whl → 0.0.4__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/branding.py +145 -0
- glaip_sdk/cli/commands/agents.py +734 -116
- glaip_sdk/cli/commands/configure.py +17 -17
- glaip_sdk/cli/commands/init.py +32 -116
- glaip_sdk/cli/commands/mcps.py +13 -8
- glaip_sdk/cli/commands/tools.py +12 -9
- glaip_sdk/cli/main.py +14 -3
- glaip_sdk/cli/utils.py +136 -18
- glaip_sdk/client/agents.py +23 -2
- glaip_sdk/client/tools.py +183 -95
- glaip_sdk/models.py +1 -1
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/METADATA +1 -1
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/RECORD +15 -14
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/agents.py
CHANGED
|
@@ -5,17 +5,22 @@ Authors:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
+
import os
|
|
8
9
|
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
9
12
|
|
|
10
13
|
import click
|
|
11
14
|
from rich.console import Console
|
|
12
15
|
from rich.panel import Panel
|
|
16
|
+
from rich.text import Text
|
|
13
17
|
|
|
14
18
|
from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
|
|
15
19
|
from glaip_sdk.exceptions import AgentTimeoutError
|
|
16
20
|
from glaip_sdk.utils import is_uuid
|
|
17
21
|
|
|
18
22
|
from ..utils import (
|
|
23
|
+
_fuzzy_pick_for_resources,
|
|
19
24
|
build_renderer,
|
|
20
25
|
coerce_to_row,
|
|
21
26
|
get_client,
|
|
@@ -28,6 +33,31 @@ from ..utils import (
|
|
|
28
33
|
console = Console()
|
|
29
34
|
|
|
30
35
|
|
|
36
|
+
def _display_run_suggestions(agent):
|
|
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
|
+
|
|
31
61
|
def _format_datetime(dt):
|
|
32
62
|
"""Format datetime object to readable string."""
|
|
33
63
|
if isinstance(dt, datetime):
|
|
@@ -37,14 +67,454 @@ def _format_datetime(dt):
|
|
|
37
67
|
return dt
|
|
38
68
|
|
|
39
69
|
|
|
70
|
+
def _fetch_full_agent_details(client, agent):
|
|
71
|
+
"""Fetch full agent details by ID to ensure all fields are populated."""
|
|
72
|
+
try:
|
|
73
|
+
agent_id = str(getattr(agent, "id", "")).strip()
|
|
74
|
+
if agent_id:
|
|
75
|
+
return client.agents.get_agent_by_id(agent_id)
|
|
76
|
+
except Exception:
|
|
77
|
+
# If fetching full details fails, continue with the resolved object
|
|
78
|
+
pass
|
|
79
|
+
return agent
|
|
80
|
+
|
|
81
|
+
|
|
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
|
+
def _get_agent_model_name(agent):
|
|
190
|
+
"""Extract model name from agent configuration."""
|
|
191
|
+
# Try different possible locations for model name
|
|
192
|
+
if hasattr(agent, "agent_config") and agent.agent_config:
|
|
193
|
+
if isinstance(agent.agent_config, dict):
|
|
194
|
+
return agent.agent_config.get("lm_name") or agent.agent_config.get("model")
|
|
195
|
+
|
|
196
|
+
if hasattr(agent, "model") and agent.model:
|
|
197
|
+
return agent.model
|
|
198
|
+
|
|
199
|
+
# Default fallback
|
|
200
|
+
return DEFAULT_MODEL
|
|
201
|
+
|
|
202
|
+
|
|
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
|
+
def _resolve_resources_by_name(
|
|
427
|
+
client, items: tuple[str, ...], resource_type: str, find_func, label: str
|
|
428
|
+
) -> list[str]:
|
|
429
|
+
"""Resolve resource names/IDs to IDs, handling ambiguity.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
client: API client
|
|
433
|
+
items: Tuple of resource names/IDs
|
|
434
|
+
resource_type: Type of resource ("tool" or "agent")
|
|
435
|
+
find_func: Function to find resources by name
|
|
436
|
+
label: Label for error messages
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
List of resolved resource IDs
|
|
440
|
+
"""
|
|
441
|
+
out = []
|
|
442
|
+
for ref in list(items or ()):
|
|
443
|
+
if is_uuid(ref):
|
|
444
|
+
out.append(ref)
|
|
445
|
+
continue
|
|
446
|
+
|
|
447
|
+
matches = find_func(name=ref)
|
|
448
|
+
if not matches:
|
|
449
|
+
raise click.ClickException(f"{label} not found: {ref}")
|
|
450
|
+
if len(matches) > 1:
|
|
451
|
+
raise click.ClickException(
|
|
452
|
+
f"Multiple {resource_type}s named '{ref}'. Use ID instead."
|
|
453
|
+
)
|
|
454
|
+
out.append(str(matches[0].id))
|
|
455
|
+
return out
|
|
456
|
+
|
|
457
|
+
|
|
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
|
+
def _display_agent_details(ctx, client, agent):
|
|
490
|
+
"""Display full agent details in a standardized format."""
|
|
491
|
+
# Fetch full details to ensure all fields are populated
|
|
492
|
+
full_agent = _fetch_full_agent_details(client, agent)
|
|
493
|
+
|
|
494
|
+
# Build result data
|
|
495
|
+
result_data = _build_agent_result_data(full_agent)
|
|
496
|
+
|
|
497
|
+
# Display using output_result
|
|
498
|
+
output_result(
|
|
499
|
+
ctx,
|
|
500
|
+
result_data,
|
|
501
|
+
title="Agent Details",
|
|
502
|
+
panel_title=f"🤖 {full_agent.name}",
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
40
506
|
@click.group(name="agents", no_args_is_help=True)
|
|
41
507
|
def agents_group():
|
|
42
508
|
"""Agent management operations."""
|
|
43
509
|
pass
|
|
44
510
|
|
|
45
511
|
|
|
46
|
-
def _resolve_agent(ctx, client, ref, select=None):
|
|
47
|
-
"""Resolve agent reference (ID or name) with ambiguity handling.
|
|
512
|
+
def _resolve_agent(ctx, client, ref, select=None, interface_preference="fuzzy"):
|
|
513
|
+
"""Resolve agent reference (ID or name) with ambiguity handling.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list
|
|
517
|
+
"""
|
|
48
518
|
return resolve_resource(
|
|
49
519
|
ctx,
|
|
50
520
|
ref,
|
|
@@ -52,13 +522,17 @@ def _resolve_agent(ctx, client, ref, select=None):
|
|
|
52
522
|
find_by_name=client.agents.find_agents,
|
|
53
523
|
label="Agent",
|
|
54
524
|
select=select,
|
|
525
|
+
interface_preference=interface_preference,
|
|
55
526
|
)
|
|
56
527
|
|
|
57
528
|
|
|
58
529
|
@agents_group.command(name="list")
|
|
530
|
+
@click.option(
|
|
531
|
+
"--simple", is_flag=True, help="Show simple table without interactive picker"
|
|
532
|
+
)
|
|
59
533
|
@output_flags()
|
|
60
534
|
@click.pass_context
|
|
61
|
-
def list_agents(ctx):
|
|
535
|
+
def list_agents(ctx, simple):
|
|
62
536
|
"""List all agents."""
|
|
63
537
|
try:
|
|
64
538
|
client = get_client(ctx)
|
|
@@ -80,6 +554,15 @@ def list_agents(ctx):
|
|
|
80
554
|
row["id"] = str(row["id"])
|
|
81
555
|
return row
|
|
82
556
|
|
|
557
|
+
# Use fuzzy picker for interactive agent selection and details (default behavior)
|
|
558
|
+
# Skip if --simple flag is used
|
|
559
|
+
if not simple and console.is_terminal and os.isatty(1) and len(agents) > 0:
|
|
560
|
+
picked_agent = _fuzzy_pick_for_resources(agents, "agent", "")
|
|
561
|
+
if picked_agent:
|
|
562
|
+
_display_agent_details(ctx, client, picked_agent)
|
|
563
|
+
return
|
|
564
|
+
|
|
565
|
+
# Show simple table (either --simple flag or non-interactive)
|
|
83
566
|
output_list(ctx, agents, "🤖 Available Agents", columns, transform_agent)
|
|
84
567
|
|
|
85
568
|
except Exception as e:
|
|
@@ -89,50 +572,62 @@ def list_agents(ctx):
|
|
|
89
572
|
@agents_group.command()
|
|
90
573
|
@click.argument("agent_ref")
|
|
91
574
|
@click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
|
|
575
|
+
@click.option(
|
|
576
|
+
"--export",
|
|
577
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
578
|
+
help="Export complete agent configuration to file (format auto-detected from .json/.yaml extension)",
|
|
579
|
+
)
|
|
92
580
|
@output_flags()
|
|
93
581
|
@click.pass_context
|
|
94
|
-
def get(ctx, agent_ref, select):
|
|
95
|
-
"""Get agent details.
|
|
582
|
+
def get(ctx, agent_ref, select, export):
|
|
583
|
+
"""Get agent details.
|
|
584
|
+
|
|
585
|
+
Examples:
|
|
586
|
+
aip agents get my-agent
|
|
587
|
+
aip agents get my-agent --export agent.json # Exports complete configuration as JSON
|
|
588
|
+
aip agents get my-agent --export agent.yaml # Exports complete configuration as YAML
|
|
589
|
+
"""
|
|
96
590
|
try:
|
|
97
591
|
client = get_client(ctx)
|
|
98
592
|
|
|
99
|
-
# Resolve agent with ambiguity handling
|
|
100
|
-
agent = _resolve_agent(
|
|
593
|
+
# Resolve agent with ambiguity handling - use questionary interface for traditional UX
|
|
594
|
+
agent = _resolve_agent(
|
|
595
|
+
ctx, client, agent_ref, select, interface_preference="questionary"
|
|
596
|
+
)
|
|
101
597
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
pass
|
|
598
|
+
# Handle export option
|
|
599
|
+
if export:
|
|
600
|
+
export_path = Path(export)
|
|
601
|
+
# Auto-detect format from file extension
|
|
602
|
+
if export_path.suffix.lower() in [".yaml", ".yml"]:
|
|
603
|
+
detected_format = "yaml"
|
|
604
|
+
else:
|
|
605
|
+
detected_format = "json"
|
|
111
606
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"updated_at": _format_datetime(getattr(agent, "updated_at", "N/A")),
|
|
123
|
-
"metadata": getattr(agent, "metadata", "N/A"),
|
|
124
|
-
"language_model_id": getattr(agent, "language_model_id", "N/A"),
|
|
125
|
-
"agent_config": getattr(agent, "agent_config", "N/A"),
|
|
126
|
-
"tool_configs": agent.tool_configs or {},
|
|
127
|
-
"tools": getattr(agent, "tools", []),
|
|
128
|
-
"agents": getattr(agent, "agents", []),
|
|
129
|
-
"mcps": getattr(agent, "mcps", []),
|
|
130
|
-
"a2a_profile": getattr(agent, "a2a_profile", "N/A"),
|
|
131
|
-
}
|
|
607
|
+
# Always export comprehensive data - re-fetch agent with full details
|
|
608
|
+
try:
|
|
609
|
+
agent = client.agents.get_agent_by_id(agent.id)
|
|
610
|
+
except Exception as e:
|
|
611
|
+
console.print(
|
|
612
|
+
Text(f"[yellow]⚠️ Could not fetch full agent details: {e}[/yellow]")
|
|
613
|
+
)
|
|
614
|
+
console.print(
|
|
615
|
+
Text("[yellow]⚠️ Proceeding with available data[/yellow]")
|
|
616
|
+
)
|
|
132
617
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
618
|
+
_export_agent_to_file(agent, export_path, detected_format)
|
|
619
|
+
console.print(
|
|
620
|
+
Text(
|
|
621
|
+
f"[green]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/green]"
|
|
622
|
+
)
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
# Display full agent details using the standardized helper
|
|
626
|
+
_display_agent_details(ctx, client, agent)
|
|
627
|
+
|
|
628
|
+
# Show run suggestions (only in rich mode, not JSON)
|
|
629
|
+
if ctx.obj.get("view") != "json":
|
|
630
|
+
_display_run_suggestions(agent)
|
|
136
631
|
|
|
137
632
|
except Exception as e:
|
|
138
633
|
raise click.ClickException(str(e))
|
|
@@ -140,8 +635,9 @@ def get(ctx, agent_ref, select):
|
|
|
140
635
|
|
|
141
636
|
@agents_group.command()
|
|
142
637
|
@click.argument("agent_ref")
|
|
638
|
+
@click.argument("input_text", required=False)
|
|
143
639
|
@click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
|
|
144
|
-
@click.option("--input", "
|
|
640
|
+
@click.option("--input", "input_option", help="Input text for the agent")
|
|
145
641
|
@click.option("--chat-history", help="JSON string of chat history")
|
|
146
642
|
@click.option(
|
|
147
643
|
"--timeout",
|
|
@@ -173,18 +669,38 @@ def run(
|
|
|
173
669
|
agent_ref,
|
|
174
670
|
select,
|
|
175
671
|
input_text,
|
|
672
|
+
input_option,
|
|
176
673
|
chat_history,
|
|
177
674
|
timeout,
|
|
178
675
|
save,
|
|
179
676
|
files,
|
|
180
677
|
verbose,
|
|
181
678
|
):
|
|
182
|
-
"""Run an agent with input text
|
|
679
|
+
"""Run an agent with input text.
|
|
680
|
+
|
|
681
|
+
Usage: aip agents run <agent_ref> <input_text> [OPTIONS]
|
|
682
|
+
|
|
683
|
+
Examples:
|
|
684
|
+
aip agents run my-agent "Hello world"
|
|
685
|
+
aip agents run agent-123 "Process this data" --timeout 600
|
|
686
|
+
aip agents run my-agent --input "Hello world" # Legacy style
|
|
687
|
+
"""
|
|
688
|
+
# Handle input precedence: --input option overrides positional argument
|
|
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
|
+
)
|
|
696
|
+
|
|
183
697
|
try:
|
|
184
698
|
client = get_client(ctx)
|
|
185
699
|
|
|
186
|
-
# Resolve agent by ID or name (align with other commands)
|
|
187
|
-
agent = _resolve_agent(
|
|
700
|
+
# Resolve agent by ID or name (align with other commands) - use fuzzy interface
|
|
701
|
+
agent = _resolve_agent(
|
|
702
|
+
ctx, client, agent_ref, select, interface_preference="fuzzy"
|
|
703
|
+
)
|
|
188
704
|
|
|
189
705
|
# Parse chat history if provided
|
|
190
706
|
parsed_chat_history = None
|
|
@@ -215,7 +731,7 @@ def run(
|
|
|
215
731
|
# Ensure timeout is applied to the root client and subclients share its session
|
|
216
732
|
run_kwargs = {
|
|
217
733
|
"agent_id": agent.id,
|
|
218
|
-
"message":
|
|
734
|
+
"message": final_input_text,
|
|
219
735
|
"files": list(files),
|
|
220
736
|
"agent_name": agent.name, # Pass agent name for better display
|
|
221
737
|
"tty": tty_enabled,
|
|
@@ -276,7 +792,7 @@ def run(
|
|
|
276
792
|
|
|
277
793
|
with open(save, "w", encoding="utf-8") as f:
|
|
278
794
|
f.write(content)
|
|
279
|
-
console.print(f"[green]Full debug output saved to: {save}[/green]")
|
|
795
|
+
console.print(Text(f"[green]Full debug output saved to: {save}[/green]"))
|
|
280
796
|
|
|
281
797
|
except AgentTimeoutError as e:
|
|
282
798
|
# Handle agent timeout errors with specific messages
|
|
@@ -293,8 +809,8 @@ def run(
|
|
|
293
809
|
|
|
294
810
|
|
|
295
811
|
@agents_group.command()
|
|
296
|
-
@click.option("--name",
|
|
297
|
-
@click.option("--instruction",
|
|
812
|
+
@click.option("--name", help="Agent name")
|
|
813
|
+
@click.option("--instruction", help="Agent instruction (prompt)")
|
|
298
814
|
@click.option(
|
|
299
815
|
"--model",
|
|
300
816
|
help=f"Language model to use (e.g., {DEFAULT_MODEL}, default: {DEFAULT_MODEL})",
|
|
@@ -307,6 +823,12 @@ def run(
|
|
|
307
823
|
type=int,
|
|
308
824
|
help="Agent execution timeout in seconds (default: 300s)",
|
|
309
825
|
)
|
|
826
|
+
@click.option(
|
|
827
|
+
"--import",
|
|
828
|
+
"import_file",
|
|
829
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
830
|
+
help="Import agent configuration from JSON file",
|
|
831
|
+
)
|
|
310
832
|
@output_flags()
|
|
311
833
|
@click.pass_context
|
|
312
834
|
def create(
|
|
@@ -317,48 +839,61 @@ def create(
|
|
|
317
839
|
tools,
|
|
318
840
|
agents,
|
|
319
841
|
timeout,
|
|
842
|
+
import_file,
|
|
320
843
|
):
|
|
321
|
-
"""Create a new agent.
|
|
844
|
+
"""Create a new agent.
|
|
845
|
+
|
|
846
|
+
Examples:
|
|
847
|
+
aip agents create --name "My Agent" --instruction "You are a helpful assistant"
|
|
848
|
+
aip agents create --import agent.json
|
|
849
|
+
"""
|
|
322
850
|
try:
|
|
323
851
|
client = get_client(ctx)
|
|
324
852
|
|
|
853
|
+
# Handle import from file
|
|
854
|
+
if import_file:
|
|
855
|
+
import_data = _load_agent_from_file(Path(import_file))
|
|
856
|
+
|
|
857
|
+
# Convert export format to import-compatible format
|
|
858
|
+
import_data = _convert_export_to_import_format(import_data)
|
|
859
|
+
|
|
860
|
+
# Merge CLI args with imported data
|
|
861
|
+
cli_args = {
|
|
862
|
+
"name": name,
|
|
863
|
+
"instruction": instruction,
|
|
864
|
+
"model": model,
|
|
865
|
+
"tools": tools or (),
|
|
866
|
+
"agents": agents or (),
|
|
867
|
+
"timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
merged_data = _merge_import_with_cli_args(import_data, cli_args)
|
|
871
|
+
|
|
872
|
+
# Extract merged values
|
|
873
|
+
name = merged_data.get("name")
|
|
874
|
+
instruction = merged_data.get("instruction")
|
|
875
|
+
model = merged_data.get("model")
|
|
876
|
+
tools = tuple(merged_data.get("tools", ()))
|
|
877
|
+
agents = tuple(merged_data.get("agents", ()))
|
|
878
|
+
timeout = merged_data.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
879
|
+
|
|
880
|
+
# Validate required fields
|
|
881
|
+
if not name:
|
|
882
|
+
raise click.ClickException("Agent name is required (--name or --import)")
|
|
883
|
+
if not instruction:
|
|
884
|
+
raise click.ClickException(
|
|
885
|
+
"Agent instruction is required (--instruction or --import)"
|
|
886
|
+
)
|
|
887
|
+
|
|
325
888
|
# Resolve tool and agent references: accept names or IDs
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
raise click.ClickException(f"Tool not found: {ref}")
|
|
335
|
-
if len(matches) > 1:
|
|
336
|
-
raise click.ClickException(
|
|
337
|
-
f"Multiple tools named '{ref}'. Use ID instead."
|
|
338
|
-
)
|
|
339
|
-
out.append(str(matches[0].id))
|
|
340
|
-
return out
|
|
341
|
-
|
|
342
|
-
def _resolve_agents(items: tuple[str, ...]) -> list[str]:
|
|
343
|
-
out: list[str] = []
|
|
344
|
-
for ref in list(items or ()): # tuple -> list
|
|
345
|
-
if is_uuid(ref):
|
|
346
|
-
out.append(ref)
|
|
347
|
-
continue
|
|
348
|
-
matches = client.find_agents(name=ref)
|
|
349
|
-
if not matches:
|
|
350
|
-
raise click.ClickException(f"Agent not found: {ref}")
|
|
351
|
-
if len(matches) > 1:
|
|
352
|
-
raise click.ClickException(
|
|
353
|
-
f"Multiple agents named '{ref}'. Use ID instead."
|
|
354
|
-
)
|
|
355
|
-
out.append(str(matches[0].id))
|
|
356
|
-
return out
|
|
357
|
-
|
|
358
|
-
resolved_tools = _resolve_tools(tools)
|
|
359
|
-
resolved_agents = _resolve_agents(agents)
|
|
360
|
-
|
|
361
|
-
# Create agent with optional model specification
|
|
889
|
+
resolved_tools = _resolve_resources_by_name(
|
|
890
|
+
client, tools, "tool", client.find_tools, "Tool"
|
|
891
|
+
)
|
|
892
|
+
resolved_agents = _resolve_resources_by_name(
|
|
893
|
+
client, agents, "agent", client.find_agents, "Agent"
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
# Create agent with comprehensive attribute support
|
|
362
897
|
create_kwargs = {
|
|
363
898
|
"name": name,
|
|
364
899
|
"instruction": instruction,
|
|
@@ -367,9 +902,45 @@ def create(
|
|
|
367
902
|
"timeout": timeout,
|
|
368
903
|
}
|
|
369
904
|
|
|
370
|
-
# Add model if specified
|
|
905
|
+
# Add model if specified (prioritize CLI model over imported model)
|
|
371
906
|
if model:
|
|
372
907
|
create_kwargs["model"] = model
|
|
908
|
+
elif (
|
|
909
|
+
import_file
|
|
910
|
+
and "agent_config" in merged_data
|
|
911
|
+
and merged_data["agent_config"]
|
|
912
|
+
):
|
|
913
|
+
# Use lm_name from agent_config for cloning
|
|
914
|
+
agent_config = merged_data["agent_config"]
|
|
915
|
+
if isinstance(agent_config, dict) and "lm_name" in agent_config:
|
|
916
|
+
create_kwargs["model"] = agent_config["lm_name"]
|
|
917
|
+
|
|
918
|
+
# If importing from file, include all other detected attributes
|
|
919
|
+
if import_file:
|
|
920
|
+
# Add all other attributes from import data (excluding already handled ones and system-only fields)
|
|
921
|
+
excluded_fields = {
|
|
922
|
+
"name",
|
|
923
|
+
"instruction",
|
|
924
|
+
"model",
|
|
925
|
+
"tools",
|
|
926
|
+
"agents",
|
|
927
|
+
"timeout",
|
|
928
|
+
# System-only fields that shouldn't be passed to create_agent
|
|
929
|
+
"id",
|
|
930
|
+
"created_at",
|
|
931
|
+
"updated_at",
|
|
932
|
+
"agent_config",
|
|
933
|
+
"language_model_id",
|
|
934
|
+
"type",
|
|
935
|
+
"framework",
|
|
936
|
+
"version",
|
|
937
|
+
"tool_configs",
|
|
938
|
+
"mcps",
|
|
939
|
+
"a2a_profile",
|
|
940
|
+
}
|
|
941
|
+
for key, value in merged_data.items():
|
|
942
|
+
if key not in excluded_fields and value is not None:
|
|
943
|
+
create_kwargs[key] = value
|
|
373
944
|
|
|
374
945
|
agent = client.agents.create_agent(**create_kwargs)
|
|
375
946
|
|
|
@@ -377,33 +948,17 @@ def create(
|
|
|
377
948
|
click.echo(json.dumps(agent.model_dump(), indent=2))
|
|
378
949
|
else:
|
|
379
950
|
# Rich output
|
|
380
|
-
|
|
381
|
-
if not lm:
|
|
382
|
-
cfg = getattr(agent, "agent_config", {}) or {}
|
|
383
|
-
lm = (
|
|
384
|
-
cfg.get("lm_name")
|
|
385
|
-
or cfg.get("model")
|
|
386
|
-
or model # Use CLI model if specified
|
|
387
|
-
or f"{DEFAULT_MODEL} (backend default)"
|
|
388
|
-
)
|
|
951
|
+
_display_agent_creation_success(agent, model)
|
|
389
952
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
f"Model: {lm}\n"
|
|
394
|
-
f"Type: {getattr(agent, 'type', 'config')}\n"
|
|
395
|
-
f"Framework: {getattr(agent, 'framework', 'langchain')}\n"
|
|
396
|
-
f"Version: {getattr(agent, 'version', '1.0')}",
|
|
397
|
-
title="🤖 Agent Created",
|
|
398
|
-
border_style="green",
|
|
399
|
-
)
|
|
400
|
-
console.print(panel)
|
|
953
|
+
# Show run suggestions (only in rich mode, not JSON)
|
|
954
|
+
if ctx.obj.get("view") != "json":
|
|
955
|
+
_display_run_suggestions(agent)
|
|
401
956
|
|
|
402
957
|
except Exception as e:
|
|
403
958
|
if ctx.obj.get("view") == "json":
|
|
404
959
|
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
405
960
|
else:
|
|
406
|
-
console.print(f"[red]Error creating agent: {e}[/red]")
|
|
961
|
+
console.print(Text(f"[red]Error creating agent: {e}[/red]"))
|
|
407
962
|
raise click.ClickException(str(e))
|
|
408
963
|
|
|
409
964
|
|
|
@@ -414,10 +969,21 @@ def create(
|
|
|
414
969
|
@click.option("--tools", multiple=True, help="New tool names or IDs")
|
|
415
970
|
@click.option("--agents", multiple=True, help="New sub-agent names")
|
|
416
971
|
@click.option("--timeout", type=int, help="New timeout value")
|
|
972
|
+
@click.option(
|
|
973
|
+
"--import",
|
|
974
|
+
"import_file",
|
|
975
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
976
|
+
help="Import agent configuration from JSON file",
|
|
977
|
+
)
|
|
417
978
|
@output_flags()
|
|
418
979
|
@click.pass_context
|
|
419
|
-
def update(ctx, agent_id, name, instruction, tools, agents, timeout):
|
|
420
|
-
"""Update an existing agent.
|
|
980
|
+
def update(ctx, agent_id, name, instruction, tools, agents, timeout, import_file):
|
|
981
|
+
"""Update an existing agent.
|
|
982
|
+
|
|
983
|
+
Examples:
|
|
984
|
+
aip agents update my-agent --instruction "New instruction"
|
|
985
|
+
aip agents update my-agent --import agent.json
|
|
986
|
+
"""
|
|
421
987
|
try:
|
|
422
988
|
client = get_client(ctx)
|
|
423
989
|
|
|
@@ -427,7 +993,32 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout):
|
|
|
427
993
|
except Exception as e:
|
|
428
994
|
raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
|
|
429
995
|
|
|
430
|
-
#
|
|
996
|
+
# Handle import from file
|
|
997
|
+
if import_file:
|
|
998
|
+
import_data = _load_agent_from_file(Path(import_file))
|
|
999
|
+
|
|
1000
|
+
# Convert export format to import-compatible format
|
|
1001
|
+
import_data = _convert_export_to_import_format(import_data)
|
|
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
|
|
431
1022
|
update_data = {}
|
|
432
1023
|
if name is not None:
|
|
433
1024
|
update_data["name"] = name
|
|
@@ -440,6 +1031,31 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout):
|
|
|
440
1031
|
if timeout is not None:
|
|
441
1032
|
update_data["timeout"] = timeout
|
|
442
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
|
|
1058
|
+
|
|
443
1059
|
if not update_data:
|
|
444
1060
|
raise click.ClickException("No update fields specified")
|
|
445
1061
|
|
|
@@ -449,15 +1065,17 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout):
|
|
|
449
1065
|
if ctx.obj.get("view") == "json":
|
|
450
1066
|
click.echo(json.dumps(updated_agent.model_dump(), indent=2))
|
|
451
1067
|
else:
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
)
|
|
1068
|
+
_display_agent_update_success(updated_agent)
|
|
1069
|
+
|
|
1070
|
+
# Show run suggestions (only in rich mode, not JSON)
|
|
1071
|
+
if ctx.obj.get("view") != "json":
|
|
1072
|
+
_display_run_suggestions(updated_agent)
|
|
455
1073
|
|
|
456
1074
|
except Exception as e:
|
|
457
1075
|
if ctx.obj.get("view") == "json":
|
|
458
1076
|
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
459
1077
|
else:
|
|
460
|
-
console.print(f"[red]Error updating agent: {e}[/red]")
|
|
1078
|
+
console.print(Text(f"[red]Error updating agent: {e}[/red]"))
|
|
461
1079
|
raise click.ClickException(str(e))
|
|
462
1080
|
|
|
463
1081
|
|
|
@@ -482,7 +1100,7 @@ def delete(ctx, agent_id, yes):
|
|
|
482
1100
|
f"Are you sure you want to delete agent '{agent.name}'?"
|
|
483
1101
|
):
|
|
484
1102
|
if ctx.obj.get("view") != "json":
|
|
485
|
-
console.print("Deletion cancelled.")
|
|
1103
|
+
console.print(Text("Deletion cancelled."))
|
|
486
1104
|
return
|
|
487
1105
|
|
|
488
1106
|
client.agents.delete_agent(agent.id)
|
|
@@ -496,12 +1114,12 @@ def delete(ctx, agent_id, yes):
|
|
|
496
1114
|
)
|
|
497
1115
|
else:
|
|
498
1116
|
console.print(
|
|
499
|
-
f"[green]✅ Agent '{agent.name}' deleted successfully[/green]"
|
|
1117
|
+
Text(f"[green]✅ Agent '{agent.name}' deleted successfully[/green]")
|
|
500
1118
|
)
|
|
501
1119
|
|
|
502
1120
|
except Exception as e:
|
|
503
1121
|
if ctx.obj.get("view") == "json":
|
|
504
1122
|
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
505
1123
|
else:
|
|
506
|
-
console.print(f"[red]Error deleting agent: {e}[/red]")
|
|
1124
|
+
console.print(Text(f"[red]Error deleting agent: {e}[/red]"))
|
|
507
1125
|
raise click.ClickException(str(e))
|