glaip-sdk 0.1.3__py3-none-any.whl → 0.6.10__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.
Files changed (141) hide show
  1. glaip_sdk/__init__.py +5 -2
  2. glaip_sdk/_version.py +9 -0
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1191 -0
  5. glaip_sdk/branding.py +13 -0
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/auth.py +254 -15
  8. glaip_sdk/cli/commands/__init__.py +2 -2
  9. glaip_sdk/cli/commands/accounts.py +746 -0
  10. glaip_sdk/cli/commands/agents.py +213 -73
  11. glaip_sdk/cli/commands/common_config.py +101 -0
  12. glaip_sdk/cli/commands/configure.py +729 -113
  13. glaip_sdk/cli/commands/mcps.py +241 -72
  14. glaip_sdk/cli/commands/models.py +11 -5
  15. glaip_sdk/cli/commands/tools.py +49 -57
  16. glaip_sdk/cli/commands/transcripts.py +755 -0
  17. glaip_sdk/cli/config.py +48 -4
  18. glaip_sdk/cli/constants.py +38 -0
  19. glaip_sdk/cli/context.py +8 -0
  20. glaip_sdk/cli/core/__init__.py +79 -0
  21. glaip_sdk/cli/core/context.py +124 -0
  22. glaip_sdk/cli/core/output.py +846 -0
  23. glaip_sdk/cli/core/prompting.py +649 -0
  24. glaip_sdk/cli/core/rendering.py +187 -0
  25. glaip_sdk/cli/display.py +35 -19
  26. glaip_sdk/cli/hints.py +57 -0
  27. glaip_sdk/cli/io.py +6 -3
  28. glaip_sdk/cli/main.py +228 -119
  29. glaip_sdk/cli/masking.py +21 -33
  30. glaip_sdk/cli/pager.py +9 -10
  31. glaip_sdk/cli/parsers/__init__.py +1 -3
  32. glaip_sdk/cli/slash/__init__.py +0 -9
  33. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  34. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  35. glaip_sdk/cli/slash/agent_session.py +62 -21
  36. glaip_sdk/cli/slash/prompt.py +21 -0
  37. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  38. glaip_sdk/cli/slash/session.py +771 -140
  39. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  40. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  41. glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
  42. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  43. glaip_sdk/cli/slash/tui/loading.py +58 -0
  44. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  45. glaip_sdk/cli/transcript/__init__.py +12 -52
  46. glaip_sdk/cli/transcript/cache.py +255 -44
  47. glaip_sdk/cli/transcript/capture.py +27 -1
  48. glaip_sdk/cli/transcript/history.py +815 -0
  49. glaip_sdk/cli/transcript/viewer.py +72 -499
  50. glaip_sdk/cli/update_notifier.py +14 -5
  51. glaip_sdk/cli/utils.py +243 -1252
  52. glaip_sdk/cli/validators.py +5 -6
  53. glaip_sdk/client/__init__.py +2 -1
  54. glaip_sdk/client/_agent_payloads.py +45 -9
  55. glaip_sdk/client/agent_runs.py +147 -0
  56. glaip_sdk/client/agents.py +287 -29
  57. glaip_sdk/client/base.py +1 -0
  58. glaip_sdk/client/main.py +19 -10
  59. glaip_sdk/client/mcps.py +122 -12
  60. glaip_sdk/client/run_rendering.py +133 -88
  61. glaip_sdk/client/shared.py +21 -0
  62. glaip_sdk/client/tools.py +155 -10
  63. glaip_sdk/config/constants.py +11 -0
  64. glaip_sdk/mcps/__init__.py +21 -0
  65. glaip_sdk/mcps/base.py +345 -0
  66. glaip_sdk/models/__init__.py +90 -0
  67. glaip_sdk/models/agent.py +47 -0
  68. glaip_sdk/models/agent_runs.py +116 -0
  69. glaip_sdk/models/common.py +42 -0
  70. glaip_sdk/models/mcp.py +33 -0
  71. glaip_sdk/models/tool.py +33 -0
  72. glaip_sdk/payload_schemas/__init__.py +1 -13
  73. glaip_sdk/registry/__init__.py +55 -0
  74. glaip_sdk/registry/agent.py +164 -0
  75. glaip_sdk/registry/base.py +139 -0
  76. glaip_sdk/registry/mcp.py +253 -0
  77. glaip_sdk/registry/tool.py +232 -0
  78. glaip_sdk/rich_components.py +58 -2
  79. glaip_sdk/runner/__init__.py +59 -0
  80. glaip_sdk/runner/base.py +84 -0
  81. glaip_sdk/runner/deps.py +115 -0
  82. glaip_sdk/runner/langgraph.py +706 -0
  83. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  84. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  85. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  86. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  87. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  88. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  89. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  90. glaip_sdk/tools/__init__.py +22 -0
  91. glaip_sdk/tools/base.py +435 -0
  92. glaip_sdk/utils/__init__.py +58 -12
  93. glaip_sdk/utils/a2a/__init__.py +34 -0
  94. glaip_sdk/utils/a2a/event_processor.py +188 -0
  95. glaip_sdk/utils/bundler.py +267 -0
  96. glaip_sdk/utils/client.py +111 -0
  97. glaip_sdk/utils/client_utils.py +39 -7
  98. glaip_sdk/utils/datetime_helpers.py +58 -0
  99. glaip_sdk/utils/discovery.py +78 -0
  100. glaip_sdk/utils/display.py +23 -15
  101. glaip_sdk/utils/export.py +143 -0
  102. glaip_sdk/utils/general.py +0 -33
  103. glaip_sdk/utils/import_export.py +12 -7
  104. glaip_sdk/utils/import_resolver.py +492 -0
  105. glaip_sdk/utils/instructions.py +101 -0
  106. glaip_sdk/utils/rendering/__init__.py +115 -1
  107. glaip_sdk/utils/rendering/formatting.py +5 -30
  108. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  109. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  110. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  111. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  112. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  113. glaip_sdk/utils/rendering/models.py +1 -0
  114. glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
  115. glaip_sdk/utils/rendering/renderer/base.py +217 -1476
  116. glaip_sdk/utils/rendering/renderer/debug.py +26 -20
  117. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  118. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  119. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  120. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  121. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  122. glaip_sdk/utils/rendering/state.py +204 -0
  123. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  124. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
  125. glaip_sdk/utils/rendering/steps/format.py +176 -0
  126. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  127. glaip_sdk/utils/rendering/timing.py +36 -0
  128. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  129. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  130. glaip_sdk/utils/resource_refs.py +25 -13
  131. glaip_sdk/utils/runtime_config.py +425 -0
  132. glaip_sdk/utils/serialization.py +18 -0
  133. glaip_sdk/utils/sync.py +142 -0
  134. glaip_sdk/utils/tool_detection.py +33 -0
  135. glaip_sdk/utils/validation.py +16 -24
  136. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
  137. glaip_sdk-0.6.10.dist-info/RECORD +159 -0
  138. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
  139. glaip_sdk/models.py +0 -240
  140. glaip_sdk-0.1.3.dist-info/RECORD +0 -83
  141. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,78 @@
