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
glaip_sdk/utils/client_utils.py
CHANGED
|
@@ -15,8 +15,10 @@ from pathlib import Path
|
|
|
15
15
|
from typing import Any, BinaryIO, NoReturn
|
|
16
16
|
|
|
17
17
|
import httpx
|
|
18
|
-
|
|
19
18
|
from glaip_sdk.exceptions import AgentTimeoutError
|
|
19
|
+
from glaip_sdk.models import AgentResponse, MCPResponse, ToolResponse
|
|
20
|
+
from glaip_sdk.utils.resource_refs import extract_ids as extract_ids_new
|
|
21
|
+
from glaip_sdk.utils.resource_refs import find_by_name as find_by_name_new
|
|
20
22
|
|
|
21
23
|
# Set up module-level logger
|
|
22
24
|
logger = logging.getLogger("glaip_sdk.client_utils")
|
|
@@ -41,6 +43,11 @@ class MultipartData:
|
|
|
41
43
|
self._exit_stack.close()
|
|
42
44
|
|
|
43
45
|
def __enter__(self) -> "MultipartData":
|
|
46
|
+
"""Enter context manager.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Self instance for context manager protocol
|
|
50
|
+
"""
|
|
44
51
|
return self
|
|
45
52
|
|
|
46
53
|
def __exit__(
|
|
@@ -49,6 +56,13 @@ class MultipartData:
|
|
|
49
56
|
_exc_val: BaseException | None,
|
|
50
57
|
_exc_tb: Any,
|
|
51
58
|
) -> None:
|
|
59
|
+
"""Exit context manager and close all file handles.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
_exc_type: Exception type (unused)
|
|
63
|
+
_exc_val: Exception value (unused)
|
|
64
|
+
_exc_tb: Exception traceback (unused)
|
|
65
|
+
"""
|
|
52
66
|
self.close()
|
|
53
67
|
|
|
54
68
|
|
|
@@ -65,8 +79,6 @@ def extract_ids(items: list[str | Any] | None) -> list[str] | None:
|
|
|
65
79
|
This function maintains backward compatibility by returning None for empty input.
|
|
66
80
|
New code should use glaip_sdk.utils.resource_refs.extract_ids which returns [].
|
|
67
81
|
"""
|
|
68
|
-
from .resource_refs import extract_ids as extract_ids_new
|
|
69
|
-
|
|
70
82
|
if not items:
|
|
71
83
|
return None
|
|
72
84
|
|
|
@@ -74,14 +86,16 @@ def extract_ids(items: list[str | Any] | None) -> list[str] | None:
|
|
|
74
86
|
return result if result else None
|
|
75
87
|
|
|
76
88
|
|
|
77
|
-
def create_model_instances(
|
|
78
|
-
data: list[dict] | None, model_class: type, client: Any
|
|
79
|
-
) -> list[Any]:
|
|
89
|
+
def create_model_instances(data: list[dict] | None, model_class: type, client: Any) -> list[Any]:
|
|
80
90
|
"""Create model instances from API data with client association.
|
|
81
91
|
|
|
82
92
|
This is a common pattern used across different clients (agents, tools, mcps)
|
|
83
93
|
to create model instances and associate them with the client.
|
|
84
94
|
|
|
95
|
+
For runtime classes (Agent, Tool, MCP) that have a from_response method,
|
|
96
|
+
this function will use the corresponding Response model to parse the API data
|
|
97
|
+
and then create the runtime instance using from_response.
|
|
98
|
+
|
|
85
99
|
Args:
|
|
86
100
|
data: List of dictionaries from API response
|
|
87
101
|
model_class: The model class to instantiate
|
|
@@ -93,12 +107,29 @@ def create_model_instances(
|
|
|
93
107
|
if not data:
|
|
94
108
|
return []
|
|
95
109
|
|
|
110
|
+
# Check if the model_class has a from_response method (runtime class pattern)
|
|
111
|
+
if hasattr(model_class, "from_response"):
|
|
112
|
+
# Map runtime classes to their response models
|
|
113
|
+
response_model_map = {
|
|
114
|
+
"Agent": AgentResponse,
|
|
115
|
+
"Tool": ToolResponse,
|
|
116
|
+
"MCP": MCPResponse,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
response_model = response_model_map.get(model_class.__name__)
|
|
120
|
+
if response_model:
|
|
121
|
+
instances = []
|
|
122
|
+
for item_data in data:
|
|
123
|
+
response = response_model(**item_data)
|
|
124
|
+
instance = model_class.from_response(response, client=client)
|
|
125
|
+
instances.append(instance)
|
|
126
|
+
return instances
|
|
127
|
+
|
|
128
|
+
# Fallback to direct instantiation for other classes
|
|
96
129
|
return [model_class(**item_data)._set_client(client) for item_data in data]
|
|
97
130
|
|
|
98
131
|
|
|
99
|
-
def find_by_name(
|
|
100
|
-
items: list[Any], name: str, case_sensitive: bool = False
|
|
101
|
-
) -> list[Any]:
|
|
132
|
+
def find_by_name(items: list[Any], name: str, case_sensitive: bool = False) -> list[Any]:
|
|
102
133
|
"""Filter items by name with optional case sensitivity.
|
|
103
134
|
|
|
104
135
|
This is a common pattern used across different clients for client-side
|
|
@@ -115,8 +146,6 @@ def find_by_name(
|
|
|
115
146
|
Note:
|
|
116
147
|
This function now delegates to glaip_sdk.utils.resource_refs.find_by_name.
|
|
117
148
|
"""
|
|
118
|
-
from .resource_refs import find_by_name as find_by_name_new
|
|
119
|
-
|
|
120
149
|
return find_by_name_new(items, name, case_sensitive)
|
|
121
150
|
|
|
122
151
|
|
|
@@ -220,9 +249,7 @@ def _handle_streaming_error(
|
|
|
220
249
|
if isinstance(e, httpx.ReadTimeout):
|
|
221
250
|
logger.error(f"Read timeout during streaming: {e}")
|
|
222
251
|
logger.error("This usually indicates the backend is taking too long to respond")
|
|
223
|
-
logger.error(
|
|
224
|
-
"Consider increasing the timeout value or checking backend performance"
|
|
225
|
-
)
|
|
252
|
+
logger.error("Consider increasing the timeout value or checking backend performance")
|
|
226
253
|
raise AgentTimeoutError(timeout_seconds or 30.0, agent_name)
|
|
227
254
|
|
|
228
255
|
elif isinstance(e, httpx.TimeoutException):
|
|
@@ -261,9 +288,7 @@ def _yield_event_data(event_data: dict[str, Any] | None) -> Iterator[dict[str, A
|
|
|
261
288
|
yield event_data
|
|
262
289
|
|
|
263
290
|
|
|
264
|
-
def _flush_remaining_buffer(
|
|
265
|
-
buf: list[str], event_type: str | None, event_id: str | None
|
|
266
|
-
) -> Iterator[dict[str, Any]]:
|
|
291
|
+
def _flush_remaining_buffer(buf: list[str], event_type: str | None, event_id: str | None) -> Iterator[dict[str, Any]]:
|
|
267
292
|
"""Flush any remaining data in buffer."""
|
|
268
293
|
if buf:
|
|
269
294
|
yield {
|
|
@@ -303,9 +328,7 @@ def iter_sse_events(
|
|
|
303
328
|
if line is None:
|
|
304
329
|
continue
|
|
305
330
|
|
|
306
|
-
buf, event_type, event_id, event_data, completed = _process_sse_line(
|
|
307
|
-
line, buf, event_type, event_id
|
|
308
|
-
)
|
|
331
|
+
buf, event_type, event_id, event_data, completed = _process_sse_line(line, buf, event_type, event_id)
|
|
309
332
|
|
|
310
333
|
yield from _yield_event_data(event_data)
|
|
311
334
|
if completed:
|
|
@@ -371,9 +394,7 @@ def _create_form_data(message: str) -> dict[str, Any]:
|
|
|
371
394
|
return {"input": message, "message": message, "stream": True}
|
|
372
395
|
|
|
373
396
|
|
|
374
|
-
def _prepare_file_entry(
|
|
375
|
-
item: str | BinaryIO, stack: ExitStack
|
|
376
|
-
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
397
|
+
def _prepare_file_entry(item: str | BinaryIO, stack: ExitStack) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
377
398
|
"""Prepare a single file entry for multipart data."""
|
|
378
399
|
if isinstance(item, str):
|
|
379
400
|
return _prepare_path_entry(item, stack)
|
|
@@ -381,9 +402,7 @@ def _prepare_file_entry(
|
|
|
381
402
|
return _prepare_stream_entry(item)
|
|
382
403
|
|
|
383
404
|
|
|
384
|
-
def _prepare_path_entry(
|
|
385
|
-
path_str: str, stack: ExitStack
|
|
386
|
-
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
405
|
+
def _prepare_path_entry(path_str: str, stack: ExitStack) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
387
406
|
"""Prepare a file path entry."""
|
|
388
407
|
file_path = Path(path_str)
|
|
389
408
|
if not file_path.exists():
|
|
@@ -426,6 +445,19 @@ def _prepare_stream_entry(
|
|
|
426
445
|
)
|
|
427
446
|
|
|
428
447
|
|
|
448
|
+
def add_kwargs_to_payload(payload: dict[str, Any], kwargs: dict[str, Any], excluded_keys: set[str]) -> None:
|
|
449
|
+
"""Add kwargs to payload excluding specified keys.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
payload: Payload dictionary to update.
|
|
453
|
+
kwargs: Keyword arguments to add.
|
|
454
|
+
excluded_keys: Keys to exclude from kwargs.
|
|
455
|
+
"""
|
|
456
|
+
for key, value in kwargs.items():
|
|
457
|
+
if key not in excluded_keys:
|
|
458
|
+
payload[key] = value
|
|
459
|
+
|
|
460
|
+
|
|
429
461
|
def prepare_multipart_data(message: str, files: list[str | BinaryIO]) -> MultipartData:
|
|
430
462
|
"""Prepare multipart form data for file uploads.
|
|
431
463
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Shared datetime parsing helpers used across CLI and rendering modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
__all__ = ["coerce_datetime", "from_numeric_timestamp"]
|
|
9
|
+
|
|
10
|
+
_Z_SUFFIX = "+00:00"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def from_numeric_timestamp(raw_value: Any) -> datetime | None:
|
|
14
|
+
"""Convert unix timestamp-like values to datetime with sanity checks."""
|
|
15
|
+
try:
|
|
16
|
+
candidate = float(raw_value)
|
|
17
|
+
except Exception:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
if candidate < 1_000_000_000:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
return datetime.fromtimestamp(candidate, tz=timezone.utc)
|
|
25
|
+
except Exception:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _parse_iso(value: str | None) -> datetime | None:
|
|
30
|
+
"""Parse ISO8601 strings while tolerating legacy 'Z' suffixes."""
|
|
31
|
+
if not value:
|
|
32
|
+
return None
|
|
33
|
+
try:
|
|
34
|
+
return datetime.fromisoformat(value.replace("Z", _Z_SUFFIX))
|
|
35
|
+
except Exception:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def coerce_datetime(value: Any) -> datetime | None:
|
|
40
|
+
"""Best-effort conversion of assorted timestamp inputs to aware UTC datetimes."""
|
|
41
|
+
if value is None:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
if isinstance(value, datetime):
|
|
45
|
+
dt = value
|
|
46
|
+
elif isinstance(value, (int, float)):
|
|
47
|
+
dt = from_numeric_timestamp(value)
|
|
48
|
+
elif isinstance(value, str):
|
|
49
|
+
dt = _parse_iso(value) or from_numeric_timestamp(value)
|
|
50
|
+
else:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
if dt is None:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
if dt.tzinfo is None:
|
|
57
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
58
|
+
return dt.astimezone(timezone.utc)
|
|
@@ -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
|
glaip_sdk/utils/display.py
CHANGED
|
@@ -4,9 +4,59 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from importlib import import_module
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from glaip_sdk.branding import SUCCESS, SUCCESS_STYLE
|
|
13
|
+
from glaip_sdk.icons import ICON_AGENT
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING: # pragma: no cover - import-time typing helpers
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.text import Text
|
|
18
|
+
|
|
19
|
+
from glaip_sdk.rich_components import AIPanel
|
|
20
|
+
else: # pragma: no cover - runtime fallback for type checking
|
|
21
|
+
AIPanel = Any # type: ignore[assignment]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _check_rich_available() -> bool:
|
|
25
|
+
"""Return True when core Rich display dependencies are importable."""
|
|
26
|
+
try:
|
|
27
|
+
__import__("rich.console")
|
|
28
|
+
__import__("rich.text")
|
|
29
|
+
__import__("glaip_sdk.rich_components")
|
|
30
|
+
except Exception:
|
|
31
|
+
return False
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
RICH_AVAILABLE = _check_rich_available()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _create_console() -> Console:
|
|
39
|
+
"""Return a Console instance with lazy import to ease mocking."""
|
|
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()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _create_text(*args: Any, **kwargs: Any) -> Text:
|
|
47
|
+
"""Return a Text instance with lazy import to ease mocking."""
|
|
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)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _create_panel(*args: Any, **kwargs: Any) -> AIPanel:
|
|
55
|
+
"""Return an AIPPanel instance with lazy import to ease mocking."""
|
|
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)
|
|
10
60
|
|
|
11
61
|
|
|
12
62
|
def print_agent_output(output: str, title: str = "Agent Output") -> None:
|
|
@@ -17,17 +67,11 @@ def print_agent_output(output: str, title: str = "Agent Output") -> None:
|
|
|
17
67
|
title: Title for the output panel
|
|
18
68
|
"""
|
|
19
69
|
if RICH_AVAILABLE:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
from glaip_sdk.rich_components import AIPPanel
|
|
25
|
-
|
|
26
|
-
console = Console()
|
|
27
|
-
panel = AIPPanel(
|
|
28
|
-
Text(output, style="green"),
|
|
70
|
+
console = _create_console()
|
|
71
|
+
panel = _create_panel(
|
|
72
|
+
_create_text(output, style=SUCCESS),
|
|
29
73
|
title=title,
|
|
30
|
-
border_style=
|
|
74
|
+
border_style=SUCCESS,
|
|
31
75
|
)
|
|
32
76
|
console.print(panel)
|
|
33
77
|
else:
|
|
@@ -36,7 +80,7 @@ def print_agent_output(output: str, title: str = "Agent Output") -> None:
|
|
|
36
80
|
print("=" * (len(title) + 8))
|
|
37
81
|
|
|
38
82
|
|
|
39
|
-
def print_agent_created(agent: Any, title: str = "
|
|
83
|
+
def print_agent_created(agent: Any, title: str = f"{ICON_AGENT} Agent Created") -> None:
|
|
40
84
|
"""Print agent creation success with rich formatting.
|
|
41
85
|
|
|
42
86
|
Args:
|
|
@@ -44,21 +88,16 @@ def print_agent_created(agent: Any, title: str = "🤖 Agent Created") -> None:
|
|
|
44
88
|
title: Title for the output panel
|
|
45
89
|
"""
|
|
46
90
|
if RICH_AVAILABLE:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
from glaip_sdk.rich_components import AIPPanel
|
|
51
|
-
|
|
52
|
-
console = Console()
|
|
53
|
-
panel = AIPPanel(
|
|
54
|
-
f"[green]✅ Agent '{agent.name}' created successfully![/green]\n\n"
|
|
91
|
+
console = _create_console()
|
|
92
|
+
panel = _create_panel(
|
|
93
|
+
f"[{SUCCESS_STYLE}]✅ Agent '{agent.name}' created successfully![/]\n\n"
|
|
55
94
|
f"ID: {agent.id}\n"
|
|
56
95
|
f"Model: {getattr(agent, 'model', 'N/A')}\n"
|
|
57
96
|
f"Type: {getattr(agent, 'type', 'config')}\n"
|
|
58
97
|
f"Framework: {getattr(agent, 'framework', 'langchain')}\n"
|
|
59
98
|
f"Version: {getattr(agent, 'version', '1.0')}",
|
|
60
99
|
title=title,
|
|
61
|
-
border_style=
|
|
100
|
+
border_style=SUCCESS,
|
|
62
101
|
)
|
|
63
102
|
console.print(panel)
|
|
64
103
|
else:
|
|
@@ -77,11 +116,8 @@ def print_agent_updated(agent: Any) -> None:
|
|
|
77
116
|
agent: The updated agent object
|
|
78
117
|
"""
|
|
79
118
|
if RICH_AVAILABLE:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
console = Console()
|
|
84
|
-
console.print(f"[green]✅ Agent '{agent.name}' updated successfully[/green]")
|
|
119
|
+
console = _create_console()
|
|
120
|
+
console.print(f"[{SUCCESS_STYLE}]✅ Agent '{agent.name}' updated successfully[/]")
|
|
85
121
|
else:
|
|
86
122
|
print(f"✅ Agent '{agent.name}' updated successfully")
|
|
87
123
|
|
|
@@ -93,10 +129,7 @@ def print_agent_deleted(agent_id: str) -> None:
|
|
|
93
129
|
agent_id: The deleted agent's ID
|
|
94
130
|
"""
|
|
95
131
|
if RICH_AVAILABLE:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
console = Console()
|
|
100
|
-
console.print(f"[green]✅ Agent deleted successfully (ID: {agent_id})[/green]")
|
|
132
|
+
console = _create_console()
|
|
133
|
+
console.print(f"[{SUCCESS_STYLE}]✅ Agent deleted successfully (ID: {agent_id})[/]")
|
|
101
134
|
else:
|
|
102
135
|
print(f"✅ Agent deleted successfully (ID: {agent_id})")
|
|
@@ -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")
|
glaip_sdk/utils/general.py
CHANGED
|
@@ -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
|
|
|
@@ -76,9 +43,7 @@ def format_datetime(dt: datetime | str | None) -> str:
|
|
|
76
43
|
return str(dt)
|
|
77
44
|
|
|
78
45
|
|
|
79
|
-
def progress_bar(
|
|
80
|
-
iterable: Iterable[Any], description: str = "Processing"
|
|
81
|
-
) -> Iterator[Any]:
|
|
46
|
+
def progress_bar(iterable: Iterable[Any], description: str = "Processing") -> Iterator[Any]:
|
|
82
47
|
"""Simple progress bar using click.
|
|
83
48
|
|
|
84
49
|
Args:
|