glaip-sdk 0.6.3__py3-none-any.whl → 0.6.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
glaip_sdk/agents/base.py CHANGED
@@ -51,10 +51,12 @@ from typing import TYPE_CHECKING, Any
51
51
 
52
52
  from glaip_sdk.registry import get_agent_registry, get_mcp_registry, get_tool_registry
53
53
  from glaip_sdk.utils.discovery import find_agent
54
+ from glaip_sdk.utils.resource_refs import is_uuid
54
55
  from glaip_sdk.utils.runtime_config import normalize_runtime_config_keys
55
56
 
56
57
  if TYPE_CHECKING:
57
58
  from glaip_sdk.models import AgentResponse
59
+ from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
58
60
 
59
61
  logger = logging.getLogger(__name__)
60
62
 
@@ -519,7 +521,7 @@ class Agent:
519
521
 
520
522
  return self
521
523
 
522
- def _build_config(self, tool_registry: Any, mcp_registry: Any) -> dict[str, Any]:
524
+ def _build_config(self, tool_registry: ToolRegistry, mcp_registry: MCPRegistry) -> dict[str, Any]:
523
525
  """Build the base configuration dictionary.
524
526
 
525
527
  Args:
@@ -558,9 +560,9 @@ class Agent:
558
560
  if self.mcps:
559
561
  config["mcps"] = self._resolve_mcps(mcp_registry)
560
562
 
561
- # Handle mcp_configs
563
+ # Handle mcp_configs - normalize keys to MCP IDs
562
564
  if self.mcp_configs:
563
- config["mcp_configs"] = self.mcp_configs
565
+ config["mcp_configs"] = self._resolve_mcp_configs(mcp_registry)
564
566
 
565
567
  # Handle a2a_profile
566
568
  if self.a2a_profile:
@@ -568,7 +570,7 @@ class Agent:
568
570
 
569
571
  return config
570
572
 
571
- def _resolve_mcps(self, registry: Any) -> list[str]:
573
+ def _resolve_mcps(self, registry: MCPRegistry) -> list[str]:
572
574
  """Resolve MCP references to IDs using MCPRegistry.
573
575
 
574
576
  Uses the global MCPRegistry to cache MCP objects across deployments.
@@ -586,7 +588,7 @@ class Agent:
586
588
 
587
589
  return [registry.resolve(mcp_ref).id for mcp_ref in self.mcps]
588
590
 
589
- def _resolve_tools(self, registry: Any) -> list[str]:
591
+ def _resolve_tools(self, registry: ToolRegistry) -> list[str]:
590
592
  """Resolve tool references to IDs using ToolRegistry.
591
593
 
592
594
  Uses the global ToolRegistry to cache Tool objects across deployments.
@@ -605,7 +607,7 @@ class Agent:
605
607
  # Resolve each tool reference to a Tool object, extract ID
606
608
  return [registry.resolve(tool_ref).id for tool_ref in self.tools]
607
609
 
608
- def _resolve_tool_configs(self, registry: Any) -> dict[str, Any]:
610
+ def _resolve_tool_configs(self, registry: ToolRegistry) -> dict[str, Any]:
609
611
  """Resolve tool_configs keys from tool names/classes to tool IDs.
610
612
 
611
613
  Allows tool_configs to be defined with tool names, class names, or
@@ -639,7 +641,7 @@ class Agent:
639
641
 
640
642
  for key, config in self.tool_configs.items():
641
643
  # If key is already a UUID-like string, pass through
642
- if isinstance(key, str) and len(key) == 36 and "-" in key:
644
+ if isinstance(key, str) and is_uuid(key):
643
645
  resolved[key] = config
644
646
  continue
645
647
 
@@ -652,7 +654,51 @@ class Agent:
652
654
 
653
655
  return resolved
654
656
 
