glaip-sdk 0.0.0b99__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 +52 -0
- glaip_sdk/_version.py +81 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1227 -0
- glaip_sdk/branding.py +211 -0
- glaip_sdk/cli/__init__.py +9 -0
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +78 -0
- glaip_sdk/cli/auth.py +705 -0
- glaip_sdk/cli/commands/__init__.py +5 -0
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +895 -0
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +67 -0
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +192 -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 +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +355 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +112 -0
- glaip_sdk/cli/main.py +686 -0
- 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 +68 -0
- glaip_sdk/cli/rich_helpers.py +27 -0
- glaip_sdk/cli/slash/__init__.py +15 -0
- glaip_sdk/cli/slash/accounts_controller.py +580 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +285 -0
- glaip_sdk/cli/slash/prompt.py +256 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +1724 -0
- glaip_sdk/cli/slash/tui/__init__.py +34 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +147 -0
- glaip_sdk/cli/slash/tui/context.py +59 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +86 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +123 -0
- glaip_sdk/cli/transcript/__init__.py +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 +374 -0
- glaip_sdk/cli/update_notifier.py +369 -0
- glaip_sdk/cli/validators.py +238 -0
- glaip_sdk/client/__init__.py +12 -0
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +1353 -0
- glaip_sdk/client/base.py +502 -0
- glaip_sdk/client/main.py +253 -0
- glaip_sdk/client/mcps.py +401 -0
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/payloads/agent/requests.py +495 -0
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +747 -0
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +690 -0
- glaip_sdk/client/validators.py +198 -0
- glaip_sdk/config/constants.py +52 -0
- glaip_sdk/exceptions.py +113 -0
- glaip_sdk/hitl/__init__.py +15 -0
- glaip_sdk/hitl/local.py +151 -0
- 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 +107 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +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 +393 -0
- glaip_sdk/rich_components.py +125 -0
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +112 -0
- glaip_sdk/runner/langgraph.py +870 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +466 -0
- glaip_sdk/utils/__init__.py +86 -0
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +194 -0
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +486 -0
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +135 -0
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +61 -0
- glaip_sdk/utils/import_export.py +168 -0
- glaip_sdk/utils/import_resolver.py +530 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -0
- glaip_sdk/utils/rendering/formatting.py +264 -0
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/layout/panels.py +156 -0
- 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 +85 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +55 -0
- glaip_sdk/utils/rendering/renderer/base.py +1082 -0
- glaip_sdk/utils/rendering/renderer/config.py +27 -0
- glaip_sdk/utils/rendering/renderer/console.py +55 -0
- glaip_sdk/utils/rendering/renderer/debug.py +178 -0
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +202 -0
- 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 +195 -0
- glaip_sdk/utils/run_renderer.py +41 -0
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +424 -0
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +264 -0
- glaip_sdk-0.0.0b99.dist-info/METADATA +239 -0
- glaip_sdk-0.0.0b99.dist-info/RECORD +207 -0
- glaip_sdk-0.0.0b99.dist-info/WHEEL +5 -0
- glaip_sdk-0.0.0b99.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.0.0b99.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
]
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Formatting helpers for renderer.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any
|
|
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
|
+
|
|
23
|
+
# Constants for argument formatting
|
|
24
|
+
DEFAULT_ARGS_MAX_LEN = 100
|
|
25
|
+
IMPORTANT_PARAMETER_KEYS = [
|
|
26
|
+
"model",
|
|
27
|
+
"temperature",
|
|
28
|
+
"max_tokens",
|
|
29
|
+
"top_p",
|
|
30
|
+
"frequency_penalty",
|
|
31
|
+
"presence_penalty",
|
|
32
|
+
"query",
|
|
33
|
+
"url",
|
|
34
|
+
]
|
|
35
|
+
SECRET_VALUE_PATTERNS = [
|
|
36
|
+
re.compile(r"sk-[a-zA-Z0-9]{20,}"), # OpenAI API keys (at least 20 chars)
|
|
37
|
+
re.compile(r"ya29\.[a-zA-Z0-9_-]+"), # Google OAuth tokens
|
|
38
|
+
re.compile(r"ghp_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
|
|
39
|
+
re.compile(r"gho_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
|
|
40
|
+
re.compile(r"ghu_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
|
|
41
|
+
re.compile(r"ghs_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
|
|
42
|
+
re.compile(r"ghr_[a-zA-Z0-9]{20,}"), # GitHub tokens (at least 20 chars)
|
|
43
|
+
re.compile(r"eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"), # JWT tokens
|
|
44
|
+
]
|
|
45
|
+
SENSITIVE_PATTERNS = re.compile(
|
|
46
|
+
r"(?:password|secret|token|key|api_key)(?:\s*[:=]\s*[^\s,}]+)?",
|
|
47
|
+
re.IGNORECASE,
|
|
48
|
+
)
|
|
49
|
+
SECRET_MASK = "••••••"
|
|
50
|
+
STATUS_GLYPHS = {
|
|
51
|
+
"success": ICON_STATUS_SUCCESS,
|
|
52
|
+
"failed": ICON_STATUS_FAILED,
|
|
53
|
+
"warning": ICON_STATUS_WARNING,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _truncate_string(s: str, max_len: int) -> str:
|
|
58
|
+
"""Truncate a string to a maximum length."""
|
|
59
|
+
if len(s) <= max_len:
|
|
60
|
+
return s
|
|
61
|
+
return s[: max_len - 3] + "…"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def mask_secrets_in_string(text: str) -> str:
|
|
65
|
+
"""Mask sensitive information in a string."""
|
|
66
|
+
result = text
|
|
67
|
+
for pattern in SECRET_VALUE_PATTERNS:
|
|
68
|
+
result = re.sub(pattern, SECRET_MASK, result)
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def redact_sensitive(text: str | dict | list) -> str | dict | list:
|
|
73
|
+
"""Redact sensitive information in a string, dict, or list."""
|
|
74
|
+
if isinstance(text, dict):
|
|
75
|
+
return _redact_dict_values(text)
|
|
76
|
+
elif isinstance(text, list):
|
|
77
|
+
return _redact_list_items(text)
|
|
78
|
+
elif isinstance(text, str):
|
|
79
|
+
return _redact_string_content(text)
|
|
80
|
+
else:
|
|
81
|
+
return text
|
|
82
|
+
|
|
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
|
+
|
|
144
|
+
def pretty_args(args: dict | None, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
|
|
145
|
+
"""Format arguments in a pretty way."""
|
|
146
|
+
if not args:
|
|
147
|
+
return "{}"
|
|
148
|
+
|
|
149
|
+
# Mask secrets first by recursively processing the structure
|
|
150
|
+
try:
|
|
151
|
+
masked_args = redact_sensitive(args)
|
|
152
|
+
except Exception:
|
|
153
|
+
# Fallback to original args if redact_sensitive fails
|
|
154
|
+
masked_args = args
|
|
155
|
+
|
|
156
|
+
# Convert to JSON string and truncate if needed
|
|
157
|
+
try:
|
|
158
|
+
args_str = json.dumps(masked_args, ensure_ascii=False, separators=(",", ":"))
|
|
159
|
+
return _truncate_string(args_str, max_len)
|
|
160
|
+
except Exception:
|
|
161
|
+
# Fallback to string representation if JSON serialization fails
|
|
162
|
+
args_str = str(masked_args)
|
|
163
|
+
return _truncate_string(args_str, max_len)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def pretty_out(output: any, max_len: int = DEFAULT_ARGS_MAX_LEN) -> str:
|
|
167
|
+
"""Format output in a pretty way."""
|
|
168
|
+
if output is None:
|
|
169
|
+
return "None"
|
|
170
|
+
|
|
171
|
+
if isinstance(output, str):
|
|
172
|
+
# Mask secrets in string output
|
|
173
|
+
masked_output = mask_secrets_in_string(output)
|
|
174
|
+
|
|
175
|
+
# Remove LaTeX commands (common math expressions)
|
|
176
|
+
masked_output = re.sub(r"\\[a-zA-Z]+\{[^}]*\}", "", masked_output)
|
|
177
|
+
masked_output = re.sub(r"\\[a-zA-Z]+", "", masked_output)
|
|
178
|
+
|
|
179
|
+
# Strip leading/trailing whitespace but preserve internal spacing
|
|
180
|
+
masked_output = masked_output.strip()
|
|
181
|
+
# Replace newlines with spaces to preserve formatting
|
|
182
|
+
masked_output = masked_output.replace("\n", " ")
|
|
183
|
+
return _truncate_string(masked_output, max_len)
|
|
184
|
+
|
|
185
|
+
# For other types, convert to string and truncate
|
|
186
|
+
output_str = str(output)
|
|
187
|
+
return _truncate_string(output_str, max_len)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_step_icon(step_kind: str) -> str:
|
|
191
|
+
"""Get the appropriate icon for a step kind."""
|
|
192
|
+
if step_kind == "tool":
|
|
193
|
+
return ICON_TOOL_STEP
|
|
194
|
+
if step_kind == "delegate":
|
|
195
|
+
return ICON_DELEGATE
|
|
196
|
+
if step_kind == "agent":
|
|
197
|
+
return ICON_AGENT_STEP
|
|
198
|
+
return ""
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def is_step_finished(step: Any) -> bool:
|
|
202
|
+
"""Check if a step is finished.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
step: The step object to check
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
True if the step status is "finished", False otherwise
|
|
209
|
+
"""
|
|
210
|
+
return getattr(step, "status", None) == "finished"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def format_main_title(
|
|
214
|
+
header_text: str,
|
|
215
|
+
has_running_steps: bool,
|
|
216
|
+
get_spinner_char: Callable[[], str],
|
|
217
|
+
) -> str:
|
|
218
|
+
"""Generate the main panel title with dynamic status indicators.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
header_text: The header text from the renderer
|
|
222
|
+
has_running_steps: Whether there are running steps
|
|
223
|
+
get_spinner_char: Function to get spinner character
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
A formatted title string showing the agent name and status.
|
|
227
|
+
"""
|
|
228
|
+
# base name
|
|
229
|
+
name = (header_text or "").strip() or "Assistant"
|
|
230
|
+
# strip leading rule emojis if present
|
|
231
|
+
name = name.replace("—", " ").strip()
|
|
232
|
+
# spinner if still working
|
|
233
|
+
mark = "✓" if not has_running_steps else get_spinner_char()
|
|
234
|
+
return f"{name} {mark}"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def print_header_once(
|
|
238
|
+
console: Any,
|
|
239
|
+
text: str,
|
|
240
|
+
last_header: str,
|
|
241
|
+
rules_enabled: bool,
|
|
242
|
+
style: str | None = None,
|
|
243
|
+
) -> str:
|
|
244
|
+
"""Print header text only when it changes to avoid duplicate output.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
console: Rich console instance
|
|
248
|
+
text: The header text to display
|
|
249
|
+
last_header: The last header text that was printed
|
|
250
|
+
rules_enabled: Whether header rules are enabled
|
|
251
|
+
style: Optional Rich style for the header rule
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
The updated last_header value
|
|
255
|
+
"""
|
|
256
|
+
if not rules_enabled:
|
|
257
|
+
return text
|
|
258
|
+
if text and text != last_header:
|
|
259
|
+
try:
|
|
260
|
+
console.rule(text, style=style)
|
|
261
|
+
except Exception:
|
|
262
|
+
console.print(text)
|
|
263
|
+
return text
|
|
264
|
+
return last_header
|
|
@@ -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
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Panel rendering utilities for the renderer package.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from rich.align import Align
|
|
11
|
+
from rich.markdown import Markdown
|
|
12
|
+
from rich.spinner import Spinner
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
|
|
15
|
+
from glaip_sdk.branding import INFO, PRIMARY, SUCCESS, WARNING
|
|
16
|
+
from glaip_sdk.rich_components import AIPPanel
|
|
17
|
+
|
|
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
|
+
|
|
29
|
+
def create_main_panel(content: str, title: str, theme: str = "dark") -> AIPPanel:
|
|
30
|
+
"""Create a main content panel.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
content: The content to display
|
|
34
|
+
title: Panel title
|
|
35
|
+
theme: Color theme ("dark" or "light")
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Rich Panel instance
|
|
39
|
+
"""
|
|
40
|
+
if content.strip():
|
|
41
|
+
return AIPPanel(
|
|
42
|
+
Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
|
|
43
|
+
title=title,
|
|
44
|
+
border_style=SUCCESS,
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
return AIPPanel(
|
|
48
|
+
_spinner_renderable(),
|
|
49
|
+
title=title,
|
|
50
|
+
border_style=SUCCESS,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def create_tool_panel(
|
|
55
|
+
title: str,
|
|
56
|
+
content: str,
|
|
57
|
+
status: str = "running",
|
|
58
|
+
theme: str = "dark",
|
|
59
|
+
is_delegation: bool = False,
|
|
60
|
+
*,
|
|
61
|
+
spinner_message: str | None = None,
|
|
62
|
+
) -> AIPPanel:
|
|
63
|
+
"""Create a tool execution panel.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
title: Tool name/title
|
|
67
|
+
content: Tool output content
|
|
68
|
+
status: Tool execution status
|
|
69
|
+
theme: Color theme
|
|
70
|
+
is_delegation: Whether this is a delegation tool
|
|
71
|
+
spinner_message: Optional custom message to show alongside the spinner
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Rich Panel instance
|
|
75
|
+
"""
|
|
76
|
+
mark = "✓" if status == "finished" else ""
|
|
77
|
+
border_style = WARNING if is_delegation else PRIMARY
|
|
78
|
+
|
|
79
|
+
if content:
|
|
80
|
+
body_renderable = Markdown(
|
|
81
|
+
content,
|
|
82
|
+
code_theme=("monokai" if theme == "dark" else "github"),
|
|
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,
|
|
94
|
+
border_style=border_style,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def create_context_panel(
|
|
99
|
+
title: str,
|
|
100
|
+
content: str,
|
|
101
|
+
status: str = "running",
|
|
102
|
+
theme: str = "dark",
|
|
103
|
+
is_delegation: bool = False,
|
|
104
|
+
) -> AIPPanel:
|
|
105
|
+
"""Create a context/sub-agent panel.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
title: Context title
|
|
109
|
+
content: Context content
|
|
110
|
+
status: Execution status
|
|
111
|
+
theme: Color theme
|
|
112
|
+
is_delegation: Whether this is a delegation context
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Rich Panel instance
|
|
116
|
+
"""
|
|
117
|
+
mark = "✓" if status == "finished" else ""
|
|
118
|
+
border_style = WARNING if is_delegation else INFO
|
|
119
|
+
|
|
120
|
+
title_text = f"{title} {mark}".rstrip()
|
|
121
|
+
|
|
122
|
+
return AIPPanel(
|
|
123
|
+
Markdown(
|
|
124
|
+
content,
|
|
125
|
+
code_theme=("monokai" if theme == "dark" else "github"),
|
|
126
|
+
),
|
|
127
|
+
title=title_text,
|
|
128
|
+
border_style=border_style,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def create_final_panel(content: str, title: str = "Final Result", theme: str = "dark") -> AIPPanel:
|
|
133
|
+
"""Create a final result panel.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
content: Final result content
|
|
137
|
+
title: Panel title
|
|
138
|
+
theme: Color theme
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Rich Panel instance
|
|
142
|
+
"""
|
|
143
|
+
return AIPPanel(
|
|
144
|
+
Markdown(content, code_theme=("monokai" if theme == "dark" else "github")),
|
|
145
|
+
title=title,
|
|
146
|
+
border_style=SUCCESS,
|
|
147
|
+
padding=(0, 1),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = [
|
|
152
|
+
"create_main_panel",
|
|
153
|
+
"create_tool_panel",
|
|
154
|
+
"create_context_panel",
|
|
155
|
+
"create_final_panel",
|
|
156
|
+
]
|