glaip-sdk 0.0.7__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/branding.py +3 -3
- glaip_sdk/cli/commands/agents.py +119 -12
- glaip_sdk/cli/display.py +4 -3
- 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 +99 -5
- glaip_sdk/utils/rendering/renderer/base.py +19 -1
- glaip_sdk/utils/serialization.py +49 -17
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.8.dist-info}/METADATA +1 -1
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.8.dist-info}/RECORD +16 -12
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.8.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.0.8.dist-info}/entry_points.txt +0 -0
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
|
|
|
@@ -53,6 +54,7 @@ from glaip_sdk.cli.utils import (
|
|
|
53
54
|
output_flags,
|
|
54
55
|
output_list,
|
|
55
56
|
output_result,
|
|
57
|
+
spinner_context,
|
|
56
58
|
)
|
|
57
59
|
from glaip_sdk.cli.validators import (
|
|
58
60
|
validate_agent_instruction_cli as validate_agent_instruction,
|
|
@@ -76,6 +78,91 @@ console = Console()
|
|
|
76
78
|
AGENT_NOT_FOUND_ERROR = "Agent not found"
|
|
77
79
|
|
|
78
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
|
+
|
|
79
166
|
def _fetch_full_agent_details(client: Any, agent: Any) -> Any | None:
|
|
80
167
|
"""Fetch full agent details by ID to ensure all fields are populated."""
|
|
81
168
|
try:
|
|
@@ -199,7 +286,12 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
199
286
|
return
|
|
200
287
|
|
|
201
288
|
# Try to fetch and format raw agent data first
|
|
202
|
-
|
|
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)
|
|
203
295
|
|
|
204
296
|
if formatted_data:
|
|
205
297
|
# Use raw API data - this preserves ALL fields including account_id
|
|
@@ -216,7 +308,12 @@ def _display_agent_details(ctx: Any, client: Any, agent: Any) -> None:
|
|
|
216
308
|
ctx, Text("[yellow]Falling back to Pydantic model data[/yellow]")
|
|
217
309
|
)
|
|
218
310
|
|
|
219
|
-
|
|
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)
|
|
220
317
|
|
|
221
318
|
# Display using output_result
|
|
222
319
|
output_result(
|
|
@@ -289,13 +386,18 @@ def list_agents(
|
|
|
289
386
|
"""List agents with optional filtering."""
|
|
290
387
|
try:
|
|
291
388
|
client = get_client(ctx)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
)
|
|
299
401
|
|
|
300
402
|
# Define table columns: (data_key, header, style, width)
|
|
301
403
|
columns = [
|
|
@@ -364,7 +466,12 @@ def get(ctx: Any, agent_ref: str, select: int | None, export: str | None) -> Non
|
|
|
364
466
|
|
|
365
467
|
# Always export comprehensive data - re-fetch agent with full details
|
|
366
468
|
try:
|
|
367
|
-
|
|
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)
|
|
368
475
|
except Exception as e:
|
|
369
476
|
handle_rich_output(
|
|
370
477
|
ctx,
|
|
@@ -776,7 +883,7 @@ def _get_language_model_display_name(agent: Any, model: str | None) -> str:
|
|
|
776
883
|
|
|
777
884
|
def _handle_successful_creation(ctx: Any, agent: Any, model: str | None) -> None:
|
|
778
885
|
"""Handle successful agent creation output."""
|
|
779
|
-
handle_json_output(ctx, agent
|
|
886
|
+
handle_json_output(ctx, _prepare_agent_output(agent))
|
|
780
887
|
|
|
781
888
|
lm_display = _get_language_model_display_name(agent, model)
|
|
782
889
|
|
|
@@ -1080,7 +1187,7 @@ def update(
|
|
|
1080
1187
|
|
|
1081
1188
|
updated_agent = client.agents.update_agent(agent.id, **update_data)
|
|
1082
1189
|
|
|
1083
|
-
handle_json_output(ctx, updated_agent
|
|
1190
|
+
handle_json_output(ctx, _prepare_agent_output(updated_agent))
|
|
1084
1191
|
handle_rich_output(ctx, display_update_success("Agent", updated_agent.name))
|
|
1085
1192
|
handle_rich_output(ctx, display_agent_run_suggestions(updated_agent))
|
|
1086
1193
|
|
glaip_sdk/cli/display.py
CHANGED
|
@@ -247,9 +247,10 @@ def display_agent_run_suggestions(agent: Any) -> Panel:
|
|
|
247
247
|
|
|
248
248
|
return AIPPanel(
|
|
249
249
|
f"[bold blue]💡 Next Steps:[/bold blue]\n\n"
|
|
250
|
-
f"🚀
|
|
251
|
-
f
|
|
252
|
-
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'
|
|
253
254
|
f' [green]aip agents run "{agent.name}" "Your message here"[/green]\n\n'
|
|
254
255
|
f"🔧 Available options:\n"
|
|
255
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
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Agent-specific interaction loop for the command palette.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
14
|
+
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
17
|
+
from .session import SlashSession
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentRunSession:
|
|
21
|
+
"""Per-agent execution context for the command palette."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, session: SlashSession, agent: Any) -> None:
|
|
24
|
+
self.session = session
|
|
25
|
+
self.agent = agent
|
|
26
|
+
self.console = session.console
|
|
27
|
+
self._agent_id = str(getattr(agent, "id", ""))
|
|
28
|
+
self._agent_name = getattr(agent, "name", "") or self._agent_id
|
|
29
|
+
self._prompt_placeholder: str = (
|
|
30
|
+
"Chat with this agent here; use / for shortcuts."
|
|
31
|
+
)
|
|
32
|
+
self._contextual_completion_help: dict[str, str] = {
|
|
33
|
+
"details": "Show this agent's full configuration.",
|
|
34
|
+
"help": "Display this context-aware menu.",
|
|
35
|
+
"exit": "Return to the command palette.",
|
|
36
|
+
"q": "Return to the command palette.",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def run(self) -> None:
|
|
40
|
+
self.session.set_contextual_commands(
|
|
41
|
+
self._contextual_completion_help, include_global=False
|
|
42
|
+
)
|
|
43
|
+
try:
|
|
44
|
+
self._display_agent_info()
|
|
45
|
+
self._run_agent_loop()
|
|
46
|
+
finally:
|
|
47
|
+
self.session.set_contextual_commands(None)
|
|
48
|
+
|
|
49
|
+
def _display_agent_info(self) -> None:
|
|
50
|
+
"""Display agent information and summary."""
|
|
51
|
+
self.session._render_header(self.agent, focus_agent=True)
|
|
52
|
+
|
|
53
|
+
def _run_agent_loop(self) -> None:
|
|
54
|
+
"""Run the main agent interaction loop."""
|
|
55
|
+
while True:
|
|
56
|
+
raw = self._get_user_input()
|
|
57
|
+
if raw is None:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
raw = raw.strip()
|
|
61
|
+
if not raw:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
if raw.startswith("/"):
|
|
65
|
+
if not self._handle_slash_command(raw, self._agent_id):
|
|
66
|
+
return
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
self._run_agent(self._agent_id, raw)
|
|
70
|
+
|
|
71
|
+
def _get_user_input(self) -> str | None:
|
|
72
|
+
"""Get user input with proper error handling."""
|
|
73
|
+
try:
|
|
74
|
+
raw = self.session._prompt(
|
|
75
|
+
f"{self._agent_name} ({self._agent_id})\n› ",
|
|
76
|
+
placeholder=self._prompt_placeholder,
|
|
77
|
+
)
|
|
78
|
+
if self._prompt_placeholder:
|
|
79
|
+
# Show the guidance once, then fall back to a clean prompt.
|
|
80
|
+
self._prompt_placeholder = ""
|
|
81
|
+
return raw
|
|
82
|
+
except EOFError:
|
|
83
|
+
self.console.print("\nExiting agent context.")
|
|
84
|
+
return None
|
|
85
|
+
except KeyboardInterrupt:
|
|
86
|
+
self.console.print("")
|
|
87
|
+
return ""
|
|
88
|
+
|
|
89
|
+
def _handle_slash_command(self, raw: str, agent_id: str) -> bool:
|
|
90
|
+
"""Handle slash commands in agent context. Returns False if should exit."""
|
|
91
|
+
# Handle simple commands first
|
|
92
|
+
if raw == "/":
|
|
93
|
+
return self._handle_help_command()
|
|
94
|
+
|
|
95
|
+
if raw in {"/exit", "/back", "/q"}:
|
|
96
|
+
return self._handle_exit_command()
|
|
97
|
+
|
|
98
|
+
if raw in {"/details", "/detail"}:
|
|
99
|
+
return self._handle_details_command(agent_id)
|
|
100
|
+
|
|
101
|
+
if raw in {"/help", "/?"}:
|
|
102
|
+
return self._handle_help_command()
|
|
103
|
+
|
|
104
|
+
# Handle other commands through the main session
|
|
105
|
+
return self._handle_other_command(raw)
|
|
106
|
+
|
|
107
|
+
def _handle_help_command(self) -> bool:
|
|
108
|
+
"""Handle help command."""
|
|
109
|
+
self.session._cmd_help([], True)
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
def _handle_exit_command(self) -> bool:
|
|
113
|
+
"""Handle exit command."""
|
|
114
|
+
self.console.print("[dim]Returning to the main prompt.[/dim]")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
def _handle_details_command(self, agent_id: str) -> bool:
|
|
118
|
+
"""Handle details command."""
|
|
119
|
+
self._show_details(agent_id)
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
def _handle_other_command(self, raw: str) -> bool:
|
|
123
|
+
"""Handle other commands through the main session."""
|
|
124
|
+
self.session.handle_command(raw, invoked_from_agent=True)
|
|
125
|
+
return not self.session._should_exit
|
|
126
|
+
|
|
127
|
+
def _show_details(self, agent_id: str) -> None:
|
|
128
|
+
try:
|
|
129
|
+
self.session.ctx.invoke(agents_get_command, agent_ref=agent_id)
|
|
130
|
+
self.console.print(
|
|
131
|
+
"[dim]Tip: Continue the conversation in this prompt, or use /help for shortcuts."
|
|
132
|
+
)
|
|
133
|
+
except click.ClickException as exc:
|
|
134
|
+
self.console.print(f"[red]{exc}[/red]")
|
|
135
|
+
|
|
136
|
+
def _run_agent(self, agent_id: str, message: str) -> None:
|
|
137
|
+
if not message:
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
self.session.ctx.invoke(
|
|
142
|
+
agents_run_command, agent_ref=agent_id, input_text=message
|
|
143
|
+
)
|
|
144
|
+
self.session.last_run_input = message
|
|
145
|
+
except click.ClickException as exc:
|
|
146
|
+
self.console.print(f"[red]{exc}[/red]")
|