glaip-sdk 0.2.2__py3-none-any.whl → 0.3.0__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 (38) hide show
  1. glaip_sdk/cli/commands/agents.py +50 -35
  2. glaip_sdk/cli/commands/mcps.py +28 -18
  3. glaip_sdk/cli/commands/models.py +3 -5
  4. glaip_sdk/cli/commands/tools.py +27 -16
  5. glaip_sdk/cli/constants.py +3 -0
  6. glaip_sdk/cli/main.py +1 -3
  7. glaip_sdk/cli/slash/agent_session.py +3 -13
  8. glaip_sdk/cli/slash/prompt.py +3 -0
  9. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  10. glaip_sdk/cli/slash/session.py +138 -47
  11. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  12. glaip_sdk/cli/slash/tui/remote_runs_app.py +632 -0
  13. glaip_sdk/cli/transcript/viewer.py +6 -32
  14. glaip_sdk/cli/utils.py +183 -9
  15. glaip_sdk/cli/validators.py +5 -6
  16. glaip_sdk/client/__init__.py +2 -1
  17. glaip_sdk/client/agent_runs.py +147 -0
  18. glaip_sdk/client/agents.py +42 -22
  19. glaip_sdk/client/main.py +18 -6
  20. glaip_sdk/client/mcps.py +2 -4
  21. glaip_sdk/client/tools.py +2 -3
  22. glaip_sdk/config/constants.py +11 -0
  23. glaip_sdk/models/__init__.py +56 -0
  24. glaip_sdk/models/agent_runs.py +117 -0
  25. glaip_sdk/rich_components.py +58 -2
  26. glaip_sdk/utils/client_utils.py +13 -0
  27. glaip_sdk/utils/export.py +143 -0
  28. glaip_sdk/utils/import_export.py +6 -9
  29. glaip_sdk/utils/rendering/__init__.py +122 -1
  30. glaip_sdk/utils/rendering/renderer/base.py +3 -7
  31. glaip_sdk/utils/rendering/renderer/debug.py +0 -1
  32. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  33. glaip_sdk/utils/rendering/steps.py +1 -0
  34. glaip_sdk/utils/resource_refs.py +26 -15
  35. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.3.0.dist-info}/METADATA +24 -2
  36. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.3.0.dist-info}/RECORD +38 -31
  37. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.3.0.dist-info}/WHEEL +0 -0
  38. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -64,10 +64,12 @@ from glaip_sdk.cli.utils import (
64
64
  build_renderer,
65
65
  coerce_to_row,
66
66
  get_client,
67
+ handle_resource_export,
67
68
  in_slash_mode,
68
69
  output_list,
69
70
  output_result,
70
71
  spinner_context,
72
+ with_client_and_spinner,
71
73
  )