655
- def _resolve_agents(self, registry: Any) -> list:
657
+ def _resolve_mcp_configs(self, registry: MCPRegistry) -> dict[str, Any]:
658
+ """Resolve mcp_configs keys from MCP names/objects to MCP IDs.
659
+
660
+ Allows mcp_configs to be defined with MCP names, MCP objects, or UUIDs
661
+ as keys. Keys are resolved to MCP IDs using the MCPRegistry.
662
+
663
+ Supported key formats:
664
+ - MCP object (with id): uses id directly
665
+ - MCP name string: resolved via registry to ID
666
+ - MCP ID (UUID string): passed through unchanged
667
+
668
+ Args:
669
+ registry: The MCP registry.
670
+
671
+ Returns:
672
+ Dict with resolved MCP IDs as keys and configs as values.
673
+ """
674
+ if not self.mcp_configs:
675
+ return {}
676
+
677
+ resolved: dict[str, Any] = {}
678
+
679
+ for key, config in self.mcp_configs.items():
680
+ try:
681
+ # If key is already a UUID-like string, pass through
682
+ if isinstance(key, str) and is_uuid(key):
683
+ resolved_id = key
684
+ else:
685
+ mcp = registry.resolve(key)
686
+ resolved_id = mcp.id
687
+
688
+ if resolved_id in resolved:
689
+ raise ValueError(
690
+ f"Duplicate mcp_configs entries resolve to the same MCP id '{resolved_id}' (key={key!r})"
691
+ )
692
+
693
+ resolved[resolved_id] = config
694
+ except (ValueError, KeyError) as exc:
695
+ raise ValueError(
696
+ f"Failed to resolve mcp config key {key!r} (type={type(key).__name__}): {exc}"
697
+ ) from exc
698
+
699
+ return resolved
700
+
701
+ def _resolve_agents(self, registry: AgentRegistry) -> list:
656
702
  """Resolve sub-agent references using AgentRegistry.
657
703
 
658
704
  Uses the global AgentRegistry to cache Agent objects across deployments.
glaip_sdk/cli/auth.py CHANGED
@@ -460,7 +460,7 @@ def _prompt_secret_with_placeholder(
460
460
  )
461
461
 
462
462
  attempts = 0
463
- while attempts <= retry_limit:
463
+ while attempts <= retry_limit: # pragma: no cover
464
464
  response = click.prompt(
465
465
  prompt_message,
466
466
  default="",
@@ -0,0 +1,79 @@
1
+ """CLI core modules for glaip-sdk.
2
+
3
+ This package contains focused modules extracted from the monolithic cli/utils.py:
4
+ - context: Click context helpers, config loading, credential resolution
5
+ - prompting: prompt_toolkit + questionary wrappers, validators
6
+ - rendering: Rich console helpers, viewer launchers, renderer builders
7
+ - output: Table/console output utilities, list rendering
8
+
9
+ Authors:
10
+ Raymond Christopher (raymond.christopher@gdplabs.id)
11
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
12
+ """ # pylint: disable=duplicate-code
13
+
14
+ from __future__ import annotations
15
+
16
+ # Re-export all public APIs from submodules for convenience
17
+ from glaip_sdk.cli.core.context import (
18
+ bind_slash_session_context,
19
+ get_client,
20
+ handle_best_effort_check,
21
+ restore_slash_session_context,
22
+ )
23
+ from glaip_sdk.cli.core.output import (
24
+ coerce_to_row,
25
+ detect_export_format,
26
+ fetch_resource_for_export,
27
+ format_datetime_fields,
28
+ format_size,
29
+ handle_resource_export,
30
+ output_list,
31
+ output_result,
32
+ parse_json_line,
33
+ resolve_resource,
34
+ handle_ambiguous_resource,
35
+ sdk_version,
36
+ )
37
+ from glaip_sdk.cli.core.prompting import (
38
+ _fuzzy_pick_for_resources,
39
+ prompt_export_choice_questionary,
40
+ questionary_safe_ask,
41
+ )
42
+ from glaip_sdk.cli.core.rendering import (
43
+ build_renderer,
44
+ spinner_context,
45
+ stop_spinner,
46
+ update_spinner,
47
+ with_client_and_spinner,
48
+ )
49
+
50
+ __all__ = [
51
+ # Context
52
+ "bind_slash_session_context",
53
+ "get_client",
54
+ "handle_best_effort_check",
55
+ "restore_slash_session_context",
56
+ # Prompting
57
+ "_fuzzy_pick_for_resources",
58
+ "prompt_export_choice_questionary",
59
+ "questionary_safe_ask",
60
+ # Rendering
61
+ "build_renderer",
62
+ "spinner_context",
63
+ "stop_spinner",
64
+ "update_spinner",
65
+ "with_client_and_spinner",
66
+ # Output
67
+ "coerce_to_row",
68
+ "detect_export_format",
69
+ "fetch_resource_for_export",
70
+ "format_datetime_fields",
71
+ "format_size",
72
+ "handle_resource_export",
73
+ "output_list",
74
+ "output_result",
75
+ "parse_json_line",
76
+ "resolve_resource",
77
+ "handle_ambiguous_resource",
78
+ "sdk_version",
79
+ ]
@@ -0,0 +1,124 @@
1
+ """CLI context helpers, config loading, and credential resolution.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib
11
+ import os
12
+ from collections.abc import Mapping
13
+ from contextlib import contextmanager
14
+ from typing import TYPE_CHECKING, Any, cast
15
+
16
+ import click
17
+
18
+ from glaip_sdk.cli.config import load_config
19
+ from glaip_sdk.cli.hints import command_hint
20
+
21
+ if TYPE_CHECKING: # pragma: no cover - import-only during type checking
22
+ from glaip_sdk import Client
23
+
24
+
25
+ @contextmanager
26
+ def bind_slash_session_context(ctx: Any, session: Any) -> Any:
27
+ """Temporarily attach a slash session to the Click context.
28
+
29
+ Args:
30
+ ctx: Click context object.
31
+ session: SlashSession instance to bind.
32
+
33
+ Yields:
34
+ None - context manager for use in with statement.
35
+ """
36
+ ctx_obj = getattr(ctx, "obj", None)
37
+ has_context = isinstance(ctx_obj, dict)
38
+ previous_session = ctx_obj.get("_slash_session") if has_context else None
39
+ if has_context:
40
+ ctx_obj["_slash_session"] = session
41
+ try:
42
+ yield
43
+ finally:
44
+ if has_context:
45
+ if previous_session is None:
46
+ ctx_obj.pop("_slash_session", None)
47
+ else:
48
+ ctx_obj["_slash_session"] = previous_session
49
+
50
+
51
+ def restore_slash_session_context(ctx_obj: dict[str, Any], previous_session: Any | None) -> None:
52
+ """Restore slash session context after operation.
53
+
54
+ Args:
55
+ ctx_obj: Click context obj dictionary.
56
+ previous_session: Previous session to restore, or None to remove.
57
+ """
58
+ if previous_session is None:
59
+ ctx_obj.pop("_slash_session", None)
60
+ else:
61
+ ctx_obj["_slash_session"] = previous_session
62
+
63
+
64
+ def handle_best_effort_check(
65
+ check_func: Any,
66
+ ) -> None:
67
+ """Handle best-effort duplicate/existence checks with proper exception handling.
68
+
69
+ Args:
70
+ check_func: Function that performs the check and raises ClickException if duplicate found.
71
+ """
72
+ try:
73
+ check_func()
74
+ except click.ClickException:
75
+ raise
76
+ except Exception:
77
+ # Non-fatal: best-effort duplicate check
78
+ pass
79
+
80
+
81
+ def get_client(ctx: Any) -> Client: # pragma: no cover
82
+ """Get configured client from context and account store (ctx > account)."""
83
+ # Import here to avoid circular import
84
+ from glaip_sdk.cli.auth import resolve_credentials # noqa: PLC0415
85
+
86
+ module = importlib.import_module("glaip_sdk")
87
+ client_class = cast("type[Client]", module.Client)
88
+ context_config_obj = getattr(ctx, "obj", None)
89
+ context_config = context_config_obj if isinstance(context_config_obj, Mapping) else {}
90
+
91
+ account_name = context_config.get("account_name")
92
+ api_url, api_key, _ = resolve_credentials(
93
+ account_name=account_name,
94
+ api_url=context_config.get("api_url"),
95
+ api_key=context_config.get("api_key"),
96
+ )
97
+
98
+ if not api_url or not api_key:
99
+ configure_hint = command_hint("accounts add", slash_command="login", ctx=ctx)
100
+ actions: list[str] = []
101
+ if configure_hint:
102
+ actions.append(f"Run `{configure_hint}` to add an account profile")
103
+ else:
104
+ actions.append("add an account with 'aip accounts add'")
105
+ raise click.ClickException(f"Missing api_url/api_key. {' or '.join(actions)}.")
106
+
107
+ # Get timeout from context or config
108
+ timeout = context_config.get("timeout")
109
+ if timeout is None:
110
+ raw_timeout = os.getenv("AIP_TIMEOUT", "0") or "0"
111
+ try:
112
+ timeout = float(raw_timeout) if raw_timeout != "0" else None
113
+ except ValueError:
114
+ timeout = None
115
+ if timeout is None:
116
+ # Fallback to legacy config
117
+ file_config = load_config() or {}
118
+ timeout = file_config.get("timeout")
119
+
120
+ return client_class(
121
+ api_url=api_url,
122
+ api_key=api_key,
123
+ timeout=float(timeout or 30.0),
124
+ )