1
+ """Agent and tool discovery functions.
2
+
3
+ This module provides functions for finding agents and tools
4
+ from the GLAIP backend.
5
+
6
+ Authors:
7
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING
13
+
14
+ from gllm_core.utils import LoggerManager
15
+
16
+ if TYPE_CHECKING:
17
+ from glaip_sdk.agents import Agent
18
+ from glaip_sdk.tools import Tool
19
+
20
+ logger = LoggerManager().get_logger(__name__)
21
+
22
+
23
+ def find_agent(name: str) -> Agent | None:
24
+ """Find an agent by name using GLAIP SDK.
25
+
26
+ Args:
27
+ name: The name of the agent to find.
28
+
29
+ Returns:
30
+ The agent if found, None otherwise.
31
+
32
+ Example:
33
+ >>> from glaip_sdk.utils.discovery import find_agent
34
+ >>> agent = find_agent("weather_reporter")
35
+ >>> if agent:
36
+ ... print(f"Found agent: {agent.name}")
37
+ """
38
+ from glaip_sdk.utils.client import get_client # noqa: PLC0415
39
+
40
+ client = get_client()
41
+ try:
42
+ agents = client.list_agents()
43
+ for agent in agents:
44
+ if agent.name == name:
45
+ return agent
46
+ return None
47
+ except Exception as e:
48
+ logger.error("Error finding agent '%s': %s", name, e)
49
+ return None
50
+
51
+
52
+ def find_tool(name: str) -> Tool | None:
53
+ """Find a tool by name using GLAIP SDK.
54
+
55
+ Args:
56
+ name: The name of the tool to find.
57
+
58
+ Returns:
59
+ The tool if found, None otherwise.
60
+
61
+ Example:
62
+ >>> from glaip_sdk.utils.discovery import find_tool
63
+ >>> tool = find_tool("weather_api")
64
+ >>> if tool:
65
+ ... print(f"Found tool: {tool.name}")
66
+ """
67
+ from glaip_sdk.utils.client import get_client # noqa: PLC0415
68
+
69
+ client = get_client()
70
+ try:
71
+ tools = client.find_tools(name)
72
+ for tool in tools:
73
+ if tool.name == name:
74
+ return tool
75
+ return None
76
+ except Exception as e:
77
+ logger.error("Error finding tool '%s': %s", name, e)
78
+ return None
@@ -4,6 +4,9 @@ Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
9
+ from importlib import import_module
7
10
  from typing import TYPE_CHECKING, Any
8
11
 
9
12
  from glaip_sdk.branding import SUCCESS, SUCCESS_STYLE
@@ -13,42 +16,47 @@ if TYPE_CHECKING: # pragma: no cover - import-time typing helpers
13
16
  from rich.console import Console
14
17
  from rich.text import Text
15
18
 
16
- from glaip_sdk.rich_components import AIPPanel
19
+ from glaip_sdk.rich_components import AIPanel
20
+ else: # pragma: no cover - runtime fallback for type checking
21
+ AIPanel = Any # type: ignore[assignment]
17
22
 
18
23
 
19
24
  def _check_rich_available() -> bool:
20
- """Check if Rich and our custom components can be imported."""
25
+ """Return True when core Rich display dependencies are importable."""
21
26
  try:
22
27
  __import__("rich.console")
23
28
  __import__("rich.text")
24
29
  __import__("glaip_sdk.rich_components")
25
- return True
26
30
  except Exception:
27
31
  return False
32
+ return True
28
33
 
29
34
 
30
35
  RICH_AVAILABLE = _check_rich_available()
31
36
 
32
37
 
33
- def _create_console() -> "Console":
38
+ def _create_console() -> Console:
34
39
  """Return a Console instance with lazy import to ease mocking."""
35
- from rich.console import Console # Local import for test friendliness
36
-
37
- return Console()
40
+ if not RICH_AVAILABLE: # pragma: no cover - defensive guard
41
+ raise RuntimeError("Rich Console is not available")
42
+ console_module = import_module("rich.console")
43
+ return console_module.Console()
38
44
 
39
45
 
40
- def _create_text(*args: Any, **kwargs: Any) -> "Text":
46
+ def _create_text(*args: Any, **kwargs: Any) -> Text:
41
47
  """Return a Text instance with lazy import to ease mocking."""
42
- from rich.text import Text # Local import for test friendliness
48
+ if not RICH_AVAILABLE: # pragma: no cover - defensive guard
49
+ raise RuntimeError("Rich Text is not available")
50
+ text_module = import_module("rich.text")
51
+ return text_module.Text(*args, **kwargs)
43
52
 
44
- return Text(*args, **kwargs)
45
53
 
46
-
47
- def _create_panel(*args: Any, **kwargs: Any) -> "AIPPanel":
54
+ def _create_panel(*args: Any, **kwargs: Any) -> AIPanel:
48
55
  """Return an AIPPanel instance with lazy import to ease mocking."""
49
- from glaip_sdk.rich_components import AIPPanel # Local import for test friendliness
50
-
51
- return AIPPanel(*args, **kwargs)
56
+ if not RICH_AVAILABLE: # pragma: no cover - defensive guard
57
+ raise RuntimeError("AIPPanel is not available")
58
+ components = import_module("glaip_sdk.rich_components")
59
+ return components.AIPPanel(*args, **kwargs)
52
60
 
53
61
 
54
62
  def print_agent_output(output: str, title: str = "Agent Output") -> None:
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env python3
2
+ """Export utilities for remote agent run transcripts.
3
+
4
+ Authors:
5
+ Raymond Christopher (raymond.christopher@gdplabs.id)
6
+ """
7
+
8
+ import json
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from glaip_sdk.models.agent_runs import RunWithOutput, RunOutputChunk
14
+
15
+
16
+ def export_remote_transcript_jsonl(
17
+ run: RunWithOutput,
18
+ destination: Path,
19
+ *,
20
+ overwrite: bool = False,
21
+ agent_name: str | None = None,
22
+ model: str | None = None,
23
+ ) -> Path:
24
+ """Export a remote run transcript to JSONL format compatible with local transcript viewers.
25
+
26
+ Args:
27
+ run: RunWithOutput instance to export
28
+ destination: Target file path for JSONL export
29
+ overwrite: Whether to overwrite existing file
30
+ agent_name: Optional agent name for metadata
31
+ model: Optional model name for metadata (extracted from run.config if not provided)
32
+
33
+ Returns:
34
+ Path to the exported file
35
+
36
+ Raises:
37
+ FileExistsError: If destination exists and overwrite is False
38
+ OSError: If file cannot be written
39
+ """
40
+ if destination.exists() and not overwrite:
41
+ raise FileExistsError(f"File already exists: {destination}")
42
+
43
+ # Ensure parent directory exists
44
+ destination.parent.mkdir(parents=True, exist_ok=True)
45
+
46
+ model_name = model or _extract_model(run)
47
+ final_output_text = _extract_final_output(run.output) or ""
48
+
49
+ meta_payload = _build_meta_payload(run, agent_name, model_name)
50
+ meta_record = _build_meta_record(run, agent_name, model_name, final_output_text, meta_payload)
51
+
52
+ _write_jsonl_file(destination, meta_record, run.output)
53
+
54
+ return destination
55
+
56
+
57
+ def _build_meta_payload(run: RunWithOutput, agent_name: str | None, model_name: str | None) -> dict[str, Any]:
58
+ """Build the meta payload dictionary."""
59
+ return {
60
+ "agent_name": agent_name,
61
+ "model": model_name,
62
+ "input_message": run.input,
63
+ "status": run.status,
64
+ "run_type": run.run_type,
65
+ "schedule_id": str(run.schedule_id) if run.schedule_id else None,
66
+ "config": run.config or {},
67
+ "created_at": run.created_at.isoformat() if run.created_at else None,
68
+ "updated_at": run.updated_at.isoformat() if run.updated_at else None,
69
+ "event_count": len(run.output),
70
+ }
71
+
72
+
73
+ def _build_meta_record(
74
+ run: RunWithOutput,
75
+ agent_name: str | None,
76
+ model_name: str | None,
77
+ final_output_text: str,
78
+ meta_payload: dict[str, Any],
79
+ ) -> dict[str, Any]:
80
+ """Build the meta record dictionary."""
81
+ return {
82
+ "type": "meta",
83
+ "run_id": str(run.id),
84
+ "agent_id": str(run.agent_id),
85
+ "agent_name": agent_name,
86
+ "model": model_name,
87
+ "created_at": run.created_at.isoformat() if run.created_at else None,
88
+ "default_output": final_output_text,
89
+ "final_output": final_output_text,
90
+ "server_run_id": str(run.id),
91
+ "started_at": run.started_at.isoformat() if run.started_at else None,
92
+ "finished_at": run.completed_at.isoformat() if run.completed_at else None,
93
+ "meta": meta_payload,
94
+ "source": "remote_history",
95
+ # Back-compat fields used by older tooling
96
+ "run_type": run.run_type,
97
+ "schedule_id": str(run.schedule_id) if run.schedule_id else None,
98
+ "status": run.status,
99
+ "input": run.input,
100
+ "config": run.config or {},
101
+ "updated_at": run.updated_at.isoformat() if run.updated_at else None,
102
+ }
103
+
104
+
105
+ def _write_jsonl_file(destination: Path, meta_record: dict[str, Any], events: list[RunOutputChunk]) -> None:
106
+ """Write the JSONL file with meta and event records."""
107
+ records: list[dict[str, Any]] = [meta_record]
108
+ records.extend({"type": "event", "event": event} for event in events)
109
+
110
+ with destination.open("w", encoding="utf-8") as fh:
111
+ for idx, record in enumerate(records):
112
+ json.dump(record, fh, ensure_ascii=False, indent=2, default=_json_default)
113
+ fh.write("\n")
114
+ if idx != len(records) - 1:
115
+ fh.write("\n")
116
+
117
+
118
+ def _extract_model(run: RunWithOutput) -> str | None:
119
+ """Best-effort extraction of the model name from run metadata."""
120
+ config = run.config or {}
121
+ if isinstance(config, dict):
122
+ model = config.get("model") or config.get("llm", {}).get("model")
123
+ if isinstance(model, str):
124
+ return model
125
+ return None
126
+
127
+
128
+ def _extract_final_output(events: list[RunOutputChunk]) -> str | None:
129
+ """Return the final response content from the event stream."""
130
+ for chunk in reversed(events):
131
+ content = chunk.get("content")
132
+ if not content:
133
+ continue
134
+ if chunk.get("event_type") == "final_response" or chunk.get("final"):
135
+ return str(content)
136
+ return None
137
+
138
+
139
+ def _json_default(obj: Any) -> Any:
140
+ """JSON serializer for datetime objects."""
141
+ if isinstance(obj, datetime):
142
+ return obj.isoformat()
143
+ raise TypeError(f"Type {type(obj)} not serializable")
@@ -4,46 +4,13 @@ Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
- import re
8
7
  from collections.abc import Iterable, Iterator
9
8
  from datetime import datetime
10
9
  from typing import Any
11
- from uuid import UUID
12
10
 
13
11
  import click
14
12
 
15
13
 
16
- def is_uuid(value: str) -> bool:
17
- """Check if a string is a valid UUID.
18
-
19
- Args:
20
- value: String to check
21
-
22
- Returns:
23
- True if value is a valid UUID, False otherwise
24
- """
25
- try:
26
- UUID(value)
27
- return True
28
- except (ValueError, TypeError):
29
- return False
30
-
31
-
32
- def sanitize_name(name: str) -> str:
33
- """Sanitize a name for resource creation.
34
-
35
- Args:
36
- name: Raw name input
37
-
38
- Returns:
39
- Sanitized name suitable for resource creation
40
- """
41
- # Remove special characters and normalize
42
- sanitized = re.sub(r"[^a-zA-Z0-9\-_]", "-", name.strip())
43
- sanitized = re.sub(r"-+", "-", sanitized) # Collapse multiple dashes
44
- return sanitized.lower().strip("-")
45
-
46
-
47
14
  def format_file_size(size_bytes: int) -> str:
48
15
  """Format file size in human readable format.
