glaip-sdk 0.0.5b1__py3-none-any.whl → 0.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. glaip_sdk/__init__.py +1 -1
  2. glaip_sdk/_version.py +42 -19
  3. glaip_sdk/branding.py +3 -2
  4. glaip_sdk/cli/commands/__init__.py +1 -1
  5. glaip_sdk/cli/commands/agents.py +452 -285
  6. glaip_sdk/cli/commands/configure.py +14 -13
  7. glaip_sdk/cli/commands/mcps.py +30 -20
  8. glaip_sdk/cli/commands/models.py +5 -3
  9. glaip_sdk/cli/commands/tools.py +111 -106
  10. glaip_sdk/cli/display.py +48 -27
  11. glaip_sdk/cli/io.py +1 -1
  12. glaip_sdk/cli/main.py +26 -5
  13. glaip_sdk/cli/resolution.py +5 -4
  14. glaip_sdk/cli/utils.py +437 -188
  15. glaip_sdk/cli/validators.py +7 -2
  16. glaip_sdk/client/agents.py +276 -153
  17. glaip_sdk/client/base.py +69 -27
  18. glaip_sdk/client/tools.py +44 -26
  19. glaip_sdk/client/validators.py +154 -94
  20. glaip_sdk/config/constants.py +0 -2
  21. glaip_sdk/models.py +5 -4
  22. glaip_sdk/utils/__init__.py +7 -7
  23. glaip_sdk/utils/client_utils.py +191 -101
  24. glaip_sdk/utils/display.py +4 -2
  25. glaip_sdk/utils/general.py +8 -6
  26. glaip_sdk/utils/import_export.py +58 -25
  27. glaip_sdk/utils/rendering/formatting.py +12 -6
  28. glaip_sdk/utils/rendering/models.py +1 -1
  29. glaip_sdk/utils/rendering/renderer/base.py +523 -332
  30. glaip_sdk/utils/rendering/renderer/console.py +6 -5
  31. glaip_sdk/utils/rendering/renderer/debug.py +94 -52
  32. glaip_sdk/utils/rendering/renderer/stream.py +93 -48
  33. glaip_sdk/utils/rendering/steps.py +103 -39
  34. glaip_sdk/utils/rich_utils.py +1 -1
  35. glaip_sdk/utils/run_renderer.py +1 -1
  36. glaip_sdk/utils/serialization.py +9 -3
  37. glaip_sdk/utils/validation.py +2 -2
  38. glaip_sdk-0.0.7.dist-info/METADATA +183 -0
  39. glaip_sdk-0.0.7.dist-info/RECORD +55 -0
  40. glaip_sdk-0.0.5b1.dist-info/METADATA +0 -645
  41. glaip_sdk-0.0.5b1.dist-info/RECORD +0 -55
  42. {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/WHEEL +0 -0
  43. {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/entry_points.txt +0 -0
@@ -7,6 +7,7 @@ Authors:
7
7
  import json
8
8
  import os
9
9
  from pathlib import Path
10
+ from typing import Any
10
11
 
11
12
  import click
12
13
  from rich.console import Console
@@ -46,7 +47,9 @@ from glaip_sdk.cli.utils import (
46
47
  _fuzzy_pick_for_resources,
47
48
  build_renderer,
48
49
  coerce_to_row,
50
+ detect_export_format,
49
51
  get_client,
52
+ get_ctx_value,
50
53
  output_flags,
51
54
  output_list,
52
55
  output_result,
@@ -73,7 +76,7 @@ console = Console()
73
76
  AGENT_NOT_FOUND_ERROR = "Agent not found"
74
77
 
75
78
 
76
- def _fetch_full_agent_details(client, agent):
79
+ def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
77
80
  """Fetch full agent details by ID to ensure all fields are populated."""
78
81
  try:
79
82
  agent_id = str(getattr(agent, "id", "")).strip()
@@ -85,7 +88,7 @@ def _fetch_full_agent_details(client, agent):
85
88
  return agent
86
89
 
87
90
 
88
- def _get_agent_model_name(agent):
91
+ def _get_agent_model_name(agent: Any) -> str | None:
89
92
  """Extract model name from agent configuration."""
90
93
  # Try different possible locations for model name
91
94
  if hasattr(agent, "agent_config") and agent.agent_config:
@@ -100,7 +103,7 @@ def _get_agent_model_name(agent):
100
103
 
101
104
 
102
105
  def _resolve_resources_by_name(
103
- _client, items: tuple[str, ...], resource_type: str, find_func, label: str
106
+ _client: Any, items: tuple[str, ...], resource_type: str, find_func: Any, label: str
104
107
  ) -> list[str]:
105
108
  """Resolve resource names/IDs to IDs, handling ambiguity.
106
109
 
@@ -131,90 +134,112 @@ def _resolve_resources_by_name(
131
134
  return out
132
135
 
133
136
 
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")
137
+ def _fetch_and_format_raw_agent_data(client: Any, agent: Any) -> dict | None:
138
+ """Fetch raw agent data and format it for display."""
139
+ try:
140
+ raw_agent_data = fetch_raw_resource_details(client, agent, "agents")
141
+ if not raw_agent_data:
142
+ return None
142
143
 
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)
144
+ # Format dates for better display
146
145
  formatted_data = raw_agent_data.copy()
147
146
  if "created_at" in formatted_data:
148
147
  formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
149
148
  if "updated_at" in formatted_data:
150
149
  formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
151
150
 
152
- # Display using output_result with raw data
151
+ return formatted_data
152
+ except Exception:
153
+ return None
154
+
155
+
156
+ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
157
+ """Format fallback agent data using Pydantic model."""
158
+ full_agent = _fetch_full_agent_details(client, agent)
159
+
160
+ # Define fields to extract
161
+ fields = [
162
+ "id",
163
+ "name",
164
+ "type",
165
+ "framework",
166
+ "version",
167
+ "description",
168
+ "instruction",
169
+ "created_at",
170
+ "updated_at",
171
+ "metadata",
172
+ "language_model_id",
173
+ "agent_config",
174
+ "tool_configs",
175
+ "tools",
176
+ "agents",
177
+ "mcps",
178
+ "a2a_profile",
179
+ ]
180
+
181
+ result_data = build_resource_result_data(full_agent, fields)
182
+
183
+ # Handle missing instruction
184
+ if result_data.get("instruction") in ["N/A", None, ""]:
185
+ result_data["instruction"] = "-"
186
+
187
+ # Format dates for better display
188
+ for date_field in ["created_at", "updated_at"]:
189
+ if result_data.get(date_field) and result_data[date_field] not in ["N/A", None]:
190
+ result_data[date_field] = format_datetime(result_data[date_field])
191
+
192
+ return result_data
193
+
194
+
195
+ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
196
+ """Display full agent details using raw API data to preserve ALL fields."""
197
+ if agent is None:
198
+ handle_rich_output(ctx, Text("[red]❌ No agent provided[/red]"))
199
+ return
200
+
201
+ # Try to fetch and format raw agent data first
202
+ formatted_data = _fetch_and_format_raw_agent_data(client, agent)
203
+
204
+ if formatted_data:
205
+ # Use raw API data - this preserves ALL fields including account_id
206
+ panel_title = f"🤖 {formatted_data.get('name', 'Unknown')}"
153
207
  output_result(
154
208
  ctx,
155
209
  formatted_data,
156
210
  title="Agent Details",
157
- panel_title=f"🤖 {raw_agent_data.get('name', 'Unknown')}",
211
+ panel_title=panel_title,
158
212
  )
159
213
  else:
160
- # Fall back to original method if raw fetch fails
214
+ # Fall back to Pydantic model data if raw fetch fails
161
215
  handle_rich_output(
162
216
  ctx, Text("[yellow]Falling back to Pydantic model data[/yellow]")
163
217
  )
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
218
 
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"])
219
+ result_data = _format_fallback_agent_data(client, agent)
201
220
 
202
221
  # Display using output_result
203
222
  output_result(
204
223
  ctx,
205
224
  result_data,
206
225
  title="Agent Details",
207
- panel_title=f"🤖 {full_agent.name}",
226
+ panel_title=f"🤖 {result_data.get('name', 'Unknown')}",
208
227
  )
209
228
 
210
229
 
211
230
  @click.group(name="agents", no_args_is_help=True)
212
- def agents_group():
231
+ def agents_group() -> None:
213
232
  """Agent management operations."""
214
233
  pass
215
234
 
216
235
 
217
- def _resolve_agent(ctx, client, ref, select=None, interface_preference="fuzzy"):
236
+ def _resolve_agent(
237
+ ctx: Any,
238
+ client: Any,
239
+ ref: str,
240
+ select: int | None = None,
241
+ interface_preference: str = "fuzzy",
242
+ ) -> Any | None:
218
243
  """Resolve agent reference (ID or name) with ambiguity handling.
219
244
 
220
245
  Args:
@@ -252,7 +277,15 @@ def _resolve_agent(ctx, client, ref, select=None, interface_preference="fuzzy"):
252
277
  )
253
278
  @output_flags()
254
279
  @click.pass_context
255
- def list_agents(ctx, simple, agent_type, framework, name, version, sync_langflow):
280
+ def list_agents(
281
+ ctx: Any,
282
+ simple: bool,
283
+ agent_type: str | None,
284
+ framework: str | None,
285
+ name: str | None,
286
+ version: str | None,
287
+ sync_langflow: bool,
288
+ ) -> None:
256
289
  """List agents with optional filtering."""
257
290
  try:
258
291
  client = get_client(ctx)
@@ -274,7 +307,7 @@ def list_agents(ctx, simple, agent_type, framework, name, version, sync_langflow
274
307
  ]
275
308
 
276
309
  # Transform function for safe attribute access
277
- def transform_agent(agent):
310
+ def transform_agent(agent: Any) -> dict[str, Any]:
278
311
  row = coerce_to_row(agent, ["id", "name", "type", "framework", "version"])
279
312
  # Ensure id is always a string
280
313
  row["id"] = str(row["id"])
@@ -307,7 +340,7 @@ def list_agents(ctx, simple, agent_type, framework, name, version, sync_langflow
307
340
  )
308
341
  @output_flags()
309
342
  @click.pass_context
310
- def get(ctx, agent_ref, select, export):
343
+ def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> None:
311
344
  """Get agent details.
312
345
 
313
346
  Examples:
@@ -324,18 +357,15 @@ def get(ctx, agent_ref, select, export):
324
357
  )
325
358
 
326
359
  # Handle export option
327
- if export: # pragma: no cover - requires filesystem verification
360
+ if export:
328
361
  export_path = Path(export)
329
362
  # 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"
363
+ detected_format = detect_export_format(export_path)
334
364
 
335
365
  # Always export comprehensive data - re-fetch agent with full details
336
366
  try:
337
367
  agent = client.agents.get_agent_by_id(agent.id)
338
- except Exception as e: # pragma: no cover - best-effort fallback messaging
368
+ except Exception as e:
339
369
  handle_rich_output(
340
370
  ctx,
341
371
  Text(
@@ -364,7 +394,7 @@ def get(ctx, agent_ref, select, export):
364
394
  raise click.ClickException(str(e))
365
395
 
366
396
 
367
- def _validate_run_input(input_option, input_text):
397
+ def _validate_run_input(input_option: str | None, input_text: str | None) -> str:
368
398
  """Validate and determine the final input text for agent run."""
369
399
  final_input_text = input_option if input_option else input_text
370
400
 
@@ -376,7 +406,7 @@ def _validate_run_input(input_option, input_text):
376
406
  return final_input_text
377
407
 
378
408
 
379
- def _parse_chat_history(chat_history):
409
+ def _parse_chat_history(chat_history: str | None) -> list[dict[str, Any]] | None:
380
410
  """Parse chat history JSON if provided."""
381
411
  if not chat_history:
382
412
  return None
@@ -387,9 +417,9 @@ def _parse_chat_history(chat_history):
387
417
  raise click.ClickException("Invalid JSON in chat history")
388
418
 
389
419
 
390
- def _setup_run_renderer(ctx, save, verbose):
420
+ def _setup_run_renderer(ctx: Any, save: str | None, verbose: bool) -> Any:
391
421
  """Set up renderer and working console for agent run."""
392
- tty_enabled = bool((ctx.obj or {}).get("tty", True))
422
+ tty_enabled = bool(get_ctx_value(ctx, "tty", True))
393
423
  return build_renderer(
394
424
  ctx,
395
425
  save_path=save,
@@ -399,8 +429,13 @@ def _setup_run_renderer(ctx, save, verbose):
399
429
 
400
430
 
401
431
  def _prepare_run_kwargs(
402
- agent, final_input_text, files, parsed_chat_history, renderer, tty_enabled
403
- ):
432
+ agent: Any,
433
+ final_input_text: str,
434
+ files: list[str] | None,
435
+ parsed_chat_history: list[dict[str, Any]] | None,
436
+ renderer: Any,
437
+ tty_enabled: bool,
438
+ ) -> dict[str, Any]:
404
439
  """Prepare kwargs for agent run."""
405
440
  run_kwargs = {
406
441
  "agent_id": agent.id,
@@ -419,10 +454,10 @@ def _prepare_run_kwargs(
419
454
  return run_kwargs
420
455
 
421
456
 
422
- def _handle_run_output(ctx, result, renderer):
457
+ def _handle_run_output(ctx: Any, result: Any, renderer: Any) -> None:
423
458
  """Handle output formatting for agent run results."""
424
459
  printed_by_renderer = bool(renderer)
425
- selected_view = (ctx.obj or {}).get("view", "rich")
460
+ selected_view = get_ctx_value(ctx, "view", "rich")
426
461
 
427
462
  if not printed_by_renderer:
428
463
  if selected_view == "json":
@@ -433,7 +468,7 @@ def _handle_run_output(ctx, result, renderer):
433
468
  click.echo(result)
434
469
 
435
470
 
436
- def _save_run_transcript(save, result, working_console):
471
+ def _save_run_transcript(save: str | None, result: Any, working_console: Any) -> None:
437
472
  """Save transcript to file if requested."""
438
473
  if not save:
439
474
  return
@@ -492,17 +527,17 @@ def _save_run_transcript(save, result, working_console):
492
527
  @output_flags()
493
528
  @click.pass_context
494
529
  def run(
495
- ctx,
496
- agent_ref,
497
- select,
498
- input_text,
499
- input_option,
500
- chat_history,
501
- timeout,
502
- save,
503
- files,
504
- verbose,
505
- ):
530
+ ctx: Any,
531
+ agent_ref: str,
532
+ select: int | None,
533
+ input_text: str | None,
534
+ input_option: str | None,
535
+ chat_history: str | None,
536
+ timeout: float | None,
537
+ save: str | None,
538
+ files: tuple[str, ...] | None,
539
+ verbose: bool,
540
+ ) -> None:
506
541
  """Run an agent with input text.
507
542
 
508
543
  Usage: aip agents run <agent_ref> <input_text> [OPTIONS]
@@ -534,7 +569,7 @@ def run(
534
569
  files,
535
570
  parsed_chat_history,
536
571
  renderer,
537
- bool((ctx.obj or {}).get("tty", True)),
572
+ bool(get_ctx_value(ctx, "tty", True)),
538
573
  )
539
574
 
540
575
  result = client.agents.run_agent(**run_kwargs, timeout=timeout)
@@ -547,8 +582,235 @@ def run(
547
582
  handle_json_output(ctx, error=Exception(error_msg))
548
583
  raise click.ClickException(error_msg)
549
584
  except Exception as e:
550
- handle_json_output(ctx, error=e)
551
- raise click.ClickException(str(e))
585
+ _handle_command_exception(ctx, e)
586
+
587
+
588
+ def _handle_import_file_logic(
589
+ import_file: str,
590
+ model: str | None,
591
+ name: str,
592
+ instruction: str,
593
+ tools: tuple[str, ...],
594
+ agents: tuple[str, ...],
595
+ mcps: tuple[str, ...],
596
+ timeout: float | None,
597
+ ) -> dict[str, Any]:
598
+ """Handle import file logic and merge with CLI args."""
599
+ import_data = load_resource_from_file(Path(import_file), "agent")
600
+ import_data = convert_export_to_import_format(import_data)
601
+ import_data = normalize_agent_config_for_import(import_data, model)
602
+
603
+ cli_args = {
604
+ "name": name,
605
+ "instruction": instruction,
606
+ "model": model,
607
+ "tools": tools or (),
608
+ "agents": agents or (),
609
+ "mcps": mcps or (),
610
+ "timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
611
+ }
612
+
613
+ return merge_import_with_cli_args(import_data, cli_args)
614
+
615
+
616
+ def _build_cli_args_data(
617
+ name: str,
618
+ instruction: str,
619
+ model: str | None,
620
+ tools: tuple[str, ...],
621
+ agents: tuple[str, ...],
622
+ mcps: tuple[str, ...],
623
+ timeout: float | None,
624
+ ) -> dict[str, Any]:
625
+ """Build merged data from CLI arguments."""
626
+ return {
627
+ "name": name,
628
+ "instruction": instruction,
629
+ "model": model,
630
+ "tools": tools or (),
631
+ "agents": agents or (),
632
+ "mcps": mcps or (),
633
+ "timeout": timeout if timeout != DEFAULT_AGENT_RUN_TIMEOUT else None,
634
+ }
635
+
636
+
637
+ def _extract_and_validate_fields(
638
+ merged_data: dict[str, Any],
639
+ ) -> tuple[str, str, str | None, tuple, tuple, tuple, Any]:
640
+ """Extract and validate required fields from merged data."""
641
+ name = merged_data.get("name")
642
+ instruction = merged_data.get("instruction")
643
+ model = merged_data.get("model")
644
+ tools = tuple(merged_data.get("tools", ()))
645
+ agents = tuple(merged_data.get("agents", ()))
646
+ mcps = tuple(merged_data.get("mcps", ()))
647
+ timeout = merged_data.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
648
+
649
+ # Validate required fields
650
+ if not name:
651
+ raise click.ClickException("Agent name is required (--name or --import)")
652
+ if not instruction:
653
+ raise click.ClickException(
654
+ "Agent instruction is required (--instruction or --import)"
655
+ )
656
+
657
+ return name, instruction, model, tools, agents, mcps, timeout
658
+
659
+
660
+ def _validate_and_coerce_fields(
661
+ name: str, instruction: str, timeout: Any
662
+ ) -> tuple[str, str, Any]:
663
+ """Validate and coerce field values."""
664
+ name = validate_agent_name(name)
665
+ instruction = validate_agent_instruction(instruction)
666
+ timeout = coerce_timeout(timeout)
667
+ if timeout is not None:
668
+ timeout = validate_timeout(timeout)
669
+
670
+ return name, instruction, timeout
671
+
672
+
673
+ def _resolve_resources(
674
+ client: Any, tools: tuple, agents: tuple, mcps: tuple
675
+ ) -> tuple[list, list, list]:
676
+ """Resolve tool, agent, and MCP references."""
677
+ resolved_tools = _resolve_resources_by_name(
678
+ client, tools, "tool", client.find_tools, "Tool"
679
+ )
680
+ resolved_agents = _resolve_resources_by_name(
681
+ client, agents, "agent", client.find_agents, "Agent"
682
+ )
683
+ resolved_mcps = _resolve_resources_by_name(
684
+ client, mcps, "mcp", client.find_mcps, "MCP"
685
+ )
686
+
687
+ return resolved_tools, resolved_agents, resolved_mcps
688
+
689
+
690
+ def _build_create_kwargs(
691
+ name: str,
692
+ instruction: str,
693
+ resolved_tools: list,
694
+ resolved_agents: list,
695
+ resolved_mcps: list,
696
+ timeout: Any,
697
+ merged_data: dict[str, Any],
698
+ model: str | None,
699
+ import_file: str | None,
700
+ ) -> dict[str, Any]:
701
+ """Build create_agent kwargs with all necessary parameters."""
702
+ create_kwargs = {
703
+ "name": name,
704
+ "instruction": instruction,
705
+ "tools": resolved_tools or None,
706
+ "agents": resolved_agents or None,
707
+ "mcps": resolved_mcps or None,
708
+ "timeout": timeout,
709
+ }
710
+
711
+ # Handle language model selection
712
+ lm_selection_dict, should_strip_lm_identity = resolve_language_model_selection(
713
+ merged_data, model
714
+ )
715
+ create_kwargs.update(lm_selection_dict)
716
+
717
+ # Handle import file specific logic
718
+ if import_file:
719
+ _add_import_file_attributes(
720
+ create_kwargs, merged_data, should_strip_lm_identity
721
+ )
722
+
723
+ return create_kwargs
724
+
725
+
726
+ def _add_import_file_attributes(
727
+ create_kwargs: dict[str, Any],
728
+ merged_data: dict[str, Any],
729
+ should_strip_lm_identity: bool,
730
+ ) -> None:
731
+ """Add import file specific attributes to create_kwargs."""
732
+ agent_config_raw = merged_data.get("agent_config")
733
+ if isinstance(agent_config_raw, dict):
734
+ create_kwargs["agent_config"] = sanitize_agent_config(
735
+ agent_config_raw, strip_lm_identity=should_strip_lm_identity
736
+ )
737
+
738
+ # Add other attributes from import data
739
+ excluded_fields = {
740
+ "name",
741
+ "instruction",
742
+ "model",
743
+ "language_model_id",
744
+ "tools",
745
+ "agents",
746
+ "timeout",
747
+ "agent_config",
748
+ "id",
749
+ "created_at",
750
+ "updated_at",
751
+ "type",
752
+ "framework",
753
+ "version",
754
+ "tool_configs",
755
+ "mcps",
756
+ "a2a_profile",
757
+ }
758
+ for key, value in merged_data.items():
759
+ if key not in excluded_fields and value is not None:
760
+ create_kwargs[key] = value
761
+
762
+
763
+ def _get_language_model_display_name(agent: Any, model: str | None) -> str:
764
+ """Get display name for the language model."""
765
+ lm_display = getattr(agent, "model", None)
766
+ if not lm_display:
767
+ cfg = getattr(agent, "agent_config", {}) or {}
768
+ lm_display = (
769
+ cfg.get("lm_name")
770
+ or cfg.get("model")
771
+ or model
772
+ or f"{DEFAULT_MODEL} (backend default)"
773
+ )
774
+ return lm_display
775
+
776
+
777
+ def _handle_successful_creation(ctx: Any, agent: Any, model: str | None) -> None:
778
+ """Handle successful agent creation output."""
779
+ handle_json_output(ctx, agent.model_dump())
780
+
781
+ lm_display = _get_language_model_display_name(agent, model)
782
+
783
+ handle_rich_output(
784
+ ctx,
785
+ display_creation_success(
786
+ "Agent",
787
+ agent.name,
788
+ agent.id,
789
+ Model=lm_display,
790
+ Type=getattr(agent, "type", "config"),
791
+ Framework=getattr(agent, "framework", "langchain"),
792
+ Version=getattr(agent, "version", "1.0"),
793
+ ),
794
+ )
795
+ handle_rich_output(ctx, display_agent_run_suggestions(agent))
796
+
797
+
798
+ def _handle_command_exception(ctx: Any, e: Exception) -> None:
799
+ """Handle exceptions during command execution with consistent error handling."""
800
+ if isinstance(e, click.ClickException):
801
+ if get_ctx_value(ctx, "view") == "json":
802
+ handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
803
+ raise
804
+
805
+ handle_json_output(ctx, error=e)
806
+ if get_ctx_value(ctx, "view") != "json":
807
+ print_api_error(e)
808
+ raise click.ClickException(str(e))
809
+
810
+
811
+ def _handle_creation_exception(ctx: Any, e: Exception) -> None:
812
+ """Handle exceptions during agent creation."""
813
+ _handle_command_exception(ctx, e)
552
814
 
553
815
 
554
816
  @agents_group.command()
@@ -576,16 +838,16 @@ def run(
576
838
  @output_flags()
577
839
  @click.pass_context
578
840
  def create(
579
- ctx,
580
- name,
581
- instruction,
582
- model,
583
- tools,
584
- agents,
585
- mcps,
586
- timeout,
587
- import_file,
588
- ):
841
+ ctx: Any,
842
+ name: str,
843
+ instruction: str,
844
+ model: str | None,
845
+ tools: tuple[str, ...] | None,
846
+ agents: tuple[str, ...] | None,
847
+ mcps: tuple[str, ...] | None,
848
+ timeout: float | None,
849
+ import_file: str | None,
850
+ ) -> None:
589
851
  """Create a new agent.
590
852
 
591
853
  Examples:
@@ -595,181 +857,59 @@ def create(
595
857
  try:
596
858
  client = get_client(ctx)
597
859
 
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)
860
+ # Handle import file or CLI args
861
+ if import_file:
862
+ merged_data = _handle_import_file_logic(
863
+ import_file, model, name, instruction, tools, agents, mcps, timeout
864
+ )
625
865
  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)"
866
+ merged_data = _build_cli_args_data(
867
+ name, instruction, model, tools, agents, mcps, timeout
654
868
  )
655
869
 
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
-
662
- # Resolve tool and agent references: accept names or IDs
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"
870
+ # Extract and validate fields
871
+ (
872
+ name,
873
+ instruction,
874
+ model,
875
+ tools,
876
+ agents,
877
+ mcps,
878
+ timeout,
879
+ ) = _extract_and_validate_fields(merged_data)
880
+ name, instruction, timeout = _validate_and_coerce_fields(
881
+ name, instruction, timeout
671
882
  )
672
883
 
673
- # Create agent with comprehensive attribute support
674
- create_kwargs = {
675
- "name": name,
676
- "instruction": instruction,
677
- "tools": resolved_tools or None,
678
- "agents": resolved_agents or None,
679
- "mcps": resolved_mcps or None,
680
- "timeout": timeout,
681
- }
682
-
683
- # Handle language model selection using helper function
684
- lm_selection_dict, should_strip_lm_identity = resolve_language_model_selection(
685
- merged_data, model
884
+ # Resolve resources
885
+ resolved_tools, resolved_agents, resolved_mcps = _resolve_resources(
886
+ client, tools, agents, mcps
686
887
  )
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
888
 
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
889
+ # Build create kwargs
890
+ create_kwargs = _build_create_kwargs(
891
+ name,
892
+ instruction,
893
+ resolved_tools,
894
+ resolved_agents,
895
+ resolved_mcps,
896
+ timeout,
897
+ merged_data,
898
+ model,
899
+ import_file,
900
+ )
728
901
 
902
+ # Create agent
729
903
  agent = client.agents.create_agent(**create_kwargs)
730
904
 
731
- handle_json_output(ctx, agent.model_dump())
732
-
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
- )
742
-
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))
905
+ # Handle successful creation
906
+ _handle_successful_creation(ctx, agent, model)
756
907
 
757
- except (
758
- click.ClickException
759
- ): # pragma: no cover - error formatting verified elsewhere
760
- # Handle JSON output for ClickExceptions if view is JSON
761
- if ctx.obj.get("view") == "json":
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)
769
- raise click.ClickException(str(e))
908
+ except Exception as e:
909
+ _handle_creation_exception(ctx, e)
770
910
 
771
911
 
772
- def _get_agent_for_update(client, agent_id):
912
+ def _get_agent_for_update(client: Any, agent_id: str) -> Any:
773
913
  """Retrieve agent by ID for update operation."""
774
914
  try:
775
915
  return client.agents.get_agent_by_id(agent_id)
@@ -777,7 +917,21 @@ def _get_agent_for_update(client, agent_id):
777
917
  raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
778
918
 
779
919
 
780
- def _handle_update_import_file(import_file, name, instruction, tools, agents, timeout):
920
+ def _handle_update_import_file(
921
+ import_file: str | None,
922
+ name: str | None,
923
+ instruction: str | None,
924
+ tools: tuple[str, ...] | None,
925
+ agents: tuple[str, ...] | None,
926
+ timeout: float | None,
927
+ ) -> tuple[
928
+ Any | None,
929
+ str | None,
930
+ str | None,
931
+ tuple[str, ...] | None,
932
+ tuple[str, ...] | None,
933
+ float | None,
934
+ ]:
781
935
  """Handle import file processing for agent update."""
782
936
  if not import_file:
783
937
  return None, name, instruction, tools, agents, timeout
@@ -806,7 +960,13 @@ def _handle_update_import_file(import_file, name, instruction, tools, agents, ti
806
960
  )
807
961
 
808
962
 
809
- def _build_update_data(name, instruction, tools, agents, timeout):
963
+ def _build_update_data(
964
+ name: str | None,
965
+ instruction: str | None,
966
+ tools: tuple[str, ...] | None,
967
+ agents: tuple[str, ...] | None,
968
+ timeout: float | None,
969
+ ) -> dict[str, Any]:
810
970
  """Build the update data dictionary from provided parameters."""
811
971
  update_data = {}
812
972
  if name is not None:
@@ -822,7 +982,9 @@ def _build_update_data(name, instruction, tools, agents, timeout):
822
982
  return update_data
823
983
 
824
984
 
825
- def _handle_update_import_config(import_file, merged_data, update_data):
985
+ def _handle_update_import_config(
986
+ import_file: str | None, merged_data: dict[str, Any], update_data: dict[str, Any]
987
+ ) -> None:
826
988
  """Handle agent config and additional attributes for import-based updates."""
827
989
  if not import_file:
828
990
  return
@@ -876,7 +1038,16 @@ def _handle_update_import_config(import_file, merged_data, update_data):
876
1038
  )
877
1039
  @output_flags()
878
1040
  @click.pass_context
879
- def update(ctx, agent_id, name, instruction, tools, agents, timeout, import_file):
1041
+ def update(
1042
+ ctx: Any,
1043
+ agent_id: str,
1044
+ name: str | None,
1045
+ instruction: str | None,
1046
+ tools: tuple[str, ...] | None,
1047
+ agents: tuple[str, ...] | None,
1048
+ timeout: float | None,
1049
+ import_file: str | None,
1050
+ ) -> None:
880
1051
  """Update an existing agent.
881
1052
 
882
1053
  Examples:
@@ -888,10 +1059,15 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout, import_file
888
1059
  agent = _get_agent_for_update(client, agent_id)
889
1060
 
890
1061
  # 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
- )
1062
+ (
1063
+ merged_data,
1064
+ name,
1065
+ instruction,
1066
+ tools,
1067
+ agents,
1068
+ timeout,
1069
+ ) = _handle_update_import_file(
1070
+ import_file, name, instruction, tools, agents, timeout
895
1071
  )
896
1072
 
897
1073
  update_data = _build_update_data(name, instruction, tools, agents, timeout)
@@ -910,15 +1086,12 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout, import_file
910
1086
 
911
1087
  except click.ClickException:
912
1088
  # Handle JSON output for ClickExceptions if view is JSON
913
- if ctx.obj.get("view") == "json":
1089
+ if get_ctx_value(ctx, "view") == "json":
914
1090
  handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
915
1091
  # Re-raise ClickExceptions without additional processing
916
1092
  raise
917
1093
  except Exception as e:
918
- handle_json_output(ctx, error=e)
919
- if ctx.obj.get("view") != "json":
920
- print_api_error(e)
921
- raise click.ClickException(str(e))
1094
+ _handle_command_exception(ctx, e)
922
1095
 
923
1096
 
924
1097
  @agents_group.command()
@@ -926,7 +1099,7 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout, import_file
926
1099
  @click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
927
1100
  @output_flags()
928
1101
  @click.pass_context
929
- def delete(ctx, agent_id, yes):
1102
+ def delete(ctx: Any, agent_id: str, yes: bool) -> None:
930
1103
  """Delete an agent."""
931
1104
  try:
932
1105
  client = get_client(ctx)
@@ -954,15 +1127,12 @@ def delete(ctx, agent_id, yes):
954
1127
 
955
1128
  except click.ClickException:
956
1129
  # Handle JSON output for ClickExceptions if view is JSON
957
- if ctx.obj.get("view") == "json":
1130
+ if get_ctx_value(ctx, "view") == "json":
958
1131
  handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
959
1132
  # Re-raise ClickExceptions without additional processing
960
1133
  raise
961
1134
  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))
