glaip-sdk 0.0.20__py3-none-any.whl → 0.7.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.
- glaip_sdk/__init__.py +44 -4
- glaip_sdk/_version.py +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1250 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +271 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +734 -143
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +14 -12
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +164 -23
- glaip_sdk/cli/config.py +49 -7
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +45 -32
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +344 -167
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +15 -22
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +5 -10
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +580 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +827 -232
- glaip_sdk/cli/slash/tui/__init__.py +34 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +147 -0
- glaip_sdk/cli/slash/tui/context.py +59 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +86 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +123 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -329
- glaip_sdk/cli/update_notifier.py +385 -24
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +3 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +370 -100
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -10
- glaip_sdk/client/mcps.py +166 -27
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +65 -74
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +583 -79
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +214 -56
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/icons.py +9 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +107 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +445 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +112 -0
- glaip_sdk/runner/langgraph.py +872 -0
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +468 -0
- glaip_sdk/utils/__init__.py +59 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- glaip_sdk/utils/import_resolver.py +524 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +38 -23
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +18 -8
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +534 -882
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +13 -54
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/{steps.py → steps/manager.py} +122 -26
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- glaip_sdk/utils/sync.py +162 -0
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +20 -28
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/METADATA +78 -23
- glaip_sdk-0.7.7.dist-info/RECORD +213 -0
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.7.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.7.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1412
- glaip_sdk/cli/commands/mcps.py +0 -1225
- glaip_sdk/cli/commands/tools.py +0 -597
- glaip_sdk/cli/utils.py +0 -1330
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.0.20.dist-info/RECORD +0 -80
- glaip_sdk-0.0.20.dist-info/entry_points.txt +0 -3
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Agent CLI commands package.
|
|
2
|
+
|
|
3
|
+
This package contains agent management commands split by operation.
|
|
4
|
+
The package is the canonical import surface.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# pylint: disable=duplicate-code
|
|
11
|
+
# Import from submodules
|
|
12
|
+
from glaip_sdk.cli.commands.agents._common import ( # noqa: E402
|
|
13
|
+
AGENT_NOT_FOUND_ERROR,
|
|
14
|
+
_get_agent_for_update,
|
|
15
|
+
_resolve_agent,
|
|
16
|
+
agents_group,
|
|
17
|
+
_coerce_mapping_candidate,
|
|
18
|
+
_display_agent_details,
|
|
19
|
+
_emit_verbose_guidance,
|
|
20
|
+
_fetch_full_agent_details,
|
|
21
|
+
_get_agent_model_name,
|
|
22
|
+
_get_language_model_display_name,
|
|
23
|
+
_model_from_config,
|
|
24
|
+
_prepare_agent_output,
|
|
25
|
+
_resolve_resources_by_name,
|
|
26
|
+
console,
|
|
27
|
+
)
|
|
28
|
+
from glaip_sdk.cli.commands.agents.create import create # noqa: E402
|
|
29
|
+
from glaip_sdk.cli.commands.agents.delete import delete # noqa: E402
|
|
30
|
+
from glaip_sdk.cli.commands.agents.get import get # noqa: E402
|
|
31
|
+
from glaip_sdk.cli.commands.agents.list import list_agents # noqa: E402
|
|
32
|
+
from glaip_sdk.cli.commands.agents.run import run, _maybe_attach_transcript_toggle # noqa: E402
|
|
33
|
+
from glaip_sdk.cli.commands.agents.sync_langflow import sync_langflow # noqa: E402
|
|
34
|
+
from glaip_sdk.cli.commands.agents.update import update # noqa: E402
|
|
35
|
+
|
|
36
|
+
# Import core functions for test compatibility
|
|
37
|
+
from glaip_sdk.cli.core.context import get_client # noqa: E402
|
|
38
|
+
from glaip_sdk.cli.core.rendering import with_client_and_spinner # noqa: E402
|
|
39
|
+
|
|
40
|
+
# Import display functions for test compatibility
|
|
41
|
+
from glaip_sdk.cli.display import ( # noqa: E402
|
|
42
|
+
handle_json_output,
|
|
43
|
+
handle_rich_output,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Import core output functions for test compatibility
|
|
47
|
+
from glaip_sdk.cli.core.output import ( # noqa: E402
|
|
48
|
+
handle_resource_export,
|
|
49
|
+
output_list,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Import rendering functions for test compatibility
|
|
53
|
+
from glaip_sdk.cli.core.rendering import ( # noqa: E402
|
|
54
|
+
build_renderer,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Import display functions for test compatibility
|
|
58
|
+
from glaip_sdk.cli.display import ( # noqa: E402
|
|
59
|
+
display_agent_run_suggestions,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Import rich helpers for test compatibility
|
|
63
|
+
from glaip_sdk.cli.rich_helpers import ( # noqa: E402
|
|
64
|
+
markup_text,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Import transcript functions for test compatibility
|
|
68
|
+
from glaip_sdk.cli.transcript import ( # noqa: E402
|
|
69
|
+
maybe_launch_post_run_viewer,
|
|
70
|
+
store_transcript_for_session,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Import IO functions for test compatibility
|
|
74
|
+
from glaip_sdk.cli.io import ( # noqa: E402
|
|
75
|
+
fetch_raw_resource_details,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Import utils for test compatibility
|
|
79
|
+
from glaip_sdk.utils import ( # noqa: E402
|
|
80
|
+
is_uuid,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
"AGENT_NOT_FOUND_ERROR",
|
|
85
|
+
"agents_group",
|
|
86
|
+
"create",
|
|
87
|
+
"delete",
|
|
88
|
+
"get",
|
|
89
|
+
"list_agents",
|
|
90
|
+
"run",
|
|
91
|
+
"sync_langflow",
|
|
92
|
+
"update",
|
|
93
|
+
"_get_agent_for_update",
|
|
94
|
+
"_resolve_agent",
|
|
95
|
+
"_coerce_mapping_candidate",
|
|
96
|
+
"_display_agent_details",
|
|
97
|
+
"_emit_verbose_guidance",
|
|
98
|
+
"_fetch_full_agent_details",
|
|
99
|
+
"_get_agent_model_name",
|
|
100
|
+
"_get_language_model_display_name",
|
|
101
|
+
"_model_from_config",
|
|
102
|
+
"_prepare_agent_output",
|
|
103
|
+
"_resolve_resources_by_name",
|
|
104
|
+
"_maybe_attach_transcript_toggle",
|
|
105
|
+
"get_client",
|
|
106
|
+
"with_client_and_spinner",
|
|
107
|
+
"console",
|
|
108
|
+
"handle_json_output",
|
|
109
|
+
"handle_rich_output",
|
|
110
|
+
"output_list",
|
|
111
|
+
"handle_resource_export",
|
|
112
|
+
"build_renderer",
|
|
113
|
+
"display_agent_run_suggestions",
|
|
114
|
+
"markup_text",
|
|
115
|
+
"maybe_launch_post_run_viewer",
|
|
116
|
+
"store_transcript_for_session",
|
|
117
|
+
"fetch_raw_resource_details",
|
|
118
|
+
"is_uuid",
|
|
119
|
+
]
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"""Common helpers and group definition for agent commands.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from copy import deepcopy
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
|
|
16
|
+
from glaip_sdk.branding import (
|
|
17
|
+
ERROR_STYLE,
|
|
18
|
+
HINT_PREFIX_STYLE,
|
|
19
|
+
WARNING_STYLE,
|
|
20
|
+
)
|
|
21
|
+
from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
|
|
22
|
+
from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS, DEFAULT_MODEL
|
|
23
|
+
from glaip_sdk.cli.context import get_ctx_value
|
|
24
|
+
from glaip_sdk.cli.display import (
|
|
25
|
+
build_resource_result_data,
|
|
26
|
+
handle_json_output,
|
|
27
|
+
handle_rich_output,
|
|
28
|
+
print_api_error,
|
|
29
|
+
)
|
|
30
|
+
from glaip_sdk.cli.hints import in_slash_mode
|
|
31
|
+
from glaip_sdk.cli.io import fetch_raw_resource_details
|
|
32
|
+
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
33
|
+
from glaip_sdk.cli.rich_helpers import markup_text
|
|
34
|
+
from glaip_sdk.cli.core.output import (
|
|
35
|
+
output_result,
|
|
36
|
+
)
|
|
37
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
38
|
+
from glaip_sdk.icons import ICON_AGENT
|
|
39
|
+
from glaip_sdk.utils import format_datetime, is_uuid
|
|
40
|
+
|
|
41
|
+
console = Console()
|
|
42
|
+
|
|
43
|
+
# Error message constants
|
|
44
|
+
AGENT_NOT_FOUND_ERROR = "Agent not found"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _safe_agent_attribute(agent: Any, name: str) -> Any:
|
|
48
|
+
"""Return attribute value for ``name`` while filtering Mock sentinels."""
|
|
49
|
+
try:
|
|
50
|
+
value = getattr(agent, name)
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
if hasattr(value, "_mock_name"):
|
|
55
|
+
return None
|
|
56
|
+
return value
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
|
|
60
|
+
"""Convert a mapping-like candidate to a plain dict when possible."""
|
|
61
|
+
if candidate is None:
|
|
62
|
+
return None
|
|
63
|
+
if isinstance(candidate, Mapping):
|
|
64
|
+
return dict(candidate)
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
|
|
69
|
+
"""Attempt to call the named method and coerce its output to a dict."""
|
|
70
|
+
method = getattr(agent, method_name, None)
|
|
71
|
+
if not callable(method):
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
candidate = method()
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
return _coerce_mapping_candidate(candidate)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
|
|
81
|
+
"""Try standard serialisation helpers to produce a mapping."""
|
|
82
|
+
for attr in ("model_dump", "dict", "to_dict"):
|
|
83
|
+
mapping = _call_agent_method(agent, attr)
|
|
84
|
+
if mapping is not None:
|
|
85
|
+
return mapping
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
|
|
90
|
+
"""Construct a minimal mapping from well-known agent attributes."""
|
|
91
|
+
fallback_fields = (
|
|
92
|
+
"id",
|
|
93
|
+
"name",
|
|
94
|
+
"instruction",
|
|
95
|
+
"description",
|
|
96
|
+
"model",
|
|
97
|
+
"agent_config",
|
|
98
|
+
*[field for field in AGENT_CONFIG_FIELDS if field not in ("name", "instruction", "model")],
|
|
99
|
+
"tool_configs",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
fallback: dict[str, Any] = {}
|
|
103
|
+
for field in fallback_fields:
|
|
104
|
+
value = _safe_agent_attribute(agent, field)
|
|
105
|
+
if value is not None:
|
|
106
|
+
fallback[field] = value
|
|
107
|
+
|
|
108
|
+
return fallback or {"name": str(agent)}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _prepare_agent_output(agent: Any) -> dict[str, Any]:
|
|
112
|
+
"""Build a JSON-serialisable mapping for CLI output."""
|
|
113
|
+
method_mapping = _coerce_agent_via_methods(agent)
|
|
114
|
+
if method_mapping is not None:
|
|
115
|
+
return method_mapping
|
|
116
|
+
|
|
117
|
+
intrinsic = _coerce_mapping_candidate(agent)
|
|
118
|
+
if intrinsic is not None:
|
|
119
|
+
return intrinsic
|
|
120
|
+
|
|
121
|
+
return _build_fallback_agent_mapping(agent)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
|
|
125
|
+
"""Fetch full agent details by ID to ensure all fields are populated."""
|
|
126
|
+
try:
|
|
127
|
+
agent_id = str(getattr(agent, "id", "")).strip()
|
|
128
|
+
if agent_id:
|
|
129
|
+
return client.agents.get_agent_by_id(agent_id)
|
|
130
|
+
except Exception:
|
|
131
|
+
# If fetching full details fails, continue with the resolved object
|
|
132
|
+
pass
|
|
133
|
+
return agent
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _normalise_model_name(value: Any) -> str | None:
|
|
137
|
+
"""Return a cleaned model name or None when not usable."""
|
|
138
|
+
if value is None:
|
|
139
|
+
return None
|
|
140
|
+
if isinstance(value, str):
|
|
141
|
+
cleaned = value.strip()
|
|
142
|
+
return cleaned or None
|
|
143
|
+
if isinstance(value, bool):
|
|
144
|
+
return None
|
|
145
|
+
return str(value)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _model_from_config(agent: Any) -> str | None:
|
|
149
|
+
"""Extract a usable model name from an agent's configuration mapping."""
|
|
150
|
+
config = getattr(agent, "agent_config", None)
|
|
151
|
+
if not config or not isinstance(config, dict):
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
for key in ("lm_name", "model"):
|
|
155
|
+
normalised = _normalise_model_name(config.get(key))
|
|
156
|
+
if normalised:
|
|
157
|
+
return normalised
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _get_agent_model_name(agent: Any) -> str | None:
|
|
162
|
+
"""Extract model name from agent configuration."""
|
|
163
|
+
config_model = _model_from_config(agent)
|
|
164
|
+
if config_model:
|
|
165
|
+
return config_model
|
|
166
|
+
|
|
167
|
+
normalised_attr = _normalise_model_name(getattr(agent, "model", None))
|
|
168
|
+
if normalised_attr:
|
|
169
|
+
return normalised_attr
|
|
170
|
+
|
|
171
|
+
return DEFAULT_MODEL
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _resolve_resources_by_name(
|
|
175
|
+
_client: Any, items: tuple[str, ...], resource_type: str, find_func: Any, label: str
|
|
176
|
+
) -> list[str]:
|
|
177
|
+
"""Resolve resource names/IDs to IDs, handling ambiguity.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
client: API client
|
|
181
|
+
items: Tuple of resource names/IDs
|
|
182
|
+
resource_type: Type of resource ("tool" or "agent")
|
|
183
|
+
find_func: Function to find resources by name
|
|
184
|
+
label: Label for error messages
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of resolved resource IDs
|
|
188
|
+
"""
|
|
189
|
+
out = []
|
|
190
|
+
for ref in items or ():
|
|
191
|
+
if is_uuid(ref):
|
|
192
|
+
out.append(ref)
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
matches = find_func(name=ref)
|
|
196
|
+
if not matches:
|
|
197
|
+
raise click.ClickException(f"{label} not found: {ref}")
|
|
198
|
+
if len(matches) > 1:
|
|
199
|
+
raise click.ClickException(f"Multiple {resource_type}s named '{ref}'. Use ID instead.")
|
|
200
|
+
out.append(str(matches[0].id))
|
|
201
|
+
return out
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _split_comma_separated_refs(items: tuple[str, ...] | None) -> tuple[str, ...]:
|
|
205
|
+
"""Expand comma-separated CLI values into a flat tuple.
|
|
206
|
+
|
|
207
|
+
Click ``multiple=True`` options can be provided as repeated flags (``--tools t1 --tools t2``)
|
|
208
|
+
or as a single comma-separated value (``--tools t1,t2``). Keep both forms working.
|
|
209
|
+
"""
|
|
210
|
+
if not items:
|
|
211
|
+
return ()
|
|
212
|
+
|
|
213
|
+
resolved: list[str] = []
|
|
214
|
+
for item in items:
|
|
215
|
+
if item is None:
|
|
216
|
+
continue
|
|
217
|
+
for part in str(item).split(","):
|
|
218
|
+
cleaned = part.strip()
|
|
219
|
+
if cleaned:
|
|
220
|
+
resolved.append(cleaned)
|
|
221
|
+
return tuple(resolved)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _fetch_and_format_raw_agent_data(client: Any, agent: Any) -> dict | None:
|
|
225
|
+
"""Fetch raw agent data and format it for display."""
|
|
226
|
+
try:
|
|
227
|
+
raw_agent_data = fetch_raw_resource_details(client, agent, "agents")
|
|
228
|
+
if not raw_agent_data:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
# Format dates for better display
|
|
232
|
+
formatted_data = raw_agent_data.copy()
|
|
233
|
+
if "created_at" in formatted_data:
|
|
234
|
+
formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
|
|
235
|
+
if "updated_at" in formatted_data:
|
|
236
|
+
formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
|
|
237
|
+
|
|
238
|
+
return formatted_data
|
|
239
|
+
except Exception:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
|
|
244
|
+
"""Format fallback agent data using Pydantic model."""
|
|
245
|
+
full_agent = _fetch_full_agent_details(client, agent)
|
|
246
|
+
|
|
247
|
+
# Define fields to extract
|
|
248
|
+
fields = [
|
|
249
|
+
"id",
|
|
250
|
+
"name",
|
|
251
|
+
"type",
|
|
252
|
+
"framework",
|
|
253
|
+
"version",
|
|
254
|
+
"description",
|
|
255
|
+
"instruction",
|
|
256
|
+
"created_at",
|
|
257
|
+
"updated_at",
|
|
258
|
+
"metadata",
|
|
259
|
+
"language_model_id",
|
|
260
|
+
"agent_config",
|
|
261
|
+
"tools",
|
|
262
|
+
"agents",
|
|
263
|
+
"mcps",
|
|
264
|
+
"a2a_profile",
|
|
265
|
+
"tool_configs",
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
result_data = build_resource_result_data(full_agent, fields)
|
|
269
|
+
|
|
270
|
+
# Handle missing instruction
|
|
271
|
+
if result_data.get("instruction") in ["N/A", None, ""]:
|
|
272
|
+
result_data["instruction"] = "-"
|
|
273
|
+
|
|
274
|
+
# Format dates for better display
|
|
275
|
+
for date_field in ["created_at", "updated_at"]:
|
|
276
|
+
if result_data.get(date_field) and result_data[date_field] not in ["N/A", None]:
|
|
277
|
+
result_data[date_field] = format_datetime(result_data[date_field])
|
|
278
|
+
|
|
279
|
+
return result_data
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _clamp_instruction_preview_limit(limit: int | None) -> int:
|
|
283
|
+
"""Normalise preview limit; 0 disables trimming."""
|
|
284
|
+
default = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
|
|
285
|
+
if limit is None: # pragma: no cover
|
|
286
|
+
return default
|
|
287
|
+
try:
|
|
288
|
+
limit_value = int(limit)
|
|
289
|
+
except (TypeError, ValueError): # pragma: no cover - defensive parsing
|
|
290
|
+
return default
|
|
291
|
+
|
|
292
|
+
if limit_value <= 0:
|
|
293
|
+
return 0
|
|
294
|
+
|
|
295
|
+
return limit_value
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _build_instruction_preview(value: Any, limit: int) -> tuple[Any, bool]:
|
|
299
|
+
"""Return a trimmed preview for long instruction strings."""
|
|
300
|
+
if not isinstance(value, str) or limit <= 0: # pragma: no cover
|
|
301
|
+
return value, False
|
|
302
|
+
|
|
303
|
+
if len(value) <= limit:
|
|
304
|
+
return value, False
|
|
305
|
+
|
|
306
|
+
trimmed_value = value[:limit].rstrip()
|
|
307
|
+
preview = f"{trimmed_value}\n\n... (preview trimmed)"
|
|
308
|
+
return preview, True
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _prepare_agent_details_payload(
|
|
312
|
+
data: dict[str, Any],
|
|
313
|
+
*,
|
|
314
|
+
instruction_preview_limit: int,
|
|
315
|
+
) -> tuple[dict[str, Any], bool]:
|
|
316
|
+
"""Return payload ready for rendering plus trim indicator."""
|
|
317
|
+
payload = deepcopy(data)
|
|
318
|
+
trimmed = False
|
|
319
|
+
if instruction_preview_limit > 0:
|
|
320
|
+
preview, trimmed = _build_instruction_preview(payload.get("instruction"), instruction_preview_limit)
|
|
321
|
+
if trimmed:
|
|
322
|
+
payload["instruction"] = preview
|
|
323
|
+
return payload, trimmed
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _show_instruction_trim_hint(
|
|
327
|
+
ctx: Any,
|
|
328
|
+
*,
|
|
329
|
+
trimmed: bool,
|
|
330
|
+
preview_limit: int,
|
|
331
|
+
) -> None:
|
|
332
|
+
"""Render hint describing how to expand or collapse the instruction preview."""
|
|
333
|
+
if not trimmed or preview_limit <= 0:
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
view = get_ctx_value(ctx, "view", "rich") if ctx is not None else "rich"
|
|
337
|
+
if view != "rich": # pragma: no cover - non-rich view handling
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
suffix = f"[dim](preview: {preview_limit:,} chars)[/]"
|
|
341
|
+
if in_slash_mode(ctx):
|
|
342
|
+
console.print(
|
|
343
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] Use '/details' again to toggle between trimmed and full prompts {suffix}"
|
|
344
|
+
)
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
console.print( # pragma: no cover - fallback hint rendering
|
|
348
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] Run 'aip agents get <agent> --instruction-preview <n>' "
|
|
349
|
+
f"to control prompt preview length {suffix}"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _display_agent_details(
|
|
354
|
+
ctx: Any,
|
|
355
|
+
client: Any,
|
|
356
|
+
agent: Any,
|
|
357
|
+
*,
|
|
358
|
+
instruction_preview_limit: int | None = None,
|
|
359
|
+
) -> None:
|
|
360
|
+
"""Display full agent details using raw API data to preserve ALL fields."""
|
|
361
|
+
if agent is None:
|
|
362
|
+
handle_rich_output(ctx, markup_text(f"[{ERROR_STYLE}]❌ No agent provided[/]"))
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
preview_limit = _clamp_instruction_preview_limit(instruction_preview_limit)
|
|
366
|
+
trimmed_instruction = False
|
|
367
|
+
|
|
368
|
+
# Try to fetch and format raw agent data first
|
|
369
|
+
with spinner_context(
|
|
370
|
+
ctx,
|
|
371
|
+
"[bold blue]Loading agent details…[/bold blue]",
|
|
372
|
+
console_override=console,
|
|
373
|
+
):
|
|
374
|
+
formatted_data = _fetch_and_format_raw_agent_data(client, agent)
|
|
375
|
+
|
|
376
|
+
if formatted_data:
|
|
377
|
+
# Use raw API data - this preserves ALL fields including account_id
|
|
378
|
+
panel_title = f"{ICON_AGENT} {formatted_data.get('name', 'Unknown')}"
|
|
379
|
+
payload, trimmed_instruction = _prepare_agent_details_payload(
|
|
380
|
+
formatted_data,
|
|
381
|
+
instruction_preview_limit=preview_limit,
|
|
382
|
+
)
|
|
383
|
+
output_result(
|
|
384
|
+
ctx,
|
|
385
|
+
payload,
|
|
386
|
+
title=panel_title,
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
# Fall back to Pydantic model data if raw fetch fails
|
|
390
|
+
handle_rich_output(
|
|
391
|
+
ctx,
|
|
392
|
+
markup_text(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]"),
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
with spinner_context(
|
|
396
|
+
ctx,
|
|
397
|
+
"[bold blue]Preparing fallback agent details…[/bold blue]",
|
|
398
|
+
console_override=console,
|
|
399
|
+
):
|
|
400
|
+
result_data = _format_fallback_agent_data(client, agent)
|
|
401
|
+
|
|
402
|
+
# Display using output_result
|
|
403
|
+
payload, trimmed_instruction = _prepare_agent_details_payload(
|
|
404
|
+
result_data,
|
|
405
|
+
instruction_preview_limit=preview_limit,
|
|
406
|
+
)
|
|
407
|
+
output_result(
|
|
408
|
+
ctx,
|
|
409
|
+
payload,
|
|
410
|
+
title="Agent Details",
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
_show_instruction_trim_hint(
|
|
414
|
+
ctx,
|
|
415
|
+
trimmed=trimmed_instruction,
|
|
416
|
+
preview_limit=preview_limit,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@click.group(name="agents", no_args_is_help=True)
|
|
421
|
+
def agents_group() -> None:
|
|
422
|
+
"""Agent management operations."""
|
|
423
|
+
pass
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _resolve_agent(
|
|
427
|
+
ctx: Any,
|
|
428
|
+
client: Any,
|
|
429
|
+
ref: str,
|
|
430
|
+
select: int | None = None,
|
|
431
|
+
interface_preference: str = "fuzzy",
|
|
432
|
+
) -> Any | None:
|
|
433
|
+
"""Resolve an agent by ID or name, supporting fuzzy and questionary interfaces.
|
|
434
|
+
|
|
435
|
+
This function provides agent-specific resolution with flexible UI options.
|
|
436
|
+
It wraps resolve_resource_reference with agent-specific configuration, allowing
|
|
437
|
+
users to choose between fuzzy search and traditional questionary selection.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
ctx: Click context for CLI command execution.
|
|
441
|
+
client: AIP SDK client instance.
|
|
442
|
+
ref: Agent identifier (UUID or name string).
|
|
443
|
+
select: Pre-selected index for non-interactive resolution (1-based).
|
|
444
|
+
interface_preference: UI preference - "fuzzy" for search or "questionary" for list.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
Agent object when found, None when resolution fails.
|
|
448
|
+
"""
|
|
449
|
+
# Configure agent-specific resolution parameters
|
|
450
|
+
resolution_config = {
|
|
451
|
+
"resource_type": "agent",
|
|
452
|
+
"get_by_id": client.agents.get_agent_by_id,
|
|
453
|
+
"find_by_name": client.agents.find_agents,
|
|
454
|
+
"label": "Agent",
|
|
455
|
+
}
|
|
456
|
+
# Use agent-specific resolution with flexible interface preference
|
|
457
|
+
return resolve_resource_reference(
|
|
458
|
+
ctx,
|
|
459
|
+
client,
|
|
460
|
+
ref,
|
|
461
|
+
resolution_config["resource_type"],
|
|
462
|
+
resolution_config["get_by_id"],
|
|
463
|
+
resolution_config["find_by_name"],
|
|
464
|
+
resolution_config["label"],
|
|
465
|
+
select=select,
|
|
466
|
+
interface_preference=interface_preference,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _get_agent_for_update(client: Any, agent_id: str) -> Any:
|
|
471
|
+
"""Resolve an agent reference for update operations."""
|
|
472
|
+
try:
|
|
473
|
+
return client.agents.get_agent_by_id(agent_id)
|
|
474
|
+
except Exception:
|
|
475
|
+
# Fall back to name-based resolution below.
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
matches = client.agents.find_agents(name=agent_id)
|
|
480
|
+
except Exception as e:
|
|
481
|
+
raise click.ClickException(f"Agent not found: {agent_id} ({e})") from e
|
|
482
|
+
|
|
483
|
+
match_list: list[Any]
|
|
484
|
+
if matches is None:
|
|
485
|
+
match_list = []
|
|
486
|
+
elif isinstance(matches, list):
|
|
487
|
+
match_list = matches
|
|
488
|
+
else:
|
|
489
|
+
try:
|
|
490
|
+
match_list = list(matches)
|
|
491
|
+
except TypeError:
|
|
492
|
+
match_list = []
|
|
493
|
+
|
|
494
|
+
if not match_list:
|
|
495
|
+
raise click.ClickException(f"Agent not found: {agent_id}")
|
|
496
|
+
if len(match_list) > 1:
|
|
497
|
+
raise click.ClickException(f"Multiple agents named '{agent_id}'. Use ID instead.")
|
|
498
|
+
return match_list[0]
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _running_in_slash_mode(ctx: Any) -> bool:
|
|
502
|
+
"""Return True if the command is executing inside the slash session."""
|
|
503
|
+
ctx_obj = getattr(ctx, "obj", None)
|
|
504
|
+
return isinstance(ctx_obj, dict) and bool(ctx_obj.get("_slash_session"))
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _emit_verbose_guidance(ctx: Any) -> None:
|
|
508
|
+
"""Explain the modern alternative to the deprecated --verbose flag."""
|
|
509
|
+
if _running_in_slash_mode(ctx):
|
|
510
|
+
message = (
|
|
511
|
+
"[dim]Tip:[/] Verbose streaming has been retired in the command palette. Run the agent normally and open "
|
|
512
|
+
"the post-run viewer (Ctrl+T) to inspect the transcript."
|
|
513
|
+
)
|
|
514
|
+
else:
|
|
515
|
+
message = (
|
|
516
|
+
"[dim]Tip:[/] `--verbose` is no longer supported. Re-run without the flag and toggle the post-run viewer "
|
|
517
|
+
"(Ctrl+T) for detailed output."
|
|
518
|
+
)
|
|
519
|
+
handle_rich_output(ctx, markup_text(message))
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _get_language_model_display_name(agent: Any, model: str | None) -> str:
|
|
523
|
+
"""Get display name for the language model."""
|
|
524
|
+
lm_display = getattr(agent, "model", None)
|
|
525
|
+
if not lm_display:
|
|
526
|
+
cfg = getattr(agent, "agent_config", {}) or {}
|
|
527
|
+
lm_display = cfg.get("lm_name") or cfg.get("model") or model or f"{DEFAULT_MODEL} (backend default)"
|
|
528
|
+
return lm_display
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _handle_command_exception(ctx: Any, e: Exception) -> None:
|
|
532
|
+
"""Handle exceptions during command execution with consistent error handling."""
|
|
533
|
+
if isinstance(e, click.ClickException):
|
|
534
|
+
if get_ctx_value(ctx, "view") == "json":
|
|
535
|
+
handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
|
|
536
|
+
raise
|
|
537
|
+
|
|
538
|
+
handle_json_output(ctx, error=e)
|
|
539
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
540
|
+
print_api_error(e)
|
|
541
|
+
raise click.exceptions.Exit(1) from e
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _handle_click_exception_for_json(ctx: Any, exc: click.ClickException) -> None:
|
|
545
|
+
"""Handle ClickException with JSON output support, then re-raise.
|
|
546
|
+
|
|
547
|
+
This helper extracts the common pattern used in agent commands for handling
|
|
548
|
+
ClickExceptions with JSON output support.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
ctx: Click context.
|
|
552
|
+
exc: The ClickException to handle.
|
|
553
|
+
|
|
554
|
+
Raises:
|
|
555
|
+
click.ClickException: Always re-raises the exception after handling JSON output.
|
|
556
|
+
"""
|
|
557
|
+
# Handle JSON output for ClickExceptions if view is JSON
|
|
558
|
+
if get_ctx_value(ctx, "view") == "json":
|
|
559
|
+
handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
|
|
560
|
+
# Re-raise ClickExceptions without additional processing
|
|
561
|
+
raise exc
|