49
16
 
@@ -9,10 +9,19 @@ Authors:
9
9
 
10
10
  from typing import Any
11
11
 
12
+ from glaip_sdk.utils.resource_refs import _extract_id_from_item
13
+
12
14
 
13
15
  def extract_ids_from_export(items: list[Any]) -> list[str]:
14
16
  """Extract IDs from export format (list of dicts with id/name fields).
15
17
 
18
+ This function is similar to `extract_ids` in `resource_refs.py` but differs in behavior:
19
+ - This function SKIPS items without IDs (doesn't convert to string)
20
+ - `extract_ids` converts items without IDs to strings as fallback
21
+
22
+ This difference is intentional: export format should only include actual IDs,
23
+ while general resource reference extraction may need fallback string conversion.
24
+
16
25
  Args:
17
26
  items: List of items (dicts with id/name or strings)
18
27
 
@@ -29,13 +38,9 @@ def extract_ids_from_export(items: list[Any]) -> list[str]:
29
38
 
30
39
  ids = []
31
40
  for item in items:
32
- if isinstance(item, str):
33
- ids.append(item)
34
- elif hasattr(item, "id"):
35
- ids.append(str(item.id))
36
- elif isinstance(item, dict) and "id" in item:
37
- ids.append(str(item["id"]))
38
- # Skip items without ID (don't convert to string)
41
+ extracted = _extract_id_from_item(item, skip_missing=True)
42
+ if extracted is not None:
43
+ ids.append(extracted)
39
44
 
40
45
  return ids
41
46