1135
+ _handle_command_exception(ctx, e)
966
1136
 
967
1137
 
968
1138
  @agents_group.command()
@@ -975,7 +1145,7 @@ def delete(ctx, agent_id, yes):
975
1145
  )
976
1146
  @output_flags()
977
1147
  @click.pass_context
978
- def sync_langflow(ctx, base_url, api_key): # pragma: no cover - integration-only path
1148
+ def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
979
1149
  """Sync agents with LangFlow server flows.
980
1150
 
981
1151
  This command fetches all flows from the configured LangFlow server and
@@ -999,7 +1169,7 @@ def sync_langflow(ctx, base_url, api_key): # pragma: no cover - integration-onl
999
1169
  handle_json_output(ctx, result)
1000
1170
 
1001
1171
  # Show success message for non-JSON output
1002
- if ctx.obj.get("view") != "json":
1172
+ if get_ctx_value(ctx, "view") != "json":
1003
1173
  from rich.text import Text
1004
1174
 
1005
1175
  # Extract some useful info from the result
@@ -1016,7 +1186,4 @@ def sync_langflow(ctx, base_url, api_key): # pragma: no cover - integration-onl
1016
1186
  )
1017
1187
 
1018
1188
  except Exception as e:
1019
- handle_json_output(ctx, error=e)
1020
- if ctx.obj.get("view") != "json":
1021
- print_api_error(e)
1022
- raise click.ClickException(str(e))
1189
+ _handle_command_exception(ctx, e)