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