glaip-sdk 0.7.12__py3-none-any.whl → 0.7.14__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 +79 -12
- glaip_sdk/cli/commands/agents/__init__.py +14 -17
- glaip_sdk/cli/commands/agents/_common.py +6 -5
- glaip_sdk/cli/commands/agents/create.py +9 -5
- glaip_sdk/cli/slash/tui/accounts.tcss +12 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +56 -15
- glaip_sdk/cli/slash/tui/context.py +7 -2
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +35 -11
- glaip_sdk/cli/slash/tui/remote_runs_app.py +47 -10
- glaip_sdk/cli/slash/tui/toast.py +14 -0
- glaip_sdk/client/agents.py +247 -15
- glaip_sdk/client/base.py +25 -0
- glaip_sdk/client/main.py +0 -4
- glaip_sdk/client/payloads/agent/requests.py +6 -1
- glaip_sdk/config/constants.py +22 -2
- glaip_sdk/models/__init__.py +30 -1
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +2 -1
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/runner/langgraph.py +105 -14
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/runtime_config.py +3 -2
- {glaip_sdk-0.7.12.dist-info → glaip_sdk-0.7.14.dist-info}/METADATA +1 -1
- {glaip_sdk-0.7.12.dist-info → glaip_sdk-0.7.14.dist-info}/RECORD +29 -25
- {glaip_sdk-0.7.12.dist-info → glaip_sdk-0.7.14.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.7.12.dist-info → glaip_sdk-0.7.14.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.7.12.dist-info → glaip_sdk-0.7.14.dist-info}/top_level.txt +0 -0
glaip_sdk/agents/base.py
CHANGED
|
@@ -54,10 +54,13 @@ from glaip_sdk.utils.resource_refs import is_uuid
|
|
|
54
54
|
|
|
55
55
|
if TYPE_CHECKING:
|
|
56
56
|
from glaip_sdk.client.schedules import AgentScheduleManager
|
|
57
|
+
from glaip_sdk.models import AgentResponse, Model
|
|
57
58
|
from glaip_sdk.guardrails import GuardrailManager
|
|
58
|
-
from glaip_sdk.models import AgentResponse
|
|
59
59
|
from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
|
|
60
60
|
|
|
61
|
+
# Import model validation utility
|
|
62
|
+
from glaip_sdk.models._validation import _validate_model
|
|
63
|
+
|
|
61
64
|
logger = logging.getLogger(__name__)
|
|
62
65
|
|
|
63
66
|
_AGENT_NOT_DEPLOYED_MSG = "Agent must be deployed before running. Call deploy() first."
|
|
@@ -99,11 +102,11 @@ class Agent:
|
|
|
99
102
|
- instruction: Agent instruction text (required)
|
|
100
103
|
- description: Agent description (default: "")
|
|
101
104
|
- tools: List of tools (default: [])
|
|
105
|
+
- model: Optional model override (default: None)
|
|
102
106
|
- agents: List of sub-agents (default: [])
|
|
103
107
|
- mcps: List of MCPs (default: [])
|
|
104
108
|
- timeout: Timeout in seconds (default: 300)
|
|
105
109
|
- metadata: Optional metadata dict (default: None)
|
|
106
|
-
- model: Optional model override (default: None)
|
|
107
110
|
- framework: Agent framework (default: "langchain")
|
|
108
111
|
- version: Agent version (default: "1.0.0")
|
|
109
112
|
- agent_type: Agent type (default: "config")
|
|
@@ -130,7 +133,7 @@ class Agent:
|
|
|
130
133
|
tools: list | None = None,
|
|
131
134
|
agents: list | None = None,
|
|
132
135
|
mcps: list | None = None,
|
|
133
|
-
model: str | None = _UNSET, # type: ignore[assignment]
|
|
136
|
+
model: str | Model | None = _UNSET, # type: ignore[assignment]
|
|
134
137
|
guardrail: GuardrailManager | None = None,
|
|
135
138
|
_client: Any = None,
|
|
136
139
|
**kwargs: Any,
|
|
@@ -148,7 +151,7 @@ class Agent:
|
|
|
148
151
|
tools: List of tools (Tool classes, SDK Tool objects, or strings).
|
|
149
152
|
agents: List of sub-agents (Agent classes, instances, or strings).
|
|
150
153
|
mcps: List of MCPs.
|
|
151
|
-
model: Model identifier.
|
|
154
|
+
model: Model identifier or Model configuration object.
|
|
152
155
|
guardrail: The guardrail manager for content safety.
|
|
153
156
|
_client: Internal client reference (set automatically).
|
|
154
157
|
|
|
@@ -176,7 +179,7 @@ class Agent:
|
|
|
176
179
|
self._tools = tools
|
|
177
180
|
self._agents = agents
|
|
178
181
|
self._mcps = mcps
|
|
179
|
-
self._model = model
|
|
182
|
+
self._model = self._validate_and_set_model(model)
|
|
180
183
|
self._guardrail = guardrail
|
|
181
184
|
self._language_model_id: str | None = None
|
|
182
185
|
# Extract parameters from kwargs with _UNSET defaults
|
|
@@ -185,6 +188,18 @@ class Agent:
|
|
|
185
188
|
self._framework = kwargs.pop("framework", Agent._UNSET) # type: ignore[assignment]
|
|
186
189
|
self._version = kwargs.pop("version", Agent._UNSET) # type: ignore[assignment]
|
|
187
190
|
self._agent_type = kwargs.pop("agent_type", Agent._UNSET) # type: ignore[assignment]
|
|
191
|
+
|
|
192
|
+
# Handle 'type' as a legacy alias for 'agent_type'
|
|
193
|
+
legacy_type = kwargs.pop("type", Agent._UNSET)
|
|
194
|
+
if legacy_type is not Agent._UNSET:
|
|
195
|
+
warnings.warn(
|
|
196
|
+
"The 'type' parameter is deprecated and will be removed in a future version. Use 'agent_type' instead.",
|
|
197
|
+
DeprecationWarning,
|
|
198
|
+
stacklevel=2,
|
|
199
|
+
)
|
|
200
|
+
if self._agent_type is Agent._UNSET:
|
|
201
|
+
self._agent_type = legacy_type
|
|
202
|
+
|
|
188
203
|
self._agent_config = kwargs.pop("agent_config", Agent._UNSET) # type: ignore[assignment]
|
|
189
204
|
self._tool_configs = kwargs.pop("tool_configs", Agent._UNSET) # type: ignore[assignment]
|
|
190
205
|
self._mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET) # type: ignore[assignment]
|
|
@@ -198,6 +213,30 @@ class Agent:
|
|
|
198
213
|
stacklevel=2,
|
|
199
214
|
)
|
|
200
215
|
|
|
216
|
+
def _validate_and_set_model(self, model: str | Any) -> str | Any:
|
|
217
|
+
"""Validate and normalize model parameter.
|
|
218
|
+
|
|
219
|
+
Supports both string model identifiers and Model objects:
|
|
220
|
+
- String: Simple model identifier (e.g., "openai/gpt-4o" or OpenAI.GPT_4O)
|
|
221
|
+
- Model: Model object with credentials/hyperparameters for local execution
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
model: Model identifier (string) or Model object.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Validated model (string or Model object).
|
|
228
|
+
"""
|
|
229
|
+
if model is None or model is Agent._UNSET:
|
|
230
|
+
return model
|
|
231
|
+
|
|
232
|
+
from glaip_sdk.models import Model # noqa: PLC0415
|
|
233
|
+
|
|
234
|
+
if isinstance(model, str):
|
|
235
|
+
return _validate_model(model)
|
|
236
|
+
elif isinstance(model, Model):
|
|
237
|
+
return model
|
|
238
|
+
return model
|
|
239
|
+
|
|
201
240
|
# ─────────────────────────────────────────────────────────────────
|
|
202
241
|
# Properties (override in subclasses OR pass to __init__)
|
|
203
242
|
# ─────────────────────────────────────────────────────────────────
|
|
@@ -342,11 +381,11 @@ class Agent:
|
|
|
342
381
|
return None
|
|
343
382
|
|
|
344
383
|
@property
|
|
345
|
-
def model(self) -> str | None:
|
|
384
|
+
def model(self) -> str | Model | None:
|
|
346
385
|
"""Optional model override.
|
|
347
386
|
|
|
348
387
|
Returns:
|
|
349
|
-
Model identifier string or None to use default.
|
|
388
|
+
Model identifier string, Model object, or None to use default.
|
|
350
389
|
"""
|
|
351
390
|
if self._model is not self._UNSET:
|
|
352
391
|
return self._model
|
|
@@ -549,9 +588,14 @@ class Agent:
|
|
|
549
588
|
"framework": self.framework,
|
|
550
589
|
"version": self.version,
|
|
551
590
|
"agent_type": self.agent_type,
|
|
552
|
-
"model": self.model,
|
|
553
591
|
}
|
|
554
592
|
|
|
593
|
+
if self.model:
|
|
594
|
+
if isinstance(self.model, str):
|
|
595
|
+
config["model"] = self.model
|
|
596
|
+
else:
|
|
597
|
+
config["model"] = self.model.id
|
|
598
|
+
|
|
555
599
|
# Handle metadata (default to empty dict if None)
|
|
556
600
|
config["metadata"] = self.metadata or {}
|
|
557
601
|
|
|
@@ -848,21 +892,42 @@ class Agent:
|
|
|
848
892
|
"""Return a dict representation of the Agent.
|
|
849
893
|
|
|
850
894
|
Provides Pydantic-style serialization for backward compatibility.
|
|
895
|
+
This implementation avoids triggering external tool resolution to ensure
|
|
896
|
+
it remains robust even when the environment is not fully configured.
|
|
851
897
|
|
|
852
898
|
Args:
|
|
853
899
|
exclude_none: If True, exclude None values from the output.
|
|
854
900
|
|
|
855
901
|
Returns:
|
|
856
|
-
Dictionary containing Agent attributes.
|
|
902
|
+
Dictionary containing Agent attributes. Note: Mutable fields (dicts, lists)
|
|
903
|
+
are returned as references. Modify with caution or make a deep copy if needed.
|
|
857
904
|
"""
|
|
858
|
-
|
|
905
|
+
# Map convenience timeout to agent_config if not already present
|
|
906
|
+
agent_config = self.agent_config if self.agent_config is not self._UNSET else {}
|
|
907
|
+
agent_config = dict(agent_config) if agent_config else {}
|
|
908
|
+
|
|
909
|
+
if self.timeout and "execution_timeout" not in agent_config:
|
|
910
|
+
agent_config["execution_timeout"] = self.timeout
|
|
911
|
+
|
|
912
|
+
# Handle guardrail serialization without full config build
|
|
913
|
+
if self.guardrail:
|
|
914
|
+
try:
|
|
915
|
+
from glaip_sdk.guardrails.serializer import ( # noqa: PLC0415
|
|
916
|
+
serialize_guardrail_manager,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
agent_config["guardrails"] = serialize_guardrail_manager(self.guardrail)
|
|
920
|
+
except ImportError: # pragma: no cover
|
|
921
|
+
# Serializer not available (optional dependency); skip guardrail data
|
|
922
|
+
pass
|
|
859
923
|
|
|
860
924
|
data = {
|
|
861
925
|
"id": self._id,
|
|
862
926
|
"name": self.name,
|
|
863
927
|
"instruction": self.instruction,
|
|
864
928
|
"description": self.description,
|
|
865
|
-
"
|
|
929
|
+
"agent_type": self.agent_type,
|
|
930
|
+
"type": self.agent_type, # Legacy key for backward compatibility
|
|
866
931
|
"framework": self.framework,
|
|
867
932
|
"version": self.version,
|
|
868
933
|
"tools": self.tools,
|
|
@@ -870,7 +935,8 @@ class Agent:
|
|
|
870
935
|
"mcps": self.mcps,
|
|
871
936
|
"timeout": self.timeout,
|
|
872
937
|
"metadata": self.metadata,
|
|
873
|
-
"
|
|
938
|
+
"model": self.model,
|
|
939
|
+
"agent_config": agent_config,
|
|
874
940
|
"tool_configs": self.tool_configs,
|
|
875
941
|
"mcp_configs": self.mcp_configs,
|
|
876
942
|
"a2a_profile": self.a2a_profile,
|
|
@@ -878,6 +944,7 @@ class Agent:
|
|
|
878
944
|
"created_at": self._created_at,
|
|
879
945
|
"updated_at": self._updated_at,
|
|
880
946
|
}
|
|
947
|
+
|
|
881
948
|
if exclude_none:
|
|
882
949
|
return {k: v for k, v in data.items() if v is not None}
|
|
883
950
|
return data
|
|
@@ -11,37 +11,30 @@ Authors:
|
|
|
11
11
|
# Import from submodules
|
|
12
12
|
from glaip_sdk.cli.commands.agents._common import ( # noqa: E402
|
|
13
13
|
AGENT_NOT_FOUND_ERROR,
|
|
14
|
-
_get_agent_for_update,
|
|
15
|
-
_resolve_agent,
|
|
16
|
-
agents_group,
|
|
17
14
|
_coerce_mapping_candidate,
|
|
18
15
|
_display_agent_details,
|
|
19
16
|
_emit_verbose_guidance,
|
|
20
17
|
_fetch_full_agent_details,
|
|
18
|
+
_get_agent_for_update,
|
|
21
19
|
_get_agent_model_name,
|
|
22
20
|
_get_language_model_display_name,
|
|
23
21
|
_model_from_config,
|
|
24
22
|
_prepare_agent_output,
|
|
23
|
+
_resolve_agent,
|
|
25
24
|
_resolve_resources_by_name,
|
|
25
|
+
agents_group,
|
|
26
26
|
console,
|
|
27
27
|
)
|
|
28
28
|
from glaip_sdk.cli.commands.agents.create import create # noqa: E402
|
|
29
29
|
from glaip_sdk.cli.commands.agents.delete import delete # noqa: E402
|
|
30
30
|
from glaip_sdk.cli.commands.agents.get import get # noqa: E402
|
|
31
31
|
from glaip_sdk.cli.commands.agents.list import list_agents # noqa: E402
|
|
32
|
-
from glaip_sdk.cli.commands.agents.run import
|
|
32
|
+
from glaip_sdk.cli.commands.agents.run import _maybe_attach_transcript_toggle, run # noqa: E402
|
|
33
33
|
from glaip_sdk.cli.commands.agents.sync_langflow import sync_langflow # noqa: E402
|
|
34
34
|
from glaip_sdk.cli.commands.agents.update import update # noqa: E402
|
|
35
35
|
|
|
36
36
|
# Import core functions for test compatibility
|
|
37
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
38
|
|
|
46
39
|
# Import core output functions for test compatibility
|
|
47
40
|
from glaip_sdk.cli.core.output import ( # noqa: E402
|
|
@@ -52,11 +45,20 @@ from glaip_sdk.cli.core.output import ( # noqa: E402
|
|
|
52
45
|
# Import rendering functions for test compatibility
|
|
53
46
|
from glaip_sdk.cli.core.rendering import ( # noqa: E402
|
|
54
47
|
build_renderer,
|
|
48
|
+
with_client_and_spinner, # noqa: E402
|
|
55
49
|
)
|
|
56
50
|
|
|
57
51
|
# Import display functions for test compatibility
|
|
58
|
-
|
|
52
|
+
# Import display functions for test compatibility
|
|
53
|
+
from glaip_sdk.cli.display import ( # noqa: E402 # noqa: E402
|
|
59
54
|
display_agent_run_suggestions,
|
|
55
|
+
handle_json_output,
|
|
56
|
+
handle_rich_output,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Import IO functions for test compatibility
|
|
60
|
+
from glaip_sdk.cli.io import ( # noqa: E402
|
|
61
|
+
fetch_raw_resource_details,
|
|
60
62
|
)
|
|
61
63
|
|
|
62
64
|
# Import rich helpers for test compatibility
|
|
@@ -70,11 +72,6 @@ from glaip_sdk.cli.transcript import ( # noqa: E402
|
|
|
70
72
|
store_transcript_for_session,
|
|
71
73
|
)
|
|
72
74
|
|
|
73
|
-
# Import IO functions for test compatibility
|
|
74
|
-
from glaip_sdk.cli.io import ( # noqa: E402
|
|
75
|
-
fetch_raw_resource_details,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
75
|
# Import utils for test compatibility
|
|
79
76
|
from glaip_sdk.utils import ( # noqa: E402
|
|
80
77
|
is_uuid,
|
|
@@ -19,8 +19,11 @@ from glaip_sdk.branding import (
|
|
|
19
19
|
WARNING_STYLE,
|
|
20
20
|
)
|
|
21
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
22
|
from glaip_sdk.cli.context import get_ctx_value
|
|
23
|
+
from glaip_sdk.cli.core.output import (
|
|
24
|
+
output_result,
|
|
25
|
+
)
|
|
26
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
24
27
|
from glaip_sdk.cli.display import (
|
|
25
28
|
build_resource_result_data,
|
|
26
29
|
handle_json_output,
|
|
@@ -31,10 +34,8 @@ from glaip_sdk.cli.hints import in_slash_mode
|
|
|
31
34
|
from glaip_sdk.cli.io import fetch_raw_resource_details
|
|
32
35
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
33
36
|
from glaip_sdk.cli.rich_helpers import markup_text
|
|
34
|
-
from glaip_sdk.
|
|
35
|
-
|
|
36
|
-
)
|
|
37
|
-
from glaip_sdk.cli.core.rendering import spinner_context
|
|
37
|
+
from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS
|
|
38
|
+
from glaip_sdk.models.constants import DEFAULT_MODEL
|
|
38
39
|
from glaip_sdk.icons import ICON_AGENT
|
|
39
40
|
from glaip_sdk.utils import format_datetime, is_uuid
|
|
40
41
|
|
|
@@ -11,19 +11,19 @@ from typing import Any
|
|
|
11
11
|
import click
|
|
12
12
|
|
|
13
13
|
from glaip_sdk.cli.context import output_flags
|
|
14
|
+
from glaip_sdk.cli.core.context import get_client
|
|
14
15
|
from glaip_sdk.cli.display import (
|
|
15
16
|
display_agent_run_suggestions,
|
|
16
17
|
display_creation_success,
|
|
17
18
|
handle_json_output,
|
|
18
19
|
handle_rich_output,
|
|
19
20
|
)
|
|
20
|
-
from glaip_sdk.cli.core.context import get_client
|
|
21
|
-
from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
|
|
22
21
|
from glaip_sdk.cli.validators import (
|
|
23
22
|
validate_agent_instruction_cli as validate_agent_instruction,
|
|
24
|
-
validate_agent_name_cli as validate_agent_name,
|
|
25
|
-
validate_timeout_cli as validate_timeout,
|
|
26
23
|
)
|
|
24
|
+
from glaip_sdk.cli.validators import validate_agent_name_cli as validate_agent_name
|
|
25
|
+
from glaip_sdk.cli.validators import validate_timeout_cli as validate_timeout
|
|
26
|
+
from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
|
|
27
27
|
from glaip_sdk.utils.validation import coerce_timeout
|
|
28
28
|
|
|
29
29
|
from ._common import (
|
|
@@ -67,7 +67,11 @@ def _handle_creation_exception(ctx: Any, e: Exception) -> None:
|
|
|
67
67
|
@click.option("--instruction", help="Agent instruction (prompt)")
|
|
68
68
|
@click.option(
|
|
69
69
|
"--model",
|
|
70
|
-
help=
|
|
70
|
+
help=(
|
|
71
|
+
"Language model in 'provider/model' format "
|
|
72
|
+
"(e.g., openai/gpt-4o-mini, bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0). "
|
|
73
|
+
"Use 'aip models list' to see available models."
|
|
74
|
+
),
|
|
71
75
|
)
|
|
72
76
|
@click.option("--tools", multiple=True, help="Tool names or IDs to attach")
|
|
73
77
|
@click.option("--agents", multiple=True, help="Sub-agent names or IDs to attach")
|
|
@@ -53,6 +53,10 @@ Screen {
|
|
|
53
53
|
margin-left: 1;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
Button:hover {
|
|
57
|
+
background: $surface-lighten-1;
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
#accounts-table {
|
|
57
61
|
padding: 0 1 0 1;
|
|
58
62
|
margin: 0 0 0 0;
|
|
@@ -60,6 +64,14 @@ Screen {
|
|
|
60
64
|
border: tall $primary;
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
#accounts-table > .datatable--row:hover {
|
|
68
|
+
background: $surface-lighten-1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.sidebar-block:hover {
|
|
72
|
+
background: $surface-lighten-1;
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
#status-bar {
|
|
64
76
|
height: 3;
|
|
65
77
|
padding: 0 1;
|
|
@@ -37,11 +37,19 @@ from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities
|
|
|
37
37
|
from glaip_sdk.cli.slash.tui.theme.catalog import _BUILTIN_THEMES
|
|
38
38
|
|
|
39
39
|
try: # pragma: no cover - optional dependency
|
|
40
|
-
from glaip_sdk.cli.slash.tui.toast import
|
|
40
|
+
from glaip_sdk.cli.slash.tui.toast import (
|
|
41
|
+
ClipboardToastMixin,
|
|
42
|
+
Toast,
|
|
43
|
+
ToastBus,
|
|
44
|
+
ToastContainer,
|
|
45
|
+
ToastHandlerMixin,
|
|
46
|
+
ToastVariant,
|
|
47
|
+
)
|
|
41
48
|
except Exception: # pragma: no cover - optional dependency
|
|
42
49
|
ClipboardToastMixin = object # type: ignore[assignment, misc]
|
|
43
50
|
Toast = None # type: ignore[assignment]
|
|
44
51
|
ToastBus = None # type: ignore[assignment]
|
|
52
|
+
ToastContainer = None # type: ignore[assignment]
|
|
45
53
|
ToastHandlerMixin = object # type: ignore[assignment, misc]
|
|
46
54
|
ToastVariant = None # type: ignore[assignment]
|
|
47
55
|
from glaip_sdk.cli.validators import validate_api_key
|
|
@@ -51,7 +59,8 @@ try: # pragma: no cover - optional dependency
|
|
|
51
59
|
from textual import events
|
|
52
60
|
from textual.app import App, ComposeResult
|
|
53
61
|
from textual.binding import Binding
|
|
54
|
-
from textual.containers import
|
|
62
|
+
from textual.containers import Horizontal, Vertical
|
|
63
|
+
from textual.coordinate import Coordinate
|
|
55
64
|
from textual.screen import ModalScreen
|
|
56
65
|
from textual.suggester import SuggestFromList
|
|
57
66
|
from textual.widgets import Button, Checkbox, DataTable, Footer, Header, Input, LoadingIndicator, Static
|
|
@@ -60,9 +69,9 @@ except Exception: # pragma: no cover - optional dependency
|
|
|
60
69
|
App = None # type: ignore[assignment]
|
|
61
70
|
ComposeResult = None # type: ignore[assignment]
|
|
62
71
|
Binding = None # type: ignore[assignment]
|
|
63
|
-
Container = None # type: ignore[assignment]
|
|
64
72
|
Horizontal = None # type: ignore[assignment]
|
|
65
73
|
Vertical = None # type: ignore[assignment]
|
|
74
|
+
Coordinate = None # type: ignore[assignment]
|
|
66
75
|
Button = None # type: ignore[assignment]
|
|
67
76
|
Checkbox = None # type: ignore[assignment]
|
|
68
77
|
DataTable = None # type: ignore[assignment]
|
|
@@ -527,7 +536,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
527
536
|
|
|
528
537
|
def compose(self) -> ComposeResult: # type: ignore[return]
|
|
529
538
|
"""Compose the Harlequin layout with account list and detail panes."""
|
|
530
|
-
if not TEXTUAL_SUPPORTED or Horizontal is None or Vertical is None or
|
|
539
|
+
if not TEXTUAL_SUPPORTED or Horizontal is None or Vertical is None or Static is None:
|
|
531
540
|
return # type: ignore[return-value]
|
|
532
541
|
|
|
533
542
|
# Main container with horizontal split (25/75)
|
|
@@ -561,8 +570,8 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
561
570
|
)
|
|
562
571
|
|
|
563
572
|
# Toast container for notifications
|
|
564
|
-
if Toast is not None:
|
|
565
|
-
yield
|
|
573
|
+
if Toast is not None and ToastContainer is not None:
|
|
574
|
+
yield ToastContainer(Toast(), id="toast-container")
|
|
566
575
|
|
|
567
576
|
def on_mount(self) -> None:
|
|
568
577
|
"""Configure the screen after mount."""
|
|
@@ -833,6 +842,21 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
833
842
|
if not self._is_switching:
|
|
834
843
|
self.action_switch_account()
|
|
835
844
|
|
|
845
|
+
def on_data_table_cell_selected(self, event: DataTable.CellSelected) -> None: # type: ignore[override]
|
|
846
|
+
"""Handle mouse click selection by triggering switch."""
|
|
847
|
+
if not TEXTUAL_SUPPORTED:
|
|
848
|
+
return
|
|
849
|
+
table = self.query_one(HARLEQUIN_ACCOUNTS_LIST_ID, DataTable)
|
|
850
|
+
try:
|
|
851
|
+
table.cursor_coordinate = (event.coordinate.row, 0)
|
|
852
|
+
except Exception:
|
|
853
|
+
return
|
|
854
|
+
filtered = self._filtered_rows()
|
|
855
|
+
if event.coordinate.row < len(filtered):
|
|
856
|
+
self._update_selected_account(filtered[event.coordinate.row])
|
|
857
|
+
if not self._is_switching:
|
|
858
|
+
self.action_switch_account()
|
|
859
|
+
|
|
836
860
|
def on_data_table_cursor_row_changed(self, event: DataTable.CursorRowChanged) -> None: # type: ignore[override]
|
|
837
861
|
"""Handle cursor movement in the accounts list."""
|
|
838
862
|
if not TEXTUAL_SUPPORTED:
|
|
@@ -875,6 +899,8 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
875
899
|
"""Open edit account modal."""
|
|
876
900
|
if self._check_env_lock():
|
|
877
901
|
return
|
|
902
|
+
# Get account from cursor position if not explicitly selected
|
|
903
|
+
self._ensure_account_selected_from_cursor()
|
|
878
904
|
name = self._get_selected_name()
|
|
879
905
|
if not name:
|
|
880
906
|
self._set_status("Select an account to edit.", "yellow")
|
|
@@ -897,6 +923,8 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
897
923
|
"""Open delete confirmation modal."""
|
|
898
924
|
if self._check_env_lock():
|
|
899
925
|
return
|
|
926
|
+
# Get account from cursor position if not explicitly selected
|
|
927
|
+
self._ensure_account_selected_from_cursor()
|
|
900
928
|
name = self._get_selected_name()
|
|
901
929
|
if not name:
|
|
902
930
|
self._set_status("Select an account to delete.", "yellow")
|
|
@@ -987,7 +1015,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
|
|
|
987
1015
|
if output is None:
|
|
988
1016
|
return None
|
|
989
1017
|
|
|
990
|
-
def _write(sequence: str, _output=output) -> None:
|
|
1018
|
+
def _write(sequence: str, _output: Any = output) -> None:
|
|
991
1019
|
_output.write(sequence)
|
|
992
1020
|
_output.flush()
|
|
993
1021
|
|
|
@@ -1310,20 +1338,33 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1310
1338
|
self._queue_switch(name)
|
|
1311
1339
|
|
|
1312
1340
|
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None: # type: ignore[override]
|
|
1313
|
-
"""Handle
|
|
1341
|
+
"""Handle row selection by triggering switch."""
|
|
1342
|
+
self._handle_table_click(self._event_row(event))
|
|
1314
1343
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1344
|
+
def on_data_table_cell_selected(self, event: DataTable.CellSelected) -> None: # type: ignore[override]
|
|
1345
|
+
"""Handle mouse click selection by triggering switch."""
|
|
1346
|
+
self._handle_table_click(self._event_row(event))
|
|
1347
|
+
|
|
1348
|
+
def _event_row(self, event: object) -> int | None:
|
|
1349
|
+
"""Extract the row index from a DataTable event."""
|
|
1350
|
+
row = getattr(event, "cursor_row", None)
|
|
1351
|
+
if row is not None:
|
|
1352
|
+
return int(row)
|
|
1353
|
+
coordinate = getattr(event, "coordinate", None)
|
|
1354
|
+
return getattr(coordinate, "row", None) if coordinate is not None else None
|
|
1355
|
+
|
|
1356
|
+
def _handle_table_click(self, row: int | None) -> None:
|
|
1357
|
+
"""Move the cursor to a clicked row and trigger the switch action."""
|
|
1358
|
+
if row is None:
|
|
1359
|
+
return
|
|
1319
1360
|
try:
|
|
1320
1361
|
table = self.query_one(ACCOUNTS_TABLE_ID, DataTable)
|
|
1321
1362
|
except Exception:
|
|
1322
|
-
# Harlequin screen is active, let it handle the
|
|
1363
|
+
# Harlequin screen is active, let it handle the action
|
|
1323
1364
|
return
|
|
1324
1365
|
try:
|
|
1325
1366
|
# Move cursor to clicked row then switch
|
|
1326
|
-
table.cursor_coordinate = (
|
|
1367
|
+
table.cursor_coordinate = Coordinate(row, 0)
|
|
1327
1368
|
except Exception:
|
|
1328
1369
|
return
|
|
1329
1370
|
self.action_switch_row()
|
|
@@ -1654,7 +1695,7 @@ class AccountsTextualApp( # pragma: no cover - interactive
|
|
|
1654
1695
|
if output is None:
|
|
1655
1696
|
return None
|
|
1656
1697
|
|
|
1657
|
-
def _write(sequence: str, _output=output) -> None:
|
|
1698
|
+
def _write(sequence: str, _output: Any = output) -> None:
|
|
1658
1699
|
_output.write(sequence)
|
|
1659
1700
|
_output.flush()
|
|
1660
1701
|
|
|
@@ -61,13 +61,18 @@ class TUIContext:
|
|
|
61
61
|
store = get_account_store()
|
|
62
62
|
settings = load_tui_settings(store=store)
|
|
63
63
|
|
|
64
|
-
# Handle env var override: normalize empty strings and "default" to None
|
|
65
|
-
# Empty string from os.getenv() is falsy, so strip() result becomes None in the or expression
|
|
66
64
|
env_theme = os.getenv("AIP_TUI_THEME")
|
|
67
65
|
env_theme = env_theme.strip() if env_theme else None
|
|
68
66
|
if env_theme and env_theme.lower() == "default":
|
|
69
67
|
env_theme = None
|
|
70
68
|
|
|
69
|
+
env_mouse = os.getenv("AIP_TUI_MOUSE_CAPTURE")
|
|
70
|
+
mouse_capture = settings.mouse_capture
|
|
71
|
+
if env_mouse is not None:
|
|
72
|
+
mouse_capture = env_mouse.lower() == "true"
|
|
73
|
+
|
|
74
|
+
terminal.mouse = mouse_capture
|
|
75
|
+
|
|
71
76
|
theme_name = env_theme or settings.theme_name
|
|
72
77
|
theme = ThemeManager(
|
|
73
78
|
terminal,
|
|
@@ -19,8 +19,8 @@ from __future__ import annotations
|
|
|
19
19
|
from typing import TYPE_CHECKING, Any
|
|
20
20
|
|
|
21
21
|
try: # pragma: no cover - optional dependency
|
|
22
|
-
from textual.containers import Container, Horizontal, Vertical
|
|
23
22
|
from textual.screen import Screen
|
|
23
|
+
from textual.widget import Widget
|
|
24
24
|
except Exception: # pragma: no cover - optional dependency
|
|
25
25
|
|
|
26
26
|
class Screen: # type: ignore[no-redef]
|
|
@@ -30,23 +30,47 @@ except Exception: # pragma: no cover - optional dependency
|
|
|
30
30
|
"""Return the class for typing subscripts."""
|
|
31
31
|
return cls
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
Vertical = None # type: ignore[assignment]
|
|
35
|
-
Container = None # type: ignore[assignment]
|
|
33
|
+
Widget = None # type: ignore[assignment]
|
|
36
34
|
|
|
37
35
|
if TYPE_CHECKING:
|
|
38
36
|
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
39
37
|
|
|
40
38
|
try: # pragma: no cover - optional dependency
|
|
41
|
-
from glaip_sdk.cli.slash.tui.toast import Toast
|
|
39
|
+
from glaip_sdk.cli.slash.tui.toast import Toast, ToastContainer
|
|
42
40
|
except Exception: # pragma: no cover - optional dependency
|
|
43
41
|
Toast = None # type: ignore[assignment, misc]
|
|
42
|
+
ToastContainer = None # type: ignore[assignment, misc]
|
|
44
43
|
|
|
45
44
|
# GDP Labs Brand Palette
|
|
46
45
|
PRIMARY_BLUE = "#005CB8"
|
|
47
46
|
BLACK_BACKGROUND = "#000000"
|
|
48
47
|
|
|
49
48
|
|
|
49
|
+
if Widget is not None:
|
|
50
|
+
|
|
51
|
+
class HarlequinContainer(Widget):
|
|
52
|
+
"""Base container for the Harlequin layout."""
|
|
53
|
+
|
|
54
|
+
DEFAULT_CSS = """
|
|
55
|
+
HarlequinContainer {
|
|
56
|
+
layout: horizontal;
|
|
57
|
+
}
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
class HarlequinPane(Widget):
|
|
61
|
+
"""Pane container for Harlequin layout sections."""
|
|
62
|
+
|
|
63
|
+
DEFAULT_CSS = """
|
|
64
|
+
HarlequinPane {
|
|
65
|
+
layout: vertical;
|
|
66
|
+
}
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
else:
|
|
70
|
+
HarlequinContainer = None # type: ignore[assignment, misc]
|
|
71
|
+
HarlequinPane = None # type: ignore[assignment, misc]
|
|
72
|
+
|
|
73
|
+
|
|
50
74
|
class HarlequinScreen(Screen[None]): # type: ignore[misc]
|
|
51
75
|
"""Base class for Harlequin-style multi-pane screens.
|
|
52
76
|
|
|
@@ -136,19 +160,19 @@ class HarlequinScreen(Screen[None]): # type: ignore[misc]
|
|
|
136
160
|
Returns:
|
|
137
161
|
ComposeResult yielding the base layout containers.
|
|
138
162
|
"""
|
|
139
|
-
if
|
|
163
|
+
if HarlequinContainer is None or HarlequinPane is None:
|
|
140
164
|
return
|
|
141
165
|
|
|
142
166
|
# Main container with horizontal split (25/75)
|
|
143
|
-
yield
|
|
144
|
-
|
|
145
|
-
|
|
167
|
+
yield HarlequinContainer(
|
|
168
|
+
HarlequinPane(id="left-pane"),
|
|
169
|
+
HarlequinPane(id="right-pane"),
|
|
146
170
|
id="harlequin-container",
|
|
147
171
|
)
|
|
148
172
|
|
|
149
173
|
# Toast container for notifications
|
|
150
|
-
if Toast is not None and
|
|
151
|
-
yield
|
|
174
|
+
if Toast is not None and ToastContainer is not None:
|
|
175
|
+
yield ToastContainer(Toast(), id="toast-container")
|
|
152
176
|
|
|
153
177
|
@property
|
|
154
178
|
def ctx(self) -> TUIContext | None:
|