72
74
  from glaip_sdk.cli.validators import (
73
75
  validate_agent_instruction_cli as validate_agent_instruction,
@@ -78,7 +80,7 @@ from glaip_sdk.cli.validators import (
78
80
  from glaip_sdk.cli.validators import (
79
81
  validate_timeout_cli as validate_timeout,
80
82
  )
81
- from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
83
+ from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS, DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
82
84
  from glaip_sdk.exceptions import AgentTimeoutError
83
85
  from glaip_sdk.icons import ICON_AGENT
84
86
  from glaip_sdk.utils import format_datetime, is_uuid
@@ -146,10 +148,7 @@ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
146
148
  "description",
147
149
  "model",
148
150
  "agent_config",
149
- "tools",
150
- "agents",
151
- "mcps",
152
- "timeout",
151
+ *[field for field in AGENT_CONFIG_FIELDS if field not in ("name", "instruction", "model")],
153
152
  "tool_configs",
154
153
  )
155
154
 
@@ -457,7 +456,6 @@ def agents_group() -> None:
457
456
  pass
458
457
 
459
458
 
460
- # pylint: disable=duplicate-code
461
459
  def _resolve_agent(
462
460
  ctx: Any,
463
461
  client: Any,
@@ -465,26 +463,38 @@ def _resolve_agent(
465
463
  select: int | None = None,
466
464
  interface_preference: str = "fuzzy",
467
465
  ) -> Any | None:
468
- """Resolve agent reference (ID or name) with ambiguity handling.
466
+ """Resolve an agent by ID or name, supporting fuzzy and questionary interfaces.
467
+
468
+ This function provides agent-specific resolution with flexible UI options.
469
+ It wraps resolve_resource_reference with agent-specific configuration, allowing
470
+ users to choose between fuzzy search and traditional questionary selection.
469
471
 
470
472
  Args:
471
- ctx: Click context object for CLI operations.
472
- client: AIP client instance for API operations.
473
- ref: Agent reference (ID or name) to resolve.
474
- select: Pre-selected agent index for non-interactive mode.
475
- interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list.
473
+ ctx: Click context for CLI command execution.
474
+ client: AIP SDK client instance.
475
+ ref: Agent identifier (UUID or name string).
476
+ select: Pre-selected index for non-interactive resolution (1-based).
477
+ interface_preference: UI preference - "fuzzy" for search or "questionary" for list.
476
478
 
477
479
  Returns:
478
- Resolved agent object or None if not found.
480
+ Agent object when found, None when resolution fails.
479
481
  """
482
+ # Configure agent-specific resolution parameters
483
+ resolution_config = {
484
+ "resource_type": "agent",
485
+ "get_by_id": client.agents.get_agent_by_id,
486
+ "find_by_name": client.agents.find_agents,
487
+ "label": "Agent",
488
+ }
489
+ # Use agent-specific resolution with flexible interface preference
480
490
  return resolve_resource_reference(
481
491
  ctx,
482
492
  client,
483
493
  ref,
484
- "agent",
485
- client.agents.get_agent_by_id,
486
- client.agents.find_agents,
487
- "Agent",
494
+ resolution_config["resource_type"],
495
+ resolution_config["get_by_id"],
496
+ resolution_config["find_by_name"],
497
+ resolution_config["label"],
488
498
  select=select,
489
499
  interface_preference=interface_preference,
490
500
  )
@@ -514,19 +524,20 @@ def list_agents(
514
524
  ) -> None:
515
525
  """List agents with optional filtering."""
516
526
  try:
517
- client = get_client(ctx)
518
- with spinner_context(
527
+ with with_client_and_spinner(
519
528
  ctx,
520
529
  "[bold blue]Fetching agents…[/bold blue]",
521
530
  console_override=console,
522
- ):
523
- agents = client.agents.list_agents(
524
- agent_type=agent_type,
525
- framework=framework,
526
- name=name,
527
- version=version,
528
- sync_langflow_agents=sync_langflow,
529
- )
531
+ ) as client:
532
+ # Query agents with specified filters
533
+ filter_params = {
534
+ "agent_type": agent_type,
535
+ "framework": framework,
536
+ "name": name,
537
+ "version": version,
538
+ "sync_langflow_agents": sync_langflow,
539
+ }
540
+ agents = client.agents.list_agents(**filter_params)
530
541
 
531
542
  # Define table columns: (data_key, header, style, width)
532
543
  columns = [
@@ -621,28 +632,30 @@ def get(
621
632
  aip agents get my-agent --export agent.yaml # Exports complete configuration as YAML
622
633
  """
623
634
  try:
624
- client = get_client(ctx)
635
+ # Initialize API client for agent retrieval
636
+ api_client = get_client(ctx)
625
637
 
626
- # Resolve agent with ambiguity handling - use questionary interface for traditional UX
627
- agent = _resolve_agent(ctx, client, agent_ref, select, interface_preference="questionary")
638
+ # Resolve agent reference using questionary interface for better UX
639
+ agent = _resolve_agent(ctx, api_client, agent_ref, select, interface_preference="questionary")
628
640
 
629
- # Handle export option
630
- if export:
631
- from glaip_sdk.cli.utils import handle_resource_export
641
+ if not agent:
642
+ raise click.ClickException(f"Agent '{agent_ref}' not found")
632
643
 
644
+ # Handle export option if requested
645
+ if export:
633
646
  handle_resource_export(
634
647
  ctx,
635
648
  agent,
636
649
  Path(export),
637
650
  resource_type="agent",
638
- get_by_id_func=client.agents.get_agent_by_id,
651
+ get_by_id_func=api_client.agents.get_agent_by_id,
639
652
  console_override=console,
640
653
  )
641
654
 
642
655
  # Display full agent details using the standardized helper
643
656
  _display_agent_details(
644
657
  ctx,
645
- client,
658
+ api_client,
646
659
  agent,
647
660
  instruction_preview_limit=instruction_preview,
648
661
  )
@@ -650,6 +663,8 @@ def get(
650
663
  # Show run suggestions via centralized display helper
651
664
  handle_rich_output(ctx, display_agent_run_suggestions(agent))
652
665
 
666
+ except click.ClickException:
667
+ raise
653
668
  except Exception as e:
654
669
  raise click.ClickException(str(e)) from e
655
670
 
@@ -43,10 +43,13 @@ from glaip_sdk.cli.resolution import resolve_resource_reference
43
43
  from glaip_sdk.cli.rich_helpers import print_markup
44
44
  from glaip_sdk.cli.utils import (
45
45
  coerce_to_row,
46
+ fetch_resource_for_export,
47
+ format_datetime_fields,
46
48
  get_client,
47
49
  output_list,
48
50
  output_result,
49
51
  spinner_context,
52
+ with_client_and_spinner,
50
53
  )
51
54
  from glaip_sdk.config.constants import (
52
55
  DEFAULT_MCP_TYPE,
@@ -298,28 +301,37 @@ def mcps_group() -> None:
298
301
  pass
299
302
 
300
303
 
301
- def _resolve_mcp(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None: # pylint: disable=duplicate-code
302
- """Resolve MCP reference (ID or name) with ambiguity handling.
304
+ def _resolve_mcp(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
305
+ """Resolve an MCP server by ID or name, with interactive selection support.
306
+
307
+ This function provides MCP-specific resolution logic. It delegates to
308
+ resolve_resource_reference for MCP-specific resolution, supporting UUID
309
+ lookups and name-based fuzzy matching.
303
310
 
304
311
  Args:
305
- ctx: Click context object
306
- client: API client instance
307
- ref: MCP reference (ID or name)
308
- select: Index to select when multiple matches found
312
+ ctx: Click context for command execution.
313
+ client: API client for backend operations.
314
+ ref: MCP identifier (UUID or name string).
315
+ select: Optional selection index when multiple MCPs match (1-based).
309
316
 
310
317
  Returns:
311
- MCP object if found, None otherwise
318
+ MCP instance if resolution succeeds, None if not found.
312
319
 
313
320
  Raises:
314
- ClickException: If MCP not found or selection invalid
321
+ click.ClickException: When resolution fails or selection is invalid.
315
322
  """
323
+ # Configure MCP-specific resolution functions
324
+ mcp_client = client.mcps
325
+ get_by_id_func = mcp_client.get_mcp_by_id
326
+ find_by_name_func = mcp_client.find_mcps
327
+ # Use MCP-specific resolution with standard fuzzy matching
316
328
  return resolve_resource_reference(
317
329
  ctx,
318
330
  client,
319
331
  ref,
320
332
  "mcp",
321
- client.mcps.get_mcp_by_id,
322
- client.mcps.find_mcps,
333
+ get_by_id_func,
334
+ find_by_name_func,
323
335
  "MCP",
324
336
  select=select,
325
337
  )
@@ -512,12 +524,11 @@ def list_mcps(ctx: Any) -> None:
512
524
  ClickException: If API request fails
513
525
  """
514
526
  try:
515
- client = get_client(ctx)
516
- with spinner_context(
527
+ with with_client_and_spinner(
517
528
  ctx,
518
529
  "[bold blue]Fetching MCPs…[/bold blue]",
519
530
  console_override=console,
520
- ):
531
+ ) as client:
521
532
  mcps = client.mcps.list_mcps()
522
533
 
523
534
  # Define table columns: (data_key, header, style, width)
@@ -612,8 +623,10 @@ def create(
612
623
  aip mcps create --import mcp-export.json --name new-name --transport sse
613
624
  """
614
625
  try:
615
- client = get_client(ctx)
626
+ # Get API client instance for MCP operations
627
+ api_client = get_client(ctx)
616
628
 
629
+ # Process import file if specified, otherwise use None
617
630
  import_payload = _load_import_ready_payload(import_file) if import_file is not None else None
618
631
 
619
632
  merged_payload, missing_fields = _merge_import_payload(
@@ -657,7 +670,7 @@ def create(
657
670
  if mcp_metadata is not None:
658
671
  create_kwargs["mcp_metadata"] = mcp_metadata
659
672
 
660
- mcp = client.mcps.create_mcp(**create_kwargs)
673
+ mcp = api_client.mcps.create_mcp(**create_kwargs)
661
674
 
662
675
  # Handle JSON output
663
676
  handle_json_output(ctx, mcp.model_dump())
@@ -704,7 +717,6 @@ def _handle_mcp_export(
704
717
  detected_format = detect_export_format(export_path)
705
718
 
706
719
  # Always export comprehensive data - re-fetch with full details
707
- from glaip_sdk.cli.utils import fetch_resource_for_export
708
720
 
709
721
  mcp = fetch_resource_for_export(
710
722
  ctx,
@@ -783,8 +795,6 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
783
795
 
784
796
  if raw_mcp_data:
785
797
  # Use raw API data - this preserves ALL fields
786
- from glaip_sdk.cli.utils import format_datetime_fields
787
-
788
798
  formatted_data = format_datetime_fields(raw_mcp_data)
789
799
 
790
800
  output_result(
@@ -12,9 +12,8 @@ from rich.console import Console
12
12
  from glaip_sdk.branding import ACCENT_STYLE, INFO, SUCCESS
13
13
  from glaip_sdk.cli.context import output_flags
14
14
  from glaip_sdk.cli.utils import (
15
- get_client,
16
15
  output_list,
17
- spinner_context,
16
+ with_client_and_spinner,
18
17
  )
19
18
 
20
19
  console = Console()
@@ -32,12 +31,11 @@ def models_group() -> None:
32
31
  def list_models(ctx: Any) -> None:
33
32
  """List available language models."""
34
33
  try:
35
- client = get_client(ctx)
36
- with spinner_context(
34
+ with with_client_and_spinner(
37
35
  ctx,
38
36
  "[bold blue]Fetching language models…[/bold blue]",
39
37
  console_override=console,
40
- ):
38
+ ) as client:
41
39
  models = client.list_language_models()
42
40
 
43
41
  # Define table columns: (data_key, header, style, width)
@@ -4,7 +4,6 @@ Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
- # pylint: disable=duplicate-code
8
7
  import json
9
8
  import re
10
9
  from pathlib import Path
@@ -40,7 +39,10 @@ from glaip_sdk.cli.resolution import resolve_resource_reference
40
39
  from glaip_sdk.cli.rich_helpers import markup_text, print_markup
41
40
  from glaip_sdk.cli.utils import (
42
41
  coerce_to_row,
42
+ format_datetime_fields,
43
43
  get_client,
44
+ handle_best_effort_check,
45
+ handle_resource_export,
44
46
  output_list,
45
47
  output_result,
46
48
  spinner_context,
@@ -57,16 +59,32 @@ def tools_group() -> None:
57
59
  pass
58
60
 
59
61
 
60
- # pylint: disable=duplicate-code
61
62
  def _resolve_tool(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
62
- """Resolve tool reference (ID or name) with ambiguity handling."""
63
+ """Resolve a tool by ID or name, handling ambiguous matches interactively.
64
+
65
+ This function provides tool-specific resolution logic. It uses
66
+ resolve_resource_reference to find tools by UUID or name, with interactive
67
+ selection when multiple matches are found.
68
+
69
+ Args:
70
+ ctx: Click context for CLI operations.
71
+ client: API client instance.
72
+ ref: Tool reference (UUID string or name).
73
+ select: Pre-selected index for non-interactive mode (1-based).
74
+
75
+ Returns:
76
+ Tool object if found, None otherwise.
77
+ """
78
+ # Configure tool-specific resolution with standard fuzzy matching
79
+ get_by_id = client.get_tool
80
+ find_by_name = client.find_tools
63
81
  return resolve_resource_reference(
64
82
  ctx,
65
83
  client,
66
84
  ref,
67
85
  "tool",
68
- client.get_tool,
69
- client.find_tools,
86
+ get_by_id,
87
+ find_by_name,
70
88
  "Tool",
71
89
  select=select,
72
90
  )
@@ -100,19 +118,16 @@ def _validate_name_match(provided: str | None, internal: str) -> str:
100
118
 
101
119
  def _check_duplicate_name(client: Any, tool_name: str) -> None:
102
120
  """Raise if a tool with the same name already exists."""
103
- try:
121
+
122
+ def _check_duplicate() -> None:
104
123
  existing = client.find_tools(name=tool_name)
105
124
  if existing:
106
125
  raise click.ClickException(
107
126
  f"A tool named '{tool_name}' already exists. "
108
127
  "Please change your plugin's 'name' to a unique value, then re-run."
109
128
  )
110
- except click.ClickException:
111
- # Re-raise ClickException (intended error)
112
- raise
113
- except Exception:
114
- # Non-fatal: best-effort duplicate check for other errors
115
- pass
129
+
130
+ handle_best_effort_check(_check_duplicate)
116
131
 
117
132
 
118
133
  def _parse_tags(tags: str | None) -> list[str]:
@@ -349,8 +364,6 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
349
364
 
350
365
  # Handle export option
351
366
  if export:
352
- from glaip_sdk.cli.utils import handle_resource_export
353
-
354
367
  handle_resource_export(
355
368
  ctx,
356
369
  tool,
@@ -371,8 +384,6 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
371
384
  if raw_tool_data:
372
385
  # Use raw API data - this preserves ALL fields
373
386
  # Format dates for better display (minimal postprocessing)
374
- from glaip_sdk.cli.utils import format_datetime_fields
375
-
376
387
  formatted_data = format_datetime_fields(raw_tool_data)
377
388
 
378
389
  # Display using output_result with raw data
@@ -33,3 +33,6 @@ UPDATE_CHECK_ENABLED = True
33
33
 
34
34
  # Agent instruction preview defaults
35
35
  DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT = 800
36
+
37
+ # Remote runs defaults
38
+ DEFAULT_REMOTE_RUNS_PAGE_LIMIT = 20
glaip_sdk/cli/main.py CHANGED
@@ -34,7 +34,7 @@ from glaip_sdk.cli.commands.mcps import mcps_group
34
34
  from glaip_sdk.cli.commands.models import models_group
35
35
  from glaip_sdk.cli.commands.tools import tools_group
36
36
  from glaip_sdk.cli.commands.transcripts import transcripts_group
37
- from glaip_sdk.cli.commands.update import update_command
37
+ from glaip_sdk.cli.commands.update import _build_upgrade_command, update_command
38
38
  from glaip_sdk.cli.config import load_config
39
39
  from glaip_sdk.cli.transcript import get_transcript_cache_stats
40
40
  from glaip_sdk.cli.update_notifier import maybe_notify_update
@@ -418,8 +418,6 @@ def update(check_only: bool, force: bool) -> None:
418
418
 
419
419
  # Update using pip
420
420
  try:
421
- from glaip_sdk.cli.commands.update import _build_upgrade_command
422
-
423
421
  cmd = list(_build_upgrade_command(include_prerelease=False))
424
422
  # Replace package name with "glaip-sdk" (main.py uses different name)
425
423
  cmd[-1] = "glaip-sdk"
@@ -16,7 +16,7 @@ from glaip_sdk.cli.commands.agents import get as agents_get_command
16
16
  from glaip_sdk.cli.commands.agents import run as agents_run_command
17
17
  from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
18
18
  from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
19
- from glaip_sdk.cli.utils import format_command_hint
19
+ from glaip_sdk.cli.utils import bind_slash_session_context, format_command_hint
20
20
 
21
21
  if TYPE_CHECKING: # pragma: no cover - type checking only
22
22
  from glaip_sdk.cli.slash.session import SlashSession
@@ -41,6 +41,7 @@ class AgentRunSession:
41
41
  self._contextual_completion_help: dict[str, str] = {
42
42
  "details": "Show this agent's configuration (+ expands prompt).",
43
43
  "help": "Display this context-aware menu.",
44
+ "runs": "✨ NEW · Browse remote run history for this agent.",
44
45
  "exit": "Return to the command palette.",
45
46
  "q": "Return to the command palette.",
46
47
  }
@@ -252,19 +253,8 @@ class AgentRunSession:
252
253
  @contextmanager
253
254
  def _bind_session_context(self) -> Any:
254
255
  """Temporarily attach this slash session to the Click context."""
255
- ctx_obj = getattr(self.session.ctx, "obj", None)
256
- has_context = isinstance(ctx_obj, dict)
257
- previous_session = ctx_obj.get("_slash_session") if has_context else None
258
- if has_context:
259
- ctx_obj["_slash_session"] = self.session
260
- try:
256
+ with bind_slash_session_context(self.session.ctx, self.session):
261
257
  yield
262
- finally:
263
- if has_context:
264
- if previous_session is None:
265
- ctx_obj.pop("_slash_session", None)
266
- else:
267
- ctx_obj["_slash_session"] = previous_session
268
258
 
269
259
  def _run_agent(self, agent_id: str, message: str) -> None:
270
260
  """Execute the agents run command for the active agent."""
@@ -177,8 +177,11 @@ def _iter_command_completions(
177
177
  return []
178
178
 
179
179
  commands = sorted(session._unique_commands.values(), key=lambda c: c.name)
180
+ agent_context = bool(getattr(session, "_current_agent", None))
180
181
 
181
182
  for cmd in commands:
183
+ if getattr(cmd, "agent_only", False) and not agent_context:
184
+ continue
182
185
  yield from _generate_command_completions(cmd, prefix, text, seen)
183
186
 
184
187