glaip-sdk 0.0.7__py3-none-any.whl → 0.6.5b6__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 +6 -3
- glaip_sdk/_version.py +12 -5
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1126 -0
- glaip_sdk/branding.py +79 -15
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +699 -0
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +503 -183
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +774 -137
- glaip_sdk/cli/commands/mcps.py +1124 -181
- glaip_sdk/cli/commands/models.py +25 -10
- glaip_sdk/cli/commands/tools.py +144 -92
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +61 -0
- glaip_sdk/cli/config.py +95 -0
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +150 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +846 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +143 -53
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +24 -18
- glaip_sdk/cli/main.py +420 -145
- glaip_sdk/cli/masking.py +136 -0
- glaip_sdk/cli/mcp_validators.py +287 -0
- glaip_sdk/cli/pager.py +266 -0
- glaip_sdk/cli/parsers/__init__.py +7 -0
- glaip_sdk/cli/parsers/json_input.py +177 -0
- glaip_sdk/cli/resolution.py +28 -21
- glaip_sdk/cli/rich_helpers.py +27 -0
- glaip_sdk/cli/slash/__init__.py +15 -0
- glaip_sdk/cli/slash/accounts_controller.py +500 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +282 -0
- glaip_sdk/cli/slash/prompt.py +245 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +1679 -0
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +872 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +31 -0
- glaip_sdk/cli/transcript/cache.py +536 -0
- glaip_sdk/cli/transcript/capture.py +329 -0
- glaip_sdk/cli/transcript/export.py +38 -0
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +77 -0
- glaip_sdk/cli/transcript/viewer.py +372 -0
- glaip_sdk/cli/update_notifier.py +290 -0
- glaip_sdk/cli/utils.py +247 -1238
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +520 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +940 -574
- glaip_sdk/client/base.py +163 -48
- glaip_sdk/client/main.py +35 -12
- glaip_sdk/client/mcps.py +126 -18
- glaip_sdk/client/run_rendering.py +415 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +195 -37
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +15 -5
- glaip_sdk/exceptions.py +16 -9
- glaip_sdk/icons.py +25 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +7 -0
- glaip_sdk/payload_schemas/agent.py +85 -0
- 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 +231 -0
- glaip_sdk/rich_components.py +98 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +597 -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 +158 -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 +177 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +59 -13
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +53 -40
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +58 -26
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +65 -32
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +20 -25
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +85 -43
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +51 -19
- glaip_sdk/utils/rendering/layout/progress.py +202 -0
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +39 -7
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +672 -759
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +75 -22
- 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/manager.py +387 -0
- 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 +422 -0
- glaip_sdk/utils/serialization.py +184 -51
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +21 -30
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/METADATA +58 -12
- glaip_sdk-0.6.5b6.dist-info/RECORD +159 -0
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -250
- glaip_sdk/utils/rendering/renderer/progress.py +0 -118
- glaip_sdk/utils/rendering/steps.py +0 -232
- glaip_sdk/utils/rich_utils.py +0 -29
- glaip_sdk-0.0.7.dist-info/RECORD +0 -55
- {glaip_sdk-0.0.7.dist-info → glaip_sdk-0.6.5b6.dist-info}/entry_points.txt +0 -0
|
@@ -1 +1,115 @@
|
|
|
1
|
-
"""Rendering utilities package (formatting, models, steps, debug).
|
|
1
|
+
"""Rendering utilities package (formatting, models, steps, debug).
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from glaip_sdk.models.agent_runs import RunWithOutput
|
|
13
|
+
from glaip_sdk.utils.rendering.renderer.debug import render_debug_event, render_debug_event_stream
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _parse_event_received_timestamp(event: dict[str, Any]) -> datetime | None:
|
|
17
|
+
"""Parse received_at timestamp from SSE event.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
event: SSE event dictionary
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Parsed datetime or None if not available
|
|
24
|
+
"""
|
|
25
|
+
received_at = event.get("received_at")
|
|
26
|
+
if not received_at:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
if isinstance(received_at, datetime):
|
|
30
|
+
return received_at
|
|
31
|
+
|
|
32
|
+
if isinstance(received_at, str):
|
|
33
|
+
try:
|
|
34
|
+
# Try ISO format first
|
|
35
|
+
return datetime.fromisoformat(received_at.replace("Z", "+00:00"))
|
|
36
|
+
except ValueError:
|
|
37
|
+
try:
|
|
38
|
+
# Try common formats
|
|
39
|
+
return datetime.strptime(received_at, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
40
|
+
except ValueError:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def render_remote_sse_transcript(
|
|
47
|
+
run: RunWithOutput,
|
|
48
|
+
console: Console,
|
|
49
|
+
*,
|
|
50
|
+
show_metadata: bool = True,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Render remote SSE transcript events for a RunWithOutput.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
run: RunWithOutput instance containing events
|
|
56
|
+
console: Rich console to render to
|
|
57
|
+
show_metadata: Whether to show run metadata summary
|
|
58
|
+
"""
|
|
59
|
+
if show_metadata:
|
|
60
|
+
# Render metadata summary
|
|
61
|
+
console.print(f"[bold]Run: {run.id}[/bold]")
|
|
62
|
+
console.print(f"[dim]Agent: {run.agent_id}[/dim]")
|
|
63
|
+
console.print(f"[dim]Status: {run.status}[/dim]")
|
|
64
|
+
console.print(f"[dim]Type: {run.run_type}[/dim]")
|
|
65
|
+
if run.schedule_id:
|
|
66
|
+
console.print(f"[dim]Schedule ID: {run.schedule_id}[/dim]")
|
|
67
|
+
else:
|
|
68
|
+
console.print("[dim]Schedule: —[/dim]")
|
|
69
|
+
console.print(f"[dim]Started: {run.started_at.isoformat()}[/dim]")
|
|
70
|
+
if run.completed_at:
|
|
71
|
+
console.print(f"[dim]Completed: {run.completed_at.isoformat()}[/dim]")
|
|
72
|
+
console.print(f"[dim]Duration: {run.duration_formatted()}[/dim]")
|
|
73
|
+
console.print()
|
|
74
|
+
|
|
75
|
+
# Render events
|
|
76
|
+
if not run.output:
|
|
77
|
+
console.print("[dim]No SSE events available for this run.[/dim]")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
console.print("[bold]SSE Events[/bold]")
|
|
81
|
+
console.print("[dim]────────────────────────────────────────────────────────[/dim]")
|
|
82
|
+
|
|
83
|
+
render_debug_event_stream(
|
|
84
|
+
run.output,
|
|
85
|
+
console,
|
|
86
|
+
resolve_timestamp=_parse_event_received_timestamp,
|
|
87
|
+
)
|
|
88
|
+
console.print()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class RemoteSSETranscriptRenderer:
|
|
92
|
+
"""Renderer for remote SSE transcripts from RunWithOutput."""
|
|
93
|
+
|
|
94
|
+
def __init__(self, console: Console | None = None):
|
|
95
|
+
"""Initialize the renderer.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
console: Rich console instance (creates default if None)
|
|
99
|
+
"""
|
|
100
|
+
self.console = console or Console()
|
|
101
|
+
|
|
102
|
+
def render(self, run: RunWithOutput, *, show_metadata: bool = True) -> None:
|
|
103
|
+
"""Render a remote run transcript.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
run: RunWithOutput instance to render
|
|
107
|
+
show_metadata: Whether to show run metadata summary
|
|
108
|
+
"""
|
|
109
|
+
render_remote_sse_transcript(run, self.console, show_metadata=show_metadata)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = [
|
|
113
|
+
"render_remote_sse_transcript",
|
|
114
|
+
"RemoteSSETranscriptRenderer",
|
|
115
|
+
]
|
|
@@ -6,11 +6,20 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import json
|
|
9
10
|
import re
|
|
10
|
-
import time
|
|
11
11
|
from collections.abc import Callable
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
|
+
from glaip_sdk.icons import (
|
|
15
|
+
ICON_AGENT_STEP,
|
|
16
|
+
ICON_DELEGATE,
|
|
17
|
+
ICON_STATUS_FAILED,
|
|
18
|
+
ICON_STATUS_SUCCESS,
|
|
19
|
+
ICON_STATUS_WARNING,
|
|
20
|
+
ICON_TOOL_STEP,
|
|
21
|
+
)
|
|
22
|
+
|
|
14
23
|
# Constants for argument formatting
|
|
15
24
|
DEFAULT_ARGS_MAX_LEN = 100
|
|
16
25
|
IMPORTANT_PARAMETER_KEYS = [
|
|
@@ -34,9 +43,15 @@ SECRET_VALUE_PATTERNS = [
|
|
|
34
43
|
re.compile(r"eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"), # JWT tokens
|
|
35
44
|
]
|
|
36
45
|
SENSITIVE_PATTERNS = re.compile(
|
|
37
|
-
r"password
|
|
46
|
+
r"(?:password|secret|token|key|api_key)(?:\s*[:=]\s*[^\s,}]+)?",
|
|
38
47
|
re.IGNORECASE,
|
|
39
48
|
)
|
|
49
|
+
SECRET_MASK = "••••••"
|
|
50
|
+
STATUS_GLYPHS = {
|
|
51
|
+
"success": ICON_STATUS_SUCCESS,
|
|
52
|
+
"failed": ICON_STATUS_FAILED,
|
|
53
|
+
"warning": ICON_STATUS_WARNING,
|
|
54
|
+
}
|
|
40
55
|
|
|
41
56
|
|
|
42
57
|
def _truncate_string(s: str, max_len: int) -> str:
|
|
@@ -50,48 +65,82 @@ def mask_secrets_in_string(text: str) -> str:
|
|
|
50
65
|
"""Mask sensitive information in a string."""
|
|
51
66
|
result = text
|
|
52
67
|
for pattern in SECRET_VALUE_PATTERNS:
|
|
53
|
-
result = re.sub(pattern,
|
|
68
|
+
result = re.sub(pattern, SECRET_MASK, result)
|
|
54
69
|
return result
|
|
55
70
|
|
|
56
71
|
|
|
57
72
|
def redact_sensitive(text: str | dict | list) -> str | dict | list:
|
|
58
73
|
"""Redact sensitive information in a string, dict, or list."""
|
|
59
74
|
if isinstance(text, dict):
|
|
60
|
-
|
|
61
|
-
result = {}
|
|
62
|
-
for key, value in text.items():
|
|
63
|
-
# Check if the key itself is sensitive
|
|
64
|
-
key_lower = key.lower()
|
|
65
|
-
if any(
|
|
66
|
-
sensitive in key_lower
|
|
67
|
-
for sensitive in ["password", "secret", "token", "key", "api_key"]
|
|
68
|
-
):
|
|
69
|
-
result[key] = "••••••"
|
|
70
|
-
elif isinstance(value, dict | list) or isinstance(value, str):
|
|
71
|
-
result[key] = redact_sensitive(value)
|
|
72
|
-
else:
|
|
73
|
-
result[key] = value
|
|
74
|
-
return result
|
|
75
|
+
return _redact_dict_values(text)
|
|
75
76
|
elif isinstance(text, list):
|
|
76
|
-
|
|
77
|
-
return [redact_sensitive(item) for item in text]
|
|
77
|
+
return _redact_list_items(text)
|
|
78
78
|
elif isinstance(text, str):
|
|
79
|
-
|
|
80
|
-
result = text
|
|
81
|
-
# First mask secrets
|
|
82
|
-
for pattern in SECRET_VALUE_PATTERNS:
|
|
83
|
-
result = re.sub(pattern, "••••••", result)
|
|
84
|
-
# Then redact sensitive patterns
|
|
85
|
-
result = re.sub(
|
|
86
|
-
SENSITIVE_PATTERNS,
|
|
87
|
-
lambda m: m.group(0).split("=")[0] + "=••••••",
|
|
88
|
-
result,
|
|
89
|
-
)
|
|
90
|
-
return result
|
|
79
|
+
return _redact_string_content(text)
|
|
91
80
|
else:
|
|
92
81
|
return text
|
|
93
82
|
|
|
94
83
|
|
|
84
|
+
def _redact_dict_values(text: dict) -> dict:
|
|
85
|
+
"""Recursively process dictionary values and redact sensitive keys."""
|
|
86
|
+
result = {}
|
|
87
|
+
for key, value in text.items():
|
|
88
|
+
if _is_sensitive_key(key):
|
|
89
|
+
result[key] = SECRET_MASK
|
|
90
|
+
elif _should_recurse_redaction(value):
|
|
91
|
+
result[key] = redact_sensitive(value)
|
|
92
|
+
else:
|
|
93
|
+
result[key] = value
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _redact_list_items(text: list) -> list:
|
|
98
|
+
"""Recursively process list items."""
|
|
99
|
+
return [redact_sensitive(item) for item in text]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _redact_string_content(text: str) -> str:
|
|
103
|
+
"""Process string - first mask secrets, then redact sensitive patterns."""
|
|
104
|
+
result = text
|
|
105
|
+
# First mask secrets
|
|
106
|
+
for pattern in SECRET_VALUE_PATTERNS:
|
|
107
|
+
result = re.sub(pattern, SECRET_MASK, result)
|
|
108
|
+
# Then redact sensitive patterns
|
|
109
|
+
result = re.sub(
|
|
110
|
+
SENSITIVE_PATTERNS,
|
|
111
|
+
lambda m: m.group(0).split("=")[0] + "=" + SECRET_MASK,
|
|
112
|
+
result,
|
|
113
|
+
)
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _is_sensitive_key(key: str) -> bool:
|
|
118
|
+
"""Check if a key contains sensitive information."""
|
|
119
|
+
key_lower = key.lower()
|
|
120
|
+
return any(sensitive in key_lower for sensitive in ["password", "secret", "token", "key", "api_key"])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _should_recurse_redaction(value: Any) -> bool:
|
|
124
|
+
"""Check if a value should be recursively processed."""
|
|
125
|
+
return isinstance(value, (dict, list)) or isinstance(value, str)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def glyph_for_status(icon_key: str | None) -> str | None:
|
|
129
|
+
"""Return glyph representing a step status icon key."""
|
|
130
|
+
if not icon_key:
|
|
131
|
+
return None
|
|
132
|
+
return STATUS_GLYPHS.get(icon_key)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def normalise_display_label(label: str | None) -> str:
|
|
136
|
+
"""Return a user facing label or the Unknown fallback."""
|
|
137
|
+
if not isinstance(label, str):
|
|
138
|
+
text = ""
|
|
139
|
+
else:
|
|
140
|
+
text = label.strip()
|
|
141
|
+
return text or "Unknown step detail"
|
|
142
|
+
|
|
143
|
+
|
|
95
144
|
def pretty_args(args: dict | None, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
|
|
96
145
|
"""Format arguments in a pretty way."""
|
|
97
146
|
if not args:
|
|
@@ -106,11 +155,9 @@ def pretty_args(args: dict | None, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
|
|
|
106
155
|
|
|
107
156
|
# Convert to JSON string and truncate if needed
|
|
108
157
|
try:
|
|
109
|
-
import json
|
|
110
|
-
|
|
111
158
|
args_str = json.dumps(masked_args, ensure_ascii=False, separators=(",", ":"))
|
|
112
159
|
return _truncate_string(args_str, max_len)
|
|
113
|
-
except
|
|
160
|
+
except Exception:
|
|
114
161
|
# Fallback to string representation if JSON serialization fails
|
|
115
162
|
args_str = str(masked_args)
|
|
116
163
|
return _truncate_string(args_str, max_len)
|
|
@@ -140,19 +187,14 @@ def pretty_out(output: any, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
|
|
|
140
187
|
return _truncate_string(output_str, max_len)
|
|
141
188
|
|
|
142
189
|
|
|
143
|
-
def get_spinner_char() -> str:
|
|
144
|
-
frames = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
145
|
-
return frames[int(time.time() * 10) % len(frames)]
|
|
146
|
-
|
|
147
|
-
|
|
148
190
|
def get_step_icon(step_kind: str) -> str:
|
|
149
191
|
"""Get the appropriate icon for a step kind."""
|
|
150
192
|
if step_kind == "tool":
|
|
151
|
-
return
|
|
193
|
+
return ICON_TOOL_STEP
|
|
152
194
|
if step_kind == "delegate":
|
|
153
|
-
return
|
|
195
|
+
return ICON_DELEGATE
|
|
154
196
|
if step_kind == "agent":
|
|
155
|
-
return
|
|
197
|
+
return ICON_AGENT_STEP
|
|
156
198
|
return ""
|
|
157
199
|
|
|
158
200
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Layout utilities exposed for renderer/viewer consumers.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from glaip_sdk.utils.rendering.layout.panels import (
|
|
8
|
+
create_context_panel,
|
|
9
|
+
create_final_panel,
|
|
10
|
+
create_main_panel,
|
|
11
|
+
create_tool_panel,
|
|
12
|
+
)
|
|
13
|
+
from glaip_sdk.utils.rendering.layout.progress import (
|
|
14
|
+
TrailingSpinnerLine,
|
|
15
|
+
build_progress_footer,
|
|
16
|
+
format_elapsed_time,
|
|
17
|
+
format_tool_title,
|
|
18
|
+
format_working_indicator,
|
|
19
|
+
get_spinner,
|
|
20
|
+
get_spinner_char,
|
|
21
|
+
is_delegation_tool,
|
|
22
|
+
)
|
|
23
|
+
from glaip_sdk.utils.rendering.layout.transcript import (
|
|
24
|
+
DEFAULT_TRANSCRIPT_THEME,
|
|
25
|
+
TranscriptGlyphs,
|
|
26
|
+
TranscriptRow,
|
|
27
|
+
TranscriptSnapshot,
|
|
28
|
+
build_final_panel,
|
|
29
|
+
build_transcript_snapshot,
|
|
30
|
+
build_transcript_view,
|
|
31
|
+
extract_query_from_meta,
|
|
32
|
+
format_final_panel_title,
|
|
33
|
+
render_final_panel,
|
|
34
|
+
)
|
|
35
|
+
from glaip_sdk.utils.rendering.layout.summary import render_summary_panels
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# Panels
|
|
39
|
+
"create_context_panel",
|
|
40
|
+
"create_final_panel",
|
|
41
|
+
"create_main_panel",
|
|
42
|
+
"create_tool_panel",
|
|
43
|
+
"render_summary_panels",
|
|
44
|
+
# Progress
|
|
45
|
+
"TrailingSpinnerLine",
|
|
46
|
+
"build_progress_footer",
|
|
47
|
+
"format_elapsed_time",
|
|
48
|
+
"format_tool_title",
|
|
49
|
+
"format_working_indicator",
|
|
50
|
+
"get_spinner",
|
|
51
|
+
"get_spinner_char",
|
|
52
|
+
"is_delegation_tool",
|
|
53
|
+
# Transcript
|
|
54
|
+
"DEFAULT_TRANSCRIPT_THEME",
|
|
55
|
+
"TranscriptGlyphs",
|
|
56
|
+
"TranscriptRow",
|
|
57
|
+
"TranscriptSnapshot",
|
|
58
|
+
"build_final_panel",
|
|
59
|
+
"build_transcript_snapshot",
|
|
60
|
+
"build_transcript_view",
|
|
61
|
+
"extract_query_from_meta",
|
|
62
|
+
"format_final_panel_title",
|
|
63
|
+
"render_final_panel",
|
|
64
|
+
]
|
|
@@ -6,12 +6,26 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
from rich.align import Align
|
|
9
11
|
from rich.markdown import Markdown
|
|
12
|
+
from rich.spinner import Spinner
|
|
10
13
|
from rich.text import Text
|
|
11
14
|
|
|
15
|
+
from glaip_sdk.branding import INFO, PRIMARY, SUCCESS, WARNING
|
|
12
16
|
from glaip_sdk.rich_components import AIPPanel
|
|
13
17
|
|
|
14
18
|
|
|
19
|
+
def _spinner_renderable(message: str = "Processing...") -> Align:
|
|
20
|
+
"""Build a Rich spinner renderable for loading placeholders."""
|
|
21
|
+
spinner = Spinner(
|
|
22
|
+
"dots",
|
|
23
|
+
text=Text(f" {message}", style="dim"),
|
|
24
|
+
style=INFO,
|
|
25
|
+
)
|
|
26
|
+
return Align.left(spinner)
|
|
27
|
+
|
|
28
|
+
|
|
15
29
|
def create_main_panel(content: str, title: str, theme: str = "dark") -> AIPPanel:
|
|
16
30
|
"""Create a main content panel.
|
|
17
31
|
|
|
@@ -27,15 +41,13 @@ def create_main_panel(content: str, title: str, theme: str = "dark") -> AIPPanel
|
|
|
27
41
|
return AIPPanel(
|
|
28
42
|
Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
|
|
29
43
|
title=title,
|
|
30
|
-
border_style=
|
|
44
|
+
border_style=SUCCESS,
|
|
31
45
|
)
|
|
32
46
|
else:
|
|
33
|
-
# Placeholder panel
|
|
34
|
-
placeholder = Text("Processing...", style="dim")
|
|
35
47
|
return AIPPanel(
|
|
36
|
-
|
|
48
|
+
_spinner_renderable(),
|
|
37
49
|
title=title,
|
|
38
|
-
border_style=
|
|
50
|
+
border_style=SUCCESS,
|
|
39
51
|
)
|
|
40
52
|
|
|
41
53
|
|
|
@@ -45,6 +57,8 @@ def create_tool_panel(
|
|
|
45
57
|
status: str = "running",
|
|
46
58
|
theme: str = "dark",
|
|
47
59
|
is_delegation: bool = False,
|
|
60
|
+
*,
|
|
61
|
+
spinner_message: str | None = None,
|
|
48
62
|
) -> AIPPanel:
|
|
49
63
|
"""Create a tool execution panel.
|
|
50
64
|
|
|
@@ -54,19 +68,29 @@ def create_tool_panel(
|
|
|
54
68
|
status: Tool execution status
|
|
55
69
|
theme: Color theme
|
|
56
70
|
is_delegation: Whether this is a delegation tool
|
|
71
|
+
spinner_message: Optional custom message to show alongside the spinner
|
|
57
72
|
|
|
58
73
|
Returns:
|
|
59
74
|
Rich Panel instance
|
|
60
75
|
"""
|
|
61
|
-
mark = "✓" if status == "finished" else "
|
|
62
|
-
border_style =
|
|
76
|
+
mark = "✓" if status == "finished" else ""
|
|
77
|
+
border_style = WARNING if is_delegation else PRIMARY
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
Markdown(
|
|
66
|
-
content
|
|
79
|
+
if content:
|
|
80
|
+
body_renderable = Markdown(
|
|
81
|
+
content,
|
|
67
82
|
code_theme=("monokai" if theme == "dark" else "github"),
|
|
68
|
-
)
|
|
69
|
-
|
|
83
|
+
)
|
|
84
|
+
elif status == "running":
|
|
85
|
+
body_renderable = _spinner_renderable(spinner_message or f"{title} running...")
|
|
86
|
+
else:
|
|
87
|
+
body_renderable = Text("No output yet.", style="dim")
|
|
88
|
+
|
|
89
|
+
title_text = f"{title} {mark}".rstrip()
|
|
90
|
+
|
|
91
|
+
return AIPPanel(
|
|
92
|
+
body_renderable,
|
|
93
|
+
title=title_text,
|
|
70
94
|
border_style=border_style,
|
|
71
95
|
)
|
|
72
96
|
|
|
@@ -90,22 +114,22 @@ def create_context_panel(
|
|
|
90
114
|
Returns:
|
|
91
115
|
Rich Panel instance
|
|
92
116
|
"""
|
|
93
|
-
mark = "✓" if status == "finished" else "
|
|
94
|
-
border_style =
|
|
117
|
+
mark = "✓" if status == "finished" else ""
|
|
118
|
+
border_style = WARNING if is_delegation else INFO
|
|
119
|
+
|
|
120
|
+
title_text = f"{title} {mark}".rstrip()
|
|
95
121
|
|
|
96
122
|
return AIPPanel(
|
|
97
123
|
Markdown(
|
|
98
124
|
content,
|
|
99
125
|
code_theme=("monokai" if theme == "dark" else "github"),
|
|
100
126
|
),
|
|
101
|
-
title=
|
|
127
|
+
title=title_text,
|
|
102
128
|
border_style=border_style,
|
|
103
129
|
)
|
|
104
130
|
|
|
105
131
|
|
|
106
|
-
def create_final_panel(
|
|
107
|
-
content: str, title: str = "Final Result", theme: str = "dark"
|
|
108
|
-
) -> AIPPanel:
|
|
132
|
+
def create_final_panel(content: str, title: str = "Final Result", theme: str = "dark") -> AIPPanel:
|
|
109
133
|
"""Create a final result panel.
|
|
110
134
|
|
|
111
135
|
Args:
|
|
@@ -119,6 +143,14 @@ def create_final_panel(
|
|
|
119
143
|
return AIPPanel(
|
|
120
144
|
Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
|
|
121
145
|
title=title,
|
|
122
|
-
border_style=
|
|
146
|
+
border_style=SUCCESS,
|
|
123
147
|
padding=(0, 1),
|
|
124
148
|
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = [
|
|
152
|
+
"create_main_panel",
|
|
153
|
+
"create_tool_panel",
|
|
154
|
+
"create_context_panel",
|
|
155
|
+
"create_final_panel",
|
|
156
|
+
]
|