glaip-sdk 0.0.3__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 +146 -0
- 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 +786 -271
- glaip_sdk/cli/commands/configure.py +19 -19
- glaip_sdk/cli/commands/mcps.py +151 -141
- glaip_sdk/cli/commands/models.py +1 -1
- glaip_sdk/cli/commands/tools.py +252 -178
- glaip_sdk/cli/display.py +244 -0
- glaip_sdk/cli/io.py +106 -0
- glaip_sdk/cli/main.py +27 -20
- glaip_sdk/cli/resolution.py +59 -0
- glaip_sdk/cli/utils.py +372 -213
- glaip_sdk/cli/validators.py +235 -0
- glaip_sdk/client/__init__.py +3 -224
- glaip_sdk/client/agents.py +632 -171
- 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 +327 -104
- glaip_sdk/config/constants.py +10 -1
- glaip_sdk/models.py +43 -3
- 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.3.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 -177
- glaip_sdk-0.0.3.dist-info/RECORD +0 -40
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.5.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.5.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/agents.py
CHANGED
|
@@ -5,36 +5,207 @@ Authors:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
import click
|
|
11
12
|
from rich.console import Console
|
|
12
|
-
from rich.
|
|
13
|
+
from rich.text import Text
|
|
13
14
|
|
|
14
|
-
from glaip_sdk.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 (
|
|
46
|
+
_fuzzy_pick_for_resources,
|
|
19
47
|
build_renderer,
|
|
20
48
|
coerce_to_row,
|
|
21
49
|
get_client,
|
|
22
50
|
output_flags,
|
|
23
51
|
output_list,
|
|
24
52
|
output_result,
|
|
25
|
-
resolve_resource,
|
|
26
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
|
|
27
69
|
|
|
28
70
|
console = Console()
|
|
29
71
|
|
|
72
|
+
# Error message constants
|
|
73
|
+
AGENT_NOT_FOUND_ERROR = "Agent not found"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _fetch_full_agent_details(client, agent):
|
|
77
|
+
"""Fetch full agent details by ID to ensure all fields are populated."""
|
|
78
|
+
try:
|
|
79
|
+
agent_id = str(getattr(agent, "id", "")).strip()
|
|
80
|
+
if agent_id:
|
|
81
|
+
return client.agents.get_agent_by_id(agent_id)
|
|
82
|
+
except Exception:
|
|
83
|
+
# If fetching full details fails, continue with the resolved object
|
|
84
|
+
pass
|
|
85
|
+
return agent
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _get_agent_model_name(agent):
|
|
89
|
+
"""Extract model name from agent configuration."""
|
|
90
|
+
# Try different possible locations for model name
|
|
91
|
+
if hasattr(agent, "agent_config") and agent.agent_config:
|
|
92
|
+
if isinstance(agent.agent_config, dict):
|
|
93
|
+
return agent.agent_config.get("lm_name") or agent.agent_config.get("model")
|
|
94
|
+
|
|
95
|
+
if hasattr(agent, "model") and agent.model:
|
|
96
|
+
return agent.model
|
|
97
|
+
|
|
98
|
+
# Default fallback
|
|
99
|
+
return DEFAULT_MODEL
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _resolve_resources_by_name(
|
|
103
|
+
_client, items: tuple[str, ...], resource_type: str, find_func, label: str
|
|
104
|
+
) -> list[str]:
|
|
105
|
+
"""Resolve resource names/IDs to IDs, handling ambiguity.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
client: API client
|
|
109
|
+
items: Tuple of resource names/IDs
|
|
110
|
+
resource_type: Type of resource ("tool" or "agent")
|
|
111
|
+
find_func: Function to find resources by name
|
|
112
|
+
label: Label for error messages
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of resolved resource IDs
|
|
116
|
+
"""
|
|
117
|
+
out = []
|
|
118
|
+
for ref in list(items or ()):
|
|
119
|
+
if is_uuid(ref):
|
|
120
|
+
out.append(ref)
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
matches = find_func(name=ref)
|
|
124
|
+
if not matches:
|
|
125
|
+
raise click.ClickException(f"{label} not found: {ref}")
|
|
126
|
+
if len(matches) > 1:
|
|
127
|
+
raise click.ClickException(
|
|
128
|
+
f"Multiple {resource_type}s named '{ref}'. Use ID instead."
|
|
129
|
+
)
|
|
130
|
+
out.append(str(matches[0].id))
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _display_agent_details(ctx, client, agent):
|
|
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")
|
|
30
142
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
+
)
|
|
38
209
|
|
|
39
210
|
|
|
40
211
|
@click.group(name="agents", no_args_is_help=True)
|
|
@@ -43,26 +214,55 @@ def agents_group():
|
|
|
43
214
|
pass
|
|
44
215
|
|
|
45
216
|
|
|
46
|
-
def _resolve_agent(ctx, client, ref, select=None):
|
|
47
|
-
"""Resolve agent reference (ID or name) with ambiguity handling.
|
|
48
|
-
|
|
217
|
+
def _resolve_agent(ctx, client, ref, select=None, interface_preference="fuzzy"):
|
|
218
|
+
"""Resolve agent reference (ID or name) with ambiguity handling.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list
|
|
222
|
+
"""
|
|
223
|
+
return resolve_resource_reference(
|
|
49
224
|
ctx,
|
|
225
|
+
client,
|
|
50
226
|
ref,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
227
|
+
"agent",
|
|
228
|
+
client.agents.get_agent_by_id,
|
|
229
|
+
client.agents.find_agents,
|
|
230
|
+
"Agent",
|
|
54
231
|
select=select,
|
|
232
|
+
interface_preference=interface_preference,
|
|
55
233
|
)
|
|
56
234
|
|
|
57
235
|
|
|
58
236
|
@agents_group.command(name="list")
|
|
237
|
+
@click.option(
|
|
238
|
+
"--simple", is_flag=True, help="Show simple table without interactive picker"
|
|
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
|
+
)
|
|
59
253
|
@output_flags()
|
|
60
254
|
@click.pass_context
|
|
61
|
-
def list_agents(ctx):
|
|
62
|
-
"""List
|
|
255
|
+
def list_agents(ctx, simple, agent_type, framework, name, version, sync_langflow):
|
|
256
|
+
"""List agents with optional filtering."""
|
|
63
257
|
try:
|
|
64
258
|
client = get_client(ctx)
|
|
65
|
-
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
|
+
)
|
|
66
266
|
|
|
67
267
|
# Define table columns: (data_key, header, style, width)
|
|
68
268
|
columns = [
|
|
@@ -80,6 +280,17 @@ def list_agents(ctx):
|
|
|
80
280
|
row["id"] = str(row["id"])
|
|
81
281
|
return row
|
|
82
282
|
|
|
283
|
+
# Use fuzzy picker for interactive agent selection and details (default behavior)
|
|
284
|
+
# Skip if --simple flag is used
|
|
285
|
+
if not simple and console.is_terminal and os.isatty(1) and len(agents) > 0:
|
|
286
|
+
picked_agent = _fuzzy_pick_for_resources(agents, "agent", "")
|
|
287
|
+
if picked_agent:
|
|
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))
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
# Show simple table (either --simple flag or non-interactive)
|
|
83
294
|
output_list(ctx, agents, "🤖 Available Agents", columns, transform_agent)
|
|
84
295
|
|
|
85
296
|
except Exception as e:
|
|
@@ -89,59 +300,171 @@ def list_agents(ctx):
|
|
|
89
300
|
@agents_group.command()
|
|
90
301
|
@click.argument("agent_ref")
|
|
91
302
|
@click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
|
|
303
|
+
@click.option(
|
|
304
|
+
"--export",
|
|
305
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
306
|
+
help="Export complete agent configuration to file (format auto-detected from .json/.yaml extension)",
|
|
307
|
+
)
|
|
92
308
|
@output_flags()
|
|
93
309
|
@click.pass_context
|
|
94
|
-
def get(ctx, agent_ref, select):
|
|
95
|
-
"""Get agent details.
|
|
310
|
+
def get(ctx, agent_ref, select, export):
|
|
311
|
+
"""Get agent details.
|
|
312
|
+
|
|
313
|
+
Examples:
|
|
314
|
+
aip agents get my-agent
|
|
315
|
+
aip agents get my-agent --export agent.json # Exports complete configuration as JSON
|
|
316
|
+
aip agents get my-agent --export agent.yaml # Exports complete configuration as YAML
|
|
317
|
+
"""
|
|
96
318
|
try:
|
|
97
319
|
client = get_client(ctx)
|
|
98
320
|
|
|
99
|
-
# Resolve agent with ambiguity handling
|
|
100
|
-
agent = _resolve_agent(
|
|
321
|
+
# Resolve agent with ambiguity handling - use questionary interface for traditional UX
|
|
322
|
+
agent = _resolve_agent(
|
|
323
|
+
ctx, client, agent_ref, select, interface_preference="questionary"
|
|
324
|
+
)
|
|
101
325
|
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
pass
|
|
326
|
+
# Handle export option
|
|
327
|
+
if export: # pragma: no cover - requires filesystem verification
|
|
328
|
+
export_path = Path(export)
|
|
329
|
+
# Auto-detect format from file extension
|
|
330
|
+
if export_path.suffix.lower() in [".yaml", ".yml"]:
|
|
331
|
+
detected_format = "yaml"
|
|
332
|
+
else:
|
|
333
|
+
detected_format = "json"
|
|
111
334
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
}
|
|
335
|
+
# Always export comprehensive data - re-fetch agent with full details
|
|
336
|
+
try:
|
|
337
|
+
agent = client.agents.get_agent_by_id(agent.id)
|
|
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
|
+
),
|
|
344
|
+
)
|
|
345
|
+
handle_rich_output(
|
|
346
|
+
ctx, Text("[yellow]⚠️ Proceeding with available data[/yellow]")
|
|
347
|
+
)
|
|
132
348
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
349
|
+
export_resource_to_file(agent, export_path, detected_format)
|
|
350
|
+
handle_rich_output(
|
|
351
|
+
ctx,
|
|
352
|
+
Text(
|
|
353
|
+
f"[green]✅ Complete agent configuration exported to: {export_path} (format: {detected_format})[/green]"
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Display full agent details using the standardized helper
|
|
358
|
+
_display_agent_details(ctx, client, agent)
|
|
359
|
+
|
|
360
|
+
# Show run suggestions via centralized display helper
|
|
361
|
+
handle_rich_output(ctx, display_agent_run_suggestions(agent))
|
|
136
362
|
|
|
137
363
|
except Exception as e:
|
|
138
364
|
raise click.ClickException(str(e))
|
|
139
365
|
|
|
140
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
|
+
|
|
141
463
|
@agents_group.command()
|
|
142
464
|
@click.argument("agent_ref")
|
|
465
|
+
@click.argument("input_text", required=False)
|
|
143
466
|
@click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
|
|
144
|
-
@click.option("--input", "
|
|
467
|
+
@click.option("--input", "input_option", help="Input text for the agent")
|
|
145
468
|
@click.option("--chat-history", help="JSON string of chat history")
|
|
146
469
|
@click.option(
|
|
147
470
|
"--timeout",
|
|
@@ -173,140 +496,83 @@ def run(
|
|
|
173
496
|
agent_ref,
|
|
174
497
|
select,
|
|
175
498
|
input_text,
|
|
499
|
+
input_option,
|
|
176
500
|
chat_history,
|
|
177
501
|
timeout,
|
|
178
502
|
save,
|
|
179
503
|
files,
|
|
180
504
|
verbose,
|
|
181
505
|
):
|
|
182
|
-
"""Run an agent with input text
|
|
183
|
-
try:
|
|
184
|
-
client = get_client(ctx)
|
|
506
|
+
"""Run an agent with input text.
|
|
185
507
|
|
|
186
|
-
|
|
187
|
-
agent = _resolve_agent(ctx, client, agent_ref, select)
|
|
508
|
+
Usage: aip agents run <agent_ref> <input_text> [OPTIONS]
|
|
188
509
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
raise click.ClickException("Invalid JSON in chat history")
|
|
510
|
+
Examples:
|
|
511
|
+
aip agents run my-agent "Hello world"
|
|
512
|
+
aip agents run agent-123 "Process this data" --timeout 600
|
|
513
|
+
aip agents run my-agent --input "Hello world" # Legacy style
|
|
514
|
+
"""
|
|
515
|
+
final_input_text = _validate_run_input(input_option, input_text)
|
|
196
516
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
renderer, working_console = build_renderer(
|
|
202
|
-
ctx,
|
|
203
|
-
save_path=save,
|
|
204
|
-
verbose=verbose,
|
|
205
|
-
tty_enabled=tty_enabled,
|
|
517
|
+
try:
|
|
518
|
+
client = get_client(ctx)
|
|
519
|
+
agent = _resolve_agent(
|
|
520
|
+
ctx, client, agent_ref, select, interface_preference="fuzzy"
|
|
206
521
|
)
|
|
207
522
|
|
|
208
|
-
|
|
209
|
-
|
|
523
|
+
parsed_chat_history = _parse_chat_history(chat_history)
|
|
524
|
+
renderer, working_console = _setup_run_renderer(ctx, save, verbose)
|
|
525
|
+
|
|
210
526
|
try:
|
|
211
527
|
client.timeout = float(timeout)
|
|
212
528
|
except Exception:
|
|
213
529
|
pass
|
|
214
530
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"tty"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
# Add optional parameters
|
|
225
|
-
if parsed_chat_history:
|
|
226
|
-
run_kwargs["chat_history"] = parsed_chat_history
|
|
227
|
-
|
|
228
|
-
# Pass custom renderer if available
|
|
229
|
-
if renderer is not None:
|
|
230
|
-
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
|
+
)
|
|
231
539
|
|
|
232
|
-
# Pass timeout to client (verbose mode is handled by the renderer)
|
|
233
540
|
result = client.agents.run_agent(**run_kwargs, timeout=timeout)
|
|
234
541
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
printed_by_renderer = bool(renderer)
|
|
238
|
-
|
|
239
|
-
# Resolve selected view from context (output_flags() stores it here)
|
|
240
|
-
selected_view = (ctx.obj or {}).get("view", "rich")
|
|
241
|
-
|
|
242
|
-
# Handle output format for fallback
|
|
243
|
-
# Only print here if nothing was printed by the renderer
|
|
244
|
-
if not printed_by_renderer:
|
|
245
|
-
if selected_view == "json":
|
|
246
|
-
click.echo(json.dumps({"output": result}, indent=2))
|
|
247
|
-
elif selected_view == "md":
|
|
248
|
-
click.echo(f"# Assistant\n\n{result}")
|
|
249
|
-
elif selected_view == "plain":
|
|
250
|
-
click.echo(result)
|
|
251
|
-
|
|
252
|
-
# Save transcript if requested
|
|
253
|
-
if save:
|
|
254
|
-
ext = (save.rsplit(".", 1)[-1] or "").lower()
|
|
255
|
-
if ext == "json":
|
|
256
|
-
# Save both the result and captured output
|
|
257
|
-
save_data = {
|
|
258
|
-
"output": result or "",
|
|
259
|
-
"full_debug_output": getattr(
|
|
260
|
-
working_console, "get_captured_output", lambda: ""
|
|
261
|
-
)(),
|
|
262
|
-
"timestamp": "captured during agent execution",
|
|
263
|
-
}
|
|
264
|
-
content = json.dumps(save_data, indent=2)
|
|
265
|
-
else:
|
|
266
|
-
# For markdown/text files, save the full captured output if available
|
|
267
|
-
# Get the full captured output including all tool panels and debug info (if available)
|
|
268
|
-
full_output = getattr(
|
|
269
|
-
working_console, "get_captured_output", lambda: ""
|
|
270
|
-
)()
|
|
271
|
-
if full_output:
|
|
272
|
-
content = f"# Agent Debug Log\n\n{full_output}\n\n---\n\n## Final Result\n\n{result or ''}\n"
|
|
273
|
-
else:
|
|
274
|
-
# Fallback to simple format
|
|
275
|
-
content = f"# Assistant\n\n{result or ''}\n"
|
|
276
|
-
|
|
277
|
-
with open(save, "w", encoding="utf-8") as f:
|
|
278
|
-
f.write(content)
|
|
279
|
-
console.print(f"[green]Full debug output saved to: {save}[/green]")
|
|
542
|
+
_handle_run_output(ctx, result, renderer)
|
|
543
|
+
_save_run_transcript(save, result, working_console)
|
|
280
544
|
|
|
281
545
|
except AgentTimeoutError as e:
|
|
282
|
-
# Handle agent timeout errors with specific messages
|
|
283
546
|
error_msg = str(e)
|
|
284
|
-
|
|
285
|
-
click.echo(json.dumps({"error": error_msg}, indent=2))
|
|
286
|
-
# Don't print the error message here - Click.ClickException will handle it
|
|
547
|
+
handle_json_output(ctx, error=Exception(error_msg))
|
|
287
548
|
raise click.ClickException(error_msg)
|
|
288
549
|
except Exception as e:
|
|
289
|
-
|
|
290
|
-
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
291
|
-
# Don't print the error message here - Click.ClickException will handle it
|
|
550
|
+
handle_json_output(ctx, error=e)
|
|
292
551
|
raise click.ClickException(str(e))
|
|
293
552
|
|
|
294
553
|
|
|
295
554
|
@agents_group.command()
|
|
296
|
-
@click.option("--name",
|
|
297
|
-
@click.option("--instruction",
|
|
555
|
+
@click.option("--name", help="Agent name")
|
|
556
|
+
@click.option("--instruction", help="Agent instruction (prompt)")
|
|
298
557
|
@click.option(
|
|
299
558
|
"--model",
|
|
300
559
|
help=f"Language model to use (e.g., {DEFAULT_MODEL}, default: {DEFAULT_MODEL})",
|
|
301
560
|
)
|
|
302
561
|
@click.option("--tools", multiple=True, help="Tool names or IDs to attach")
|
|
303
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")
|
|
304
564
|
@click.option(
|
|
305
565
|
"--timeout",
|
|
306
566
|
default=DEFAULT_AGENT_RUN_TIMEOUT,
|
|
307
567
|
type=int,
|
|
308
568
|
help="Agent execution timeout in seconds (default: 300s)",
|
|
309
569
|
)
|
|
570
|
+
@click.option(
|
|
571
|
+
"--import",
|
|
572
|
+
"import_file",
|
|
573
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
574
|
+
help="Import agent configuration from JSON file",
|
|
575
|
+
)
|
|
310
576
|
@output_flags()
|
|
311
577
|
@click.pass_context
|
|
312
578
|
def create(
|
|
@@ -316,97 +582,285 @@ def create(
|
|
|
316
582
|
model,
|
|
317
583
|
tools,
|
|
318
584
|
agents,
|
|
585
|
+
mcps,
|
|
319
586
|
timeout,
|
|
587
|
+
import_file,
|
|
320
588
|
):
|
|
321
|
-
"""Create a new agent.
|
|
589
|
+
"""Create a new agent.
|
|
590
|
+
|
|
591
|
+
Examples:
|
|
592
|
+
aip agents create --name "My Agent" --instruction "You are a helpful assistant"
|
|
593
|
+
aip agents create --import agent.json
|
|
594
|
+
"""
|
|
322
595
|
try:
|
|
323
596
|
client = get_client(ctx)
|
|
324
597
|
|
|
598
|
+
# Initialize merged_data for cases without import_file
|
|
599
|
+
merged_data = {}
|
|
600
|
+
|
|
601
|
+
# Handle import from file
|
|
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")
|
|
606
|
+
|
|
607
|
+
# Convert export format to import-compatible format
|
|
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)
|
|
612
|
+
|
|
613
|
+
# Merge CLI args with imported data
|
|
614
|
+
cli_args = {
|
|
615
|
+
"name": name,
|
|
616
|
+
"instruction": instruction,
|
|
617
|
+
"model": model,
|
|
618
|
+
"tools": tools or (),
|
|
619
|
+
"agents": agents or (),
|
|
620
|
+
"mcps": mcps or (),
|
|
621
|
+
"timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
|
|
622
|
+
}
|
|
623
|
+
|
|
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
|
+
}
|
|
636
|
+
|
|
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
|
|
649
|
+
if not name:
|
|
650
|
+
raise click.ClickException("Agent name is required (--name or --import)")
|
|
651
|
+
if not instruction:
|
|
652
|
+
raise click.ClickException(
|
|
653
|
+
"Agent instruction is required (--instruction or --import)"
|
|
654
|
+
)
|
|
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
|
+
|
|
325
662
|
# Resolve tool and agent references: accept names or IDs
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
|
663
|
+
resolved_tools = _resolve_resources_by_name(
|
|
664
|
+
client, tools, "tool", client.find_tools, "Tool"
|
|
665
|
+
)
|
|
666
|
+
resolved_agents = _resolve_resources_by_name(
|
|
667
|
+
client, agents, "agent", client.find_agents, "Agent"
|
|
668
|
+
)
|
|
669
|
+
resolved_mcps = _resolve_resources_by_name(
|
|
670
|
+
client, mcps, "mcp", client.find_mcps, "MCP"
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# Create agent with comprehensive attribute support
|
|
362
674
|
create_kwargs = {
|
|
363
675
|
"name": name,
|
|
364
676
|
"instruction": instruction,
|
|
365
677
|
"tools": resolved_tools or None,
|
|
366
678
|
"agents": resolved_agents or None,
|
|
679
|
+
"mcps": resolved_mcps or None,
|
|
367
680
|
"timeout": timeout,
|
|
368
681
|
}
|
|
369
682
|
|
|
370
|
-
#
|
|
371
|
-
|
|
372
|
-
|
|
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
|
+
)
|
|
701
|
+
|
|
702
|
+
# If importing from file, include all other detected attributes
|
|
703
|
+
if import_file:
|
|
704
|
+
# Add all other attributes from import data (excluding already handled ones and system-only fields)
|
|
705
|
+
excluded_fields = {
|
|
706
|
+
"name",
|
|
707
|
+
"instruction",
|
|
708
|
+
"model",
|
|
709
|
+
"language_model_id",
|
|
710
|
+
"tools",
|
|
711
|
+
"agents",
|
|
712
|
+
"timeout",
|
|
713
|
+
"agent_config", # handled explicitly above
|
|
714
|
+
# System-only fields that shouldn't be passed to create_agent
|
|
715
|
+
"id",
|
|
716
|
+
"created_at",
|
|
717
|
+
"updated_at",
|
|
718
|
+
"type",
|
|
719
|
+
"framework",
|
|
720
|
+
"version",
|
|
721
|
+
"tool_configs",
|
|
722
|
+
"mcps",
|
|
723
|
+
"a2a_profile",
|
|
724
|
+
}
|
|
725
|
+
for key, value in merged_data.items():
|
|
726
|
+
if key not in excluded_fields and value is not None:
|
|
727
|
+
create_kwargs[key] = value
|
|
373
728
|
|
|
374
729
|
agent = client.agents.create_agent(**create_kwargs)
|
|
375
730
|
|
|
376
|
-
|
|
377
|
-
click.echo(json.dumps(agent.model_dump(), indent=2))
|
|
378
|
-
else:
|
|
379
|
-
# Rich output
|
|
380
|
-
lm = getattr(agent, "model", None)
|
|
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
|
-
)
|
|
731
|
+
handle_json_output(ctx, agent.model_dump())
|
|
389
732
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
border_style="green",
|
|
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)"
|
|
399
741
|
)
|
|
400
|
-
console.print(panel)
|
|
401
742
|
|
|
402
|
-
|
|
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
|
|
403
761
|
if ctx.obj.get("view") == "json":
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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)
|
|
407
769
|
raise click.ClickException(str(e))
|
|
408
770
|
|
|
409
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
|
+
|
|
410
864
|
@agents_group.command()
|
|
411
865
|
@click.argument("agent_id")
|
|
412
866
|
@click.option("--name", help="New agent name")
|
|
@@ -414,50 +868,56 @@ def create(
|
|
|
414
868
|
@click.option("--tools", multiple=True, help="New tool names or IDs")
|
|
415
869
|
@click.option("--agents", multiple=True, help="New sub-agent names")
|
|
416
870
|
@click.option("--timeout", type=int, help="New timeout value")
|
|
871
|
+
@click.option(
|
|
872
|
+
"--import",
|
|
873
|
+
"import_file",
|
|
874
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
875
|
+
help="Import agent configuration from JSON file",
|
|
876
|
+
)
|
|
417
877
|
@output_flags()
|
|
418
878
|
@click.pass_context
|
|
419
|
-
def update(ctx, agent_id, name, instruction, tools, agents, timeout):
|
|
420
|
-
"""Update an existing agent.
|
|
879
|
+
def update(ctx, agent_id, name, instruction, tools, agents, timeout, import_file):
|
|
880
|
+
"""Update an existing agent.
|
|
881
|
+
|
|
882
|
+
Examples:
|
|
883
|
+
aip agents update my-agent --instruction "New instruction"
|
|
884
|
+
aip agents update my-agent --import agent.json
|
|
885
|
+
"""
|
|
421
886
|
try:
|
|
422
887
|
client = get_client(ctx)
|
|
888
|
+
agent = _get_agent_for_update(client, agent_id)
|
|
423
889
|
|
|
424
|
-
#
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
+
)
|
|
429
896
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if
|
|
433
|
-
|
|
434
|
-
if instruction is not None:
|
|
435
|
-
update_data["instruction"] = instruction
|
|
436
|
-
if tools:
|
|
437
|
-
update_data["tools"] = list(tools)
|
|
438
|
-
if agents:
|
|
439
|
-
update_data["agents"] = list(agents)
|
|
440
|
-
if timeout is not None:
|
|
441
|
-
update_data["timeout"] = timeout
|
|
897
|
+
update_data = _build_update_data(name, instruction, tools, agents, timeout)
|
|
898
|
+
|
|
899
|
+
if merged_data:
|
|
900
|
+
_handle_update_import_config(import_file, merged_data, update_data)
|
|
442
901
|
|
|
443
902
|
if not update_data:
|
|
444
903
|
raise click.ClickException("No update fields specified")
|
|
445
904
|
|
|
446
|
-
# Update agent
|
|
447
905
|
updated_agent = client.agents.update_agent(agent.id, **update_data)
|
|
448
906
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
console.print(
|
|
453
|
-
f"[green]✅ Agent '{updated_agent.name}' updated successfully[/green]"
|
|
454
|
-
)
|
|
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))
|
|
455
910
|
|
|
456
|
-
except
|
|
911
|
+
except click.ClickException:
|
|
912
|
+
# Handle JSON output for ClickExceptions if view is JSON
|
|
457
913
|
if ctx.obj.get("view") == "json":
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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)
|
|
461
921
|
raise click.ClickException(str(e))
|
|
462
922
|
|
|
463
923
|
|
|
@@ -477,31 +937,86 @@ def delete(ctx, agent_id, yes):
|
|
|
477
937
|
except Exception as e:
|
|
478
938
|
raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
|
|
479
939
|
|
|
480
|
-
# Confirm deletion
|
|
481
|
-
if not yes and not
|
|
482
|
-
f"Are you sure you want to delete agent '{agent.name}'?"
|
|
483
|
-
):
|
|
484
|
-
if ctx.obj.get("view") != "json":
|
|
485
|
-
console.print("Deletion cancelled.")
|
|
940
|
+
# Confirm deletion when not forced
|
|
941
|
+
if not yes and not display_confirmation_prompt("Agent", agent.name):
|
|
486
942
|
return
|
|
487
943
|
|
|
488
944
|
client.agents.delete_agent(agent.id)
|
|
489
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
|
|
490
957
|
if ctx.obj.get("view") == "json":
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
+
),
|
|
500
1016
|
)
|
|
501
1017
|
|
|
502
1018
|
except Exception as e:
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
console.print(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)
|
|
507
1022
|
raise click.ClickException(str(e))
|