glaip-sdk 0.0.6a0__py3-none-any.whl → 0.0.8__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/_version.py +42 -19
- glaip_sdk/branding.py +3 -3
- glaip_sdk/cli/commands/agents.py +136 -38
- glaip_sdk/cli/commands/configure.py +2 -2
- glaip_sdk/cli/commands/mcps.py +2 -4
- glaip_sdk/cli/commands/tools.py +2 -4
- glaip_sdk/cli/display.py +15 -15
- glaip_sdk/cli/main.py +51 -5
- glaip_sdk/cli/resolution.py +17 -9
- glaip_sdk/cli/slash/__init__.py +25 -0
- glaip_sdk/cli/slash/agent_session.py +146 -0
- glaip_sdk/cli/slash/prompt.py +198 -0
- glaip_sdk/cli/slash/session.py +665 -0
- glaip_sdk/cli/utils.py +162 -38
- glaip_sdk/client/agents.py +101 -73
- glaip_sdk/client/base.py +45 -14
- glaip_sdk/client/tools.py +44 -26
- glaip_sdk/models.py +1 -0
- glaip_sdk/utils/client_utils.py +95 -71
- glaip_sdk/utils/import_export.py +3 -1
- glaip_sdk/utils/rendering/renderer/base.py +170 -125
- glaip_sdk/utils/serialization.py +52 -16
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/METADATA +1 -1
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/RECORD +26 -22
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.6a0.dist-info → glaip_sdk-0.0.8.dist-info}/entry_points.txt +0 -0
glaip_sdk/_version.py
CHANGED
|
@@ -24,28 +24,51 @@ except Exception: # pragma: no cover
|
|
|
24
24
|
_toml = None # type: ignore
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def _try_get_installed_version() -> str | None:
|
|
28
|
+
"""Try to get version from installed package."""
|
|
28
29
|
try:
|
|
29
30
|
return version("glaip-sdk")
|
|
30
31
|
except PackageNotFoundError:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _try_get_dev_version() -> str | None:
|
|
36
|
+
"""Try to get version from local pyproject.toml for development."""
|
|
37
|
+
if _toml is None:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
here = Path(__file__).resolve()
|
|
44
|
+
root = here.parent.parent # project root (contains pyproject.toml)
|
|
45
|
+
pyproject = root / "pyproject.toml"
|
|
46
|
+
if not pyproject.is_file():
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
data = _toml.loads(pyproject.read_text(encoding="utf-8"))
|
|
50
|
+
ver = data.get("project", {}).get("version") or data.get("tool", {}).get(
|
|
51
|
+
"poetry", {}
|
|
52
|
+
).get("version")
|
|
53
|
+
if isinstance(ver, str) and ver:
|
|
54
|
+
return ver
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_version() -> str:
|
|
61
|
+
# Try installed version first
|
|
62
|
+
installed_version = _try_get_installed_version()
|
|
63
|
+
if installed_version:
|
|
64
|
+
return installed_version
|
|
65
|
+
|
|
66
|
+
# Try development version
|
|
67
|
+
dev_version = _try_get_dev_version()
|
|
68
|
+
if dev_version:
|
|
69
|
+
return dev_version
|
|
70
|
+
|
|
71
|
+
return "0.0.0.dev0"
|
|
49
72
|
|
|
50
73
|
|
|
51
74
|
__version__ = _get_version()
|
glaip_sdk/branding.py
CHANGED
|
@@ -31,8 +31,8 @@ except Exception: # pragma: no cover
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
# ---- minimal, readable styles (light blue + white theme) -----------------
|
|
34
|
-
PRIMARY = "
|
|
35
|
-
BORDER =
|
|
34
|
+
PRIMARY = "#15a2d8" # GDP Labs brand blue
|
|
35
|
+
BORDER = PRIMARY # Keep borders aligned with brand tone
|
|
36
36
|
TITLE_STYLE = f"bold {PRIMARY}"
|
|
37
37
|
LABEL = "bold"
|
|
38
38
|
|
|
@@ -95,7 +95,7 @@ GDP Labs AI Agents Package
|
|
|
95
95
|
# ---- public API -----------------------------------------------------------
|
|
96
96
|
def get_welcome_banner(self) -> str:
|
|
97
97
|
"""Get AIP banner with version info."""
|
|
98
|
-
banner = self.AIP_LOGO
|
|
98
|
+
banner = f"[{PRIMARY}]{self.AIP_LOGO}[/{PRIMARY}]"
|
|
99
99
|
line = f"Version: {self.version}"
|
|
100
100
|
banner = f"{banner}\n{line}"
|
|
101
101
|
return banner
|
glaip_sdk/cli/commands/agents.py
CHANGED
|
@@ -6,6 +6,7 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
|
+
from collections.abc import Mapping
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
@@ -47,11 +48,13 @@ from glaip_sdk.cli.utils import (
|
|
|
47
48
|
_fuzzy_pick_for_resources,
|
|
48
49
|
build_renderer,
|
|
49
50
|
coerce_to_row,
|
|
51
|
+
detect_export_format,
|
|
50
52
|
get_client,
|
|
51
53
|
get_ctx_value,
|
|
52
54
|
output_flags,
|
|
53
55
|
output_list,
|
|
54
56
|
output_result,
|
|
57
|
+
spinner_context,
|
|
55
58
|
)
|
|
56
59
|
from glaip_sdk.cli.validators import (
|
|
57
60
|
validate_agent_instruction_cli as validate_agent_instruction,
|
|
@@ -75,6 +78,91 @@ console = Console()
|
|
|
75
78
|
AGENT_NOT_FOUND_ERROR = "Agent not found"
|
|
76
79
|
|
|
77
80
|
|
|
81
|
+
def _safe_agent_attribute(agent: Any, name: str) -> Any:
|
|
82
|
+
"""Return attribute value for ``name`` while filtering Mock sentinels."""
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
value = getattr(agent, name)
|
|
86
|
+
except Exception:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
if hasattr(value, "_mock_name"):
|
|
90
|
+
return None
|
|
91
|
+
return value
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _coerce_mapping_candidate(candidate: Any) -> dict[str, Any] | None:
|
|
95
|
+
"""Convert a mapping-like candidate to a plain dict when possible."""
|
|
96
|
+
|
|
97
|
+
if candidate is None:
|
|
98
|
+
return None
|
|
99
|
+
if isinstance(candidate, Mapping):
|
|
100
|
+
return dict(candidate)
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _call_agent_method(agent: Any, method_name: str) -> dict[str, Any] | None:
|
|
105
|
+
"""Attempt to call the named method and coerce its output to a dict."""
|
|
106
|
+
|
|
107
|
+
method = getattr(agent, method_name, None)
|
|
108
|
+
if not callable(method):
|
|
109
|
+
return None
|
|
110
|
+
try:
|
|
111
|
+
candidate = method()
|
|
112
|
+
except Exception:
|
|
113
|
+
return None
|
|
114
|
+
return _coerce_mapping_candidate(candidate)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _coerce_agent_via_methods(agent: Any) -> dict[str, Any] | None:
|
|
118
|
+
"""Try standard serialisation helpers to produce a mapping."""
|
|
119
|
+
|
|
120
|
+
for attr in ("model_dump", "dict", "to_dict"):
|
|
121
|
+
mapping = _call_agent_method(agent, attr)
|
|
122
|
+
if mapping is not None:
|
|
123
|
+
return mapping
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
|
|
128
|
+
"""Construct a minimal mapping from well-known agent attributes."""
|
|
129
|
+
|
|
130
|
+
fallback_fields = (
|
|
131
|
+
"id",
|
|
132
|
+
"name",
|
|
133
|
+
"instruction",
|
|
134
|
+
"description",
|
|
135
|
+
"model",
|
|
136
|
+
"agent_config",
|
|
137
|
+
"tools",
|
|
138
|
+
"agents",
|
|
139
|
+
"mcps",
|
|
140
|
+
"timeout",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
fallback: dict[str, Any] = {}
|
|
144
|
+
for field in fallback_fields:
|
|
145
|
+
value = _safe_agent_attribute(agent, field)
|
|
146
|
+
if value is not None:
|
|
147
|
+
fallback[field] = value
|
|
148
|
+
|
|
149
|
+
return fallback or {"name": str(agent)}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _prepare_agent_output(agent: Any) -> dict[str, Any]:
|
|
153
|
+
"""Build a JSON-serialisable mapping for CLI output."""
|
|
154
|
+
|
|
155
|
+
method_mapping = _coerce_agent_via_methods(agent)
|
|
156
|
+
if method_mapping is not None:
|
|
157
|
+
return method_mapping
|
|
158
|
+
|
|
159
|
+
intrinsic = _coerce_mapping_candidate(agent)
|
|
160
|
+
if intrinsic is not None:
|
|
161
|
+
return intrinsic
|
|
162
|
+
|
|
163
|
+
return _build_fallback_agent_mapping(agent)
|
|
164
|
+
|
|
165
|
+
|
|
78
166
|
def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
|
|
79
167
|
"""Fetch full agent details by ID to ensure all fields are populated."""
|
|
80
168
|
try:
|
|
@@ -180,7 +268,7 @@ def _format_fallback_agent_data(client: Any, agent: Any) -> dict:
|
|
|
180
268
|
result_data = build_resource_result_data(full_agent, fields)
|
|
181
269
|
|
|
182
270
|
# Handle missing instruction
|
|
183
|
-
if
|
|
271
|
+
if result_data.get("instruction") in ["N/A", None, ""]:
|
|
184
272
|
result_data["instruction"] = "-"
|
|
185
273
|
|
|
186
274
|
# Format dates for better display
|
|
@@ -198,7 +286,12 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
198
286
|
return
|
|
199
287
|
|
|
200
288
|
# Try to fetch and format raw agent data first
|
|
201
|
-
|
|
289
|
+
with spinner_context(
|
|
290
|
+
ctx,
|
|
291
|
+
"[bold blue]Loading agent details…[/bold blue]",
|
|
292
|
+
console_override=console,
|
|
293
|
+
):
|
|
294
|
+
formatted_data = _fetch_and_format_raw_agent_data(client, agent)
|
|
202
295
|
|
|
203
296
|
if formatted_data:
|
|
204
297
|
# Use raw API data - this preserves ALL fields including account_id
|
|
@@ -215,7 +308,12 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
215
308
|
ctx, Text("[yellow]Falling back to Pydantic model data[/yellow]")
|
|
216
309
|
)
|
|
217
310
|
|
|
218
|
-
|
|
311
|
+
with spinner_context(
|
|
312
|
+
ctx,
|
|
313
|
+
"[bold blue]Preparing fallback agent details…[/bold blue]",
|
|
314
|
+
console_override=console,
|
|
315
|
+
):
|
|
316
|
+
result_data = _format_fallback_agent_data(client, agent)
|
|
219
317
|
|
|
220
318
|
# Display using output_result
|
|
221
319
|
output_result(
|
|
@@ -288,13 +386,18 @@ def list_agents(
|
|
|
288
386
|
"""List agents with optional filtering."""
|
|
289
387
|
try:
|
|
290
388
|
client = get_client(ctx)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
389
|
+
with spinner_context(
|
|
390
|
+
ctx,
|
|
391
|
+
"[bold blue]Fetching agents…[/bold blue]",
|
|
392
|
+
console_override=console,
|
|
393
|
+
):
|
|
394
|
+
agents = client.agents.list_agents(
|
|
395
|
+
agent_type=agent_type,
|
|
396
|
+
framework=framework,
|
|
397
|
+
name=name,
|
|
398
|
+
version=version,
|
|
399
|
+
sync_langflow_agents=sync_langflow,
|
|
400
|
+
)
|
|
298
401
|
|
|
299
402
|
# Define table columns: (data_key, header, style, width)
|
|
300
403
|
columns = [
|
|
@@ -356,18 +459,20 @@ def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> Non
|
|
|
356
459
|
)
|
|
357
460
|
|
|
358
461
|
# Handle export option
|
|
359
|
-
if export:
|
|
462
|
+
if export:
|
|
360
463
|
export_path = Path(export)
|
|
361
464
|
# Auto-detect format from file extension
|
|
362
|
-
|
|
363
|
-
detected_format = "yaml"
|
|
364
|
-
else:
|
|
365
|
-
detected_format = "json"
|
|
465
|
+
detected_format = detect_export_format(export_path)
|
|
366
466
|
|
|
367
467
|
# Always export comprehensive data - re-fetch agent with full details
|
|
368
468
|
try:
|
|
369
|
-
|
|
370
|
-
|
|
469
|
+
with spinner_context(
|
|
470
|
+
ctx,
|
|
471
|
+
"[bold blue]Fetching complete agent data…[/bold blue]",
|
|
472
|
+
console_override=console,
|
|
473
|
+
):
|
|
474
|
+
agent = client.agents.get_agent_by_id(agent.id)
|
|
475
|
+
except Exception as e:
|
|
371
476
|
handle_rich_output(
|
|
372
477
|
ctx,
|
|
373
478
|
Text(
|
|
@@ -584,8 +689,7 @@ def run(
|
|
|
584
689
|
handle_json_output(ctx, error=Exception(error_msg))
|
|
585
690
|
raise click.ClickException(error_msg)
|
|
586
691
|
except Exception as e:
|
|
587
|
-
|
|
588
|
-
raise click.ClickException(str(e))
|
|
692
|
+
_handle_command_exception(ctx, e)
|
|
589
693
|
|
|
590
694
|
|
|
591
695
|
def _handle_import_file_logic(
|
|
@@ -779,7 +883,7 @@ def _get_language_model_display_name(agent: Any, model: str | None) -> str:
|
|
|
779
883
|
|
|
780
884
|
def _handle_successful_creation(ctx: Any, agent: Any, model: str | None) -> None:
|
|
781
885
|
"""Handle successful agent creation output."""
|
|
782
|
-
handle_json_output(ctx, agent
|
|
886
|
+
handle_json_output(ctx, _prepare_agent_output(agent))
|
|
783
887
|
|
|
784
888
|
lm_display = _get_language_model_display_name(agent, model)
|
|
785
889
|
|
|
@@ -798,8 +902,8 @@ def _handle_successful_creation(ctx: Any, agent: Any, model: str | None) -> None
|
|
|
798
902
|
handle_rich_output(ctx, display_agent_run_suggestions(agent))
|
|
799
903
|
|
|
800
904
|
|
|
801
|
-
def
|
|
802
|
-
"""Handle exceptions during
|
|
905
|
+
def _handle_command_exception(ctx: Any, e: Exception) -> None:
|
|
906
|
+
"""Handle exceptions during command execution with consistent error handling."""
|
|
803
907
|
if isinstance(e, click.ClickException):
|
|
804
908
|
if get_ctx_value(ctx, "view") == "json":
|
|
805
909
|
handle_json_output(ctx, error=Exception(AGENT_NOT_FOUND_ERROR))
|
|
@@ -811,6 +915,11 @@ def _handle_creation_exception(ctx: Any, e: Exception) -> None:
|
|
|
811
915
|
raise click.ClickException(str(e))
|
|
812
916
|
|
|
813
917
|
|
|
918
|
+
def _handle_creation_exception(ctx: Any, e: Exception) -> None:
|
|
919
|
+
"""Handle exceptions during agent creation."""
|
|
920
|
+
_handle_command_exception(ctx, e)
|
|
921
|
+
|
|
922
|
+
|
|
814
923
|
@agents_group.command()
|
|
815
924
|
@click.option("--name", help="Agent name")
|
|
816
925
|
@click.option("--instruction", help="Agent instruction (prompt)")
|
|
@@ -1078,7 +1187,7 @@ def update(
|
|
|
1078
1187
|
|
|
1079
1188
|
updated_agent = client.agents.update_agent(agent.id, **update_data)
|
|
1080
1189
|
|
|
1081
|
-
handle_json_output(ctx, updated_agent
|
|
1190
|
+
handle_json_output(ctx, _prepare_agent_output(updated_agent))
|
|
1082
1191
|
handle_rich_output(ctx, display_update_success("Agent", updated_agent.name))
|
|
1083
1192
|
handle_rich_output(ctx, display_agent_run_suggestions(updated_agent))
|
|
1084
1193
|
|
|
@@ -1089,10 +1198,7 @@ def update(
|
|
|
1089
1198
|
# Re-raise ClickExceptions without additional processing
|
|
1090
1199
|
raise
|
|
1091
1200
|
except Exception as e:
|
|
1092
|
-
|
|
1093
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
1094
|
-
print_api_error(e)
|
|
1095
|
-
raise click.ClickException(str(e))
|
|
1201
|
+
_handle_command_exception(ctx, e)
|
|
1096
1202
|
|
|
1097
1203
|
|
|
1098
1204
|
@agents_group.command()
|
|
@@ -1133,10 +1239,7 @@ def delete(ctx: Any, agent_id: str, yes: bool) -> None:
|
|
|
1133
1239
|
# Re-raise ClickExceptions without additional processing
|
|
1134
1240
|
raise
|
|
1135
1241
|
except Exception as e:
|
|
1136
|
-
|
|
1137
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
1138
|
-
print_api_error(e)
|
|
1139
|
-
raise click.ClickException(str(e))
|
|
1242
|
+
_handle_command_exception(ctx, e)
|
|
1140
1243
|
|
|
1141
1244
|
|
|
1142
1245
|
@agents_group.command()
|
|
@@ -1149,9 +1252,7 @@ def delete(ctx: Any, agent_id: str, yes: bool) -> None:
|
|
|
1149
1252
|
)
|
|
1150
1253
|
@output_flags()
|
|
1151
1254
|
@click.pass_context
|
|
1152
|
-
def sync_langflow(
|
|
1153
|
-
ctx: Any, base_url: str | None, api_key: str | None
|
|
1154
|
-
) -> None: # pragma: no cover - integration-only path
|
|
1255
|
+
def sync_langflow(ctx: Any, base_url: str | None, api_key: str | None) -> None:
|
|
1155
1256
|
"""Sync agents with LangFlow server flows.
|
|
1156
1257
|
|
|
1157
1258
|
This command fetches all flows from the configured LangFlow server and
|
|
@@ -1192,7 +1293,4 @@ def sync_langflow(
|
|
|
1192
1293
|
)
|
|
1193
1294
|
|
|
1194
1295
|
except Exception as e:
|
|
1195
|
-
|
|
1196
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
1197
|
-
print_api_error(e)
|
|
1198
|
-
raise click.ClickException(str(e))
|
|
1296
|
+
_handle_command_exception(ctx, e)
|
|
@@ -47,8 +47,8 @@ def save_config(config: dict[str, Any]) -> None:
|
|
|
47
47
|
# Set secure file permissions
|
|
48
48
|
try:
|
|
49
49
|
os.chmod(CONFIG_FILE, 0o600)
|
|
50
|
-
except Exception:
|
|
51
|
-
pass
|
|
50
|
+
except Exception: # pragma: no cover - platform dependent best effort
|
|
51
|
+
pass
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
@click.group()
|
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -30,6 +30,7 @@ from glaip_sdk.cli.io import (
|
|
|
30
30
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
31
31
|
from glaip_sdk.cli.utils import (
|
|
32
32
|
coerce_to_row,
|
|
33
|
+
detect_export_format,
|
|
33
34
|
get_client,
|
|
34
35
|
get_ctx_value,
|
|
35
36
|
output_flags,
|
|
@@ -178,10 +179,7 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
|
|
|
178
179
|
if export:
|
|
179
180
|
export_path = Path(export)
|
|
180
181
|
# Auto-detect format from file extension
|
|
181
|
-
|
|
182
|
-
detected_format = "yaml"
|
|
183
|
-
else:
|
|
184
|
-
detected_format = "json"
|
|
182
|
+
detected_format = detect_export_format(export_path)
|
|
185
183
|
|
|
186
184
|
# Always export comprehensive data - re-fetch MCP with full details if needed
|
|
187
185
|
try:
|
glaip_sdk/cli/commands/tools.py
CHANGED
|
@@ -34,6 +34,7 @@ from glaip_sdk.cli.io import (
|
|
|
34
34
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
35
35
|
from glaip_sdk.cli.utils import (
|
|
36
36
|
coerce_to_row,
|
|
37
|
+
detect_export_format,
|
|
37
38
|
get_client,
|
|
38
39
|
get_ctx_value,
|
|
39
40
|
output_flags,
|
|
@@ -328,10 +329,7 @@ def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None
|
|
|
328
329
|
if export:
|
|
329
330
|
export_path = Path(export)
|
|
330
331
|
# Auto-detect format from file extension
|
|
331
|
-
|
|
332
|
-
detected_format = "yaml"
|
|
333
|
-
else:
|
|
334
|
-
detected_format = "json"
|
|
332
|
+
detected_format = detect_export_format(export_path)
|
|
335
333
|
|
|
336
334
|
# Always export comprehensive data - re-fetch tool with full details if needed
|
|
337
335
|
try:
|
glaip_sdk/cli/display.py
CHANGED
|
@@ -134,17 +134,18 @@ _MISSING = object()
|
|
|
134
134
|
def build_resource_result_data(resource: Any, fields: list[str]) -> dict[str, Any]:
|
|
135
135
|
"""Return a normalized mapping of ``fields`` extracted from ``resource``."""
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
result: dict[str, Any] = {}
|
|
138
|
+
for field in fields:
|
|
139
|
+
try:
|
|
140
|
+
value = getattr(resource, field)
|
|
141
|
+
except AttributeError:
|
|
142
|
+
value = _MISSING
|
|
143
|
+
except Exception:
|
|
144
|
+
value = _MISSING
|
|
141
145
|
|
|
146
|
+
result[field] = _normalise_field_value(field, value)
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
|
-
return getattr(resource, field, _MISSING)
|
|
146
|
-
except Exception:
|
|
147
|
-
return _MISSING
|
|
148
|
+
return result
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
def _normalise_field_value(field: str, value: Any) -> Any:
|
|
@@ -188,9 +189,7 @@ def _build_success_output_data(data: Any) -> dict[str, Any]:
|
|
|
188
189
|
return data if data is not None else {"success": True}
|
|
189
190
|
|
|
190
191
|
|
|
191
|
-
def handle_json_output(
|
|
192
|
-
ctx: Any, data: Any = None, error: Exception = None
|
|
193
|
-
) -> None: # pragma: no cover - formatting covered via integration tests
|
|
192
|
+
def handle_json_output(ctx: Any, data: Any = None, error: Exception = None) -> None:
|
|
194
193
|
"""Handle JSON output format for CLI commands.
|
|
195
194
|
|
|
196
195
|
Args:
|
|
@@ -248,9 +247,10 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
|
|
|
248
247
|
|
|
249
248
|
return AIPPanel(
|
|
250
249
|
f"[bold blue]💡 Next Steps:[/bold blue]\n\n"
|
|
251
|
-
f"🚀
|
|
252
|
-
f
|
|
253
|
-
f"📋
|
|
250
|
+
f"🚀 Start chatting with [bold]{agent.name}[/bold] right here:\n"
|
|
251
|
+
f" Type your message below and press Enter to run it immediately.\n\n"
|
|
252
|
+
f"📋 Prefer the CLI instead?\n"
|
|
253
|
+
f' [green]aip agents run {agent.id} "Your message here"[/green]\n'
|
|
254
254
|
f' [green]aip agents run "{agent.name}" "Your message here"[/green]\n\n'
|
|
255
255
|
f"🔧 Available options:\n"
|
|
256
256
|
f" [dim]--chat-history[/dim] Include previous conversation\n"
|
glaip_sdk/cli/main.py
CHANGED
|
@@ -24,11 +24,21 @@ from glaip_sdk.cli.commands.configure import (
|
|
|
24
24
|
from glaip_sdk.cli.commands.mcps import mcps_group
|
|
25
25
|
from glaip_sdk.cli.commands.models import models_group
|
|
26
26
|
from glaip_sdk.cli.commands.tools import tools_group
|
|
27
|
-
from glaip_sdk.
|
|
27
|
+
from glaip_sdk.cli.utils import spinner_context
|
|
28
|
+
from glaip_sdk.config.constants import (
|
|
29
|
+
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
30
|
+
)
|
|
28
31
|
from glaip_sdk.rich_components import AIPPanel, AIPTable
|
|
29
32
|
|
|
33
|
+
# Import SlashSession for potential mocking in tests
|
|
34
|
+
try:
|
|
35
|
+
from glaip_sdk.cli.slash import SlashSession
|
|
36
|
+
except ImportError: # pragma: no cover - optional slash dependencies
|
|
37
|
+
# Slash dependencies might not be available in all environments
|
|
38
|
+
SlashSession = None
|
|
39
|
+
|
|
30
40
|
|
|
31
|
-
@click.group()
|
|
41
|
+
@click.group(invoke_without_command=True)
|
|
32
42
|
@click.version_option(version=_SDK_VERSION, prog_name="aip")
|
|
33
43
|
@click.option("--api-url", envvar="AIP_API_URL", help="AIP API URL")
|
|
34
44
|
@click.option("--api-key", envvar="AIP_API_KEY", help="AIP API Key")
|
|
@@ -72,6 +82,15 @@ def main(
|
|
|
72
82
|
|
|
73
83
|
ctx.obj["tty"] = not no_tty
|
|
74
84
|
|
|
85
|
+
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
86
|
+
if _should_launch_slash(ctx) and SlashSession is not None:
|
|
87
|
+
session = SlashSession(ctx)
|
|
88
|
+
session.run()
|
|
89
|
+
ctx.exit()
|
|
90
|
+
else:
|
|
91
|
+
click.echo(ctx.get_help())
|
|
92
|
+
ctx.exit()
|
|
93
|
+
|
|
75
94
|
|
|
76
95
|
# Add command groups
|
|
77
96
|
main.add_command(agents_group)
|
|
@@ -87,6 +106,19 @@ main.add_command(configure_command)
|
|
|
87
106
|
# Tip: `--version` is provided by click.version_option above.
|
|
88
107
|
|
|
89
108
|
|
|
109
|
+
def _should_launch_slash(ctx: click.Context) -> bool:
|
|
110
|
+
"""Determine whether to open the command palette automatically."""
|
|
111
|
+
|
|
112
|
+
ctx_obj = ctx.obj or {}
|
|
113
|
+
if not bool(ctx_obj.get("tty", True)):
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
if not (sys.stdin.isatty() and sys.stdout.isatty()):
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
|
|
90
122
|
@main.command()
|
|
91
123
|
@click.pass_context
|
|
92
124
|
def status(ctx: Any) -> None:
|
|
@@ -152,9 +184,23 @@ def status(ctx: Any) -> None:
|
|
|
152
184
|
|
|
153
185
|
# Test connection by listing resources
|
|
154
186
|
try:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
with spinner_context(
|
|
188
|
+
ctx,
|
|
189
|
+
"[bold blue]Checking GL AIP status…[/bold blue]",
|
|
190
|
+
console_override=console,
|
|
191
|
+
spinner_style="cyan",
|
|
192
|
+
) as status_indicator:
|
|
193
|
+
if status_indicator is not None:
|
|
194
|
+
status_indicator.update("[bold blue]Fetching agents…[/bold blue]")
|
|
195
|
+
agents = client.list_agents()
|
|
196
|
+
|
|
197
|
+
if status_indicator is not None:
|
|
198
|
+
status_indicator.update("[bold blue]Fetching tools…[/bold blue]")
|
|
199
|
+
tools = client.list_tools()
|
|
200
|
+
|
|
201
|
+
if status_indicator is not None:
|
|
202
|
+
status_indicator.update("[bold blue]Fetching MCPs…[/bold blue]")
|
|
203
|
+
mcps = client.list_mcps()
|
|
158
204
|
|
|
159
205
|
# Create status table
|
|
160
206
|
table = AIPTable(title="🔗 GL AIP Status")
|
glaip_sdk/cli/resolution.py
CHANGED
|
@@ -12,7 +12,7 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
import click
|
|
14
14
|
|
|
15
|
-
from glaip_sdk.cli.utils import resolve_resource
|
|
15
|
+
from glaip_sdk.cli.utils import resolve_resource, spinner_context
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def resolve_resource_reference(
|
|
@@ -25,6 +25,7 @@ def resolve_resource_reference(
|
|
|
25
25
|
label: str,
|
|
26
26
|
select: int | None = None,
|
|
27
27
|
interface_preference: str | None = None,
|
|
28
|
+
spinner_message: str | None = None,
|
|
28
29
|
) -> Any | None:
|
|
29
30
|
"""Resolve resource reference (ID or name) with ambiguity handling.
|
|
30
31
|
|
|
@@ -47,14 +48,21 @@ def resolve_resource_reference(
|
|
|
47
48
|
click.ClickException: If resolution fails
|
|
48
49
|
"""
|
|
49
50
|
try:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
find_by_name=find_by_name_func,
|
|
55
|
-
label=label,
|
|
56
|
-
select=select,
|
|
57
|
-
interface_preference=interface_preference,
|
|
51
|
+
message = (
|
|
52
|
+
spinner_message
|
|
53
|
+
if spinner_message is not None
|
|
54
|
+
else f"[bold blue]Fetching {label}…[/bold blue]"
|
|
58
55
|
)
|
|
56
|
+
with spinner_context(ctx, message, spinner_style="cyan") as status_indicator:
|
|
57
|
+
return resolve_resource(
|
|
58
|
+
ctx,
|
|
59
|
+
reference,
|
|
60
|
+
get_by_id=get_by_id_func,
|
|
61
|
+
find_by_name=find_by_name_func,
|
|
62
|
+
label=label,
|
|
63
|
+
select=select,
|
|
64
|
+
interface_preference=interface_preference,
|
|
65
|
+
status_indicator=status_indicator,
|
|
66
|
+
)
|
|
59
67
|
except Exception as e:
|
|
60
68
|
raise click.ClickException(f"Failed to resolve {resource_type.lower()}: {e}")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Slash command palette entrypoints.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
8
|
+
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
9
|
+
from glaip_sdk.cli.commands.configure import configure_command, load_config
|
|
10
|
+
from glaip_sdk.cli.utils import get_client
|
|
11
|
+
|
|
12
|
+
from .agent_session import AgentRunSession
|
|
13
|
+
from .prompt import _HAS_PROMPT_TOOLKIT
|
|
14
|
+
from .session import SlashSession
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentRunSession",
|
|
18
|
+
"SlashSession",
|
|
19
|
+
"_HAS_PROMPT_TOOLKIT",
|
|
20
|
+
"agents_get_command",
|
|
21
|
+
"agents_run_command",
|
|
22
|
+
"configure_command",
|
|
23
|
+
"get_client",
|
|
24
|
+
"load_config",
|
|
25
|
+
]
|