glaip-sdk 0.1.2__py3-none-any.whl → 0.7.17__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 +44 -4
- glaip_sdk/_version.py +9 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1413 -0
- glaip_sdk/branding.py +126 -2
- glaip_sdk/cli/account_store.py +555 -0
- glaip_sdk/cli/auth.py +260 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +116 -0
- glaip_sdk/cli/commands/agents/_common.py +562 -0
- glaip_sdk/cli/commands/agents/create.py +155 -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 +728 -113
- 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 +12 -8
- 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 +163 -17
- glaip_sdk/cli/config.py +49 -4
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -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 +41 -20
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +340 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +12 -13
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/__init__.py +0 -9
- 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 +62 -21
- glaip_sdk/cli/slash/prompt.py +21 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +568 -0
- glaip_sdk/cli/slash/session.py +1105 -153
- glaip_sdk/cli/slash/tui/__init__.py +36 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +177 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +1853 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +92 -0
- glaip_sdk/cli/slash/tui/indicators.py +341 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
- glaip_sdk/cli/slash/tui/loading.py +80 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +760 -0
- glaip_sdk/cli/slash/tui/terminal.py +407 -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 +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +388 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +66 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -463
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +227 -10
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +3 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +576 -44
- glaip_sdk/client/base.py +26 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -14
- glaip_sdk/client/mcps.py +165 -24
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +63 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +546 -92
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +206 -32
- glaip_sdk/config/constants.py +33 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +136 -0
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +48 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -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 +445 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +1055 -0
- glaip_sdk/runner/logging_config.py +77 -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 +116 -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 +242 -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 +488 -0
- glaip_sdk/utils/__init__.py +59 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +39 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +12 -7
- glaip_sdk/utils/import_resolver.py +524 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +5 -30
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +1 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
- glaip_sdk/utils/rendering/renderer/base.py +299 -1434
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- glaip_sdk/utils/rendering/renderer/debug.py +26 -20
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +4 -33
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -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/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- 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 +25 -13
- glaip_sdk/utils/runtime_config.py +426 -0
- glaip_sdk/utils/serialization.py +18 -0
- glaip_sdk/utils/sync.py +162 -0
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +16 -24
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +69 -23
- glaip_sdk-0.7.17.dist-info/RECORD +224 -0
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1369
- glaip_sdk/cli/commands/mcps.py +0 -1187
- glaip_sdk/cli/commands/tools.py +0 -584
- glaip_sdk/cli/utils.py +0 -1278
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.2.dist-info/RECORD +0 -82
- glaip_sdk-0.1.2.dist-info/entry_points.txt +0 -3
glaip_sdk/client/tools.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import logging
|
|
@@ -16,11 +17,14 @@ from glaip_sdk.config.constants import (
|
|
|
16
17
|
DEFAULT_TOOL_TYPE,
|
|
17
18
|
DEFAULT_TOOL_VERSION,
|
|
18
19
|
)
|
|
19
|
-
from glaip_sdk.models import
|
|
20
|
+
from glaip_sdk.models import ToolResponse
|
|
21
|
+
from glaip_sdk.tools import Tool
|
|
20
22
|
from glaip_sdk.utils.client_utils import (
|
|
23
|
+
add_kwargs_to_payload,
|
|
21
24
|
create_model_instances,
|
|
22
25
|
find_by_name,
|
|
23
26
|
)
|
|
27
|
+
from glaip_sdk.utils.resource_refs import is_uuid
|
|
24
28
|
|
|
25
29
|
# API endpoints
|
|
26
30
|
TOOLS_ENDPOINT = "/tools/"
|
|
@@ -58,11 +62,11 @@ class ToolClient(BaseClient):
|
|
|
58
62
|
def get_tool_by_id(self, tool_id: str) -> Tool:
|
|
59
63
|
"""Get tool by ID."""
|
|
60
64
|
data = self._request("GET", f"{TOOLS_ENDPOINT}{tool_id}")
|
|
61
|
-
|
|
65
|
+
response = ToolResponse(**data)
|
|
66
|
+
return Tool.from_response(response, client=self)
|
|
62
67
|
|
|
63
68
|
def find_tools(self, name: str | None = None) -> list[Tool]:
|
|
64
69
|
"""Find tools by name."""
|
|
65
|
-
# Backend doesn't support name query parameter, so we fetch all and filter client-side
|
|
66
70
|
data = self._request("GET", TOOLS_ENDPOINT)
|
|
67
71
|
tools = create_model_instances(data, Tool, self)
|
|
68
72
|
return find_by_name(tools, name, case_sensitive=False)
|
|
@@ -99,6 +103,9 @@ class ToolClient(BaseClient):
|
|
|
99
103
|
def _prepare_upload_data(self, name: str, framework: str, description: str | None = None, **kwargs) -> dict:
|
|
100
104
|
"""Prepare upload data dictionary.
|
|
101
105
|
|
|
106
|
+
Uses the same payload building logic as _build_create_payload to ensure
|
|
107
|
+
consistency between upload and metadata-only tool creation.
|
|
108
|
+
|
|
102
109
|
Args:
|
|
103
110
|
name: Tool name
|
|
104
111
|
framework: Tool framework
|
|
@@ -108,27 +115,19 @@ class ToolClient(BaseClient):
|
|
|
108
115
|
Returns:
|
|
109
116
|
dict: Upload data dictionary
|
|
110
117
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"framework": framework,
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if description:
|
|
117
|
-
data["description"] = description
|
|
118
|
-
|
|
119
|
-
# Handle tags if provided in kwargs
|
|
120
|
-
if kwargs.get("tags"):
|
|
121
|
-
if isinstance(kwargs["tags"], list):
|
|
122
|
-
data["tags"] = ",".join(kwargs["tags"])
|
|
123
|
-
else:
|
|
124
|
-
data["tags"] = kwargs["tags"]
|
|
118
|
+
# Extract tool_type from kwargs if present, defaulting to DEFAULT_TOOL_TYPE
|
|
119
|
+
tool_type = kwargs.pop("tool_type", DEFAULT_TOOL_TYPE)
|
|
125
120
|
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
121
|
+
# Use _build_create_payload to build the payload consistently
|
|
122
|
+
payload = self._build_create_payload(
|
|
123
|
+
name=name,
|
|
124
|
+
description=description,
|
|
125
|
+
framework=framework,
|
|
126
|
+
tool_type=tool_type,
|
|
127
|
+
**kwargs,
|
|
128
|
+
)
|
|
130
129
|
|
|
131
|
-
return
|
|
130
|
+
return payload
|
|
132
131
|
|
|
133
132
|
def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
|
|
134
133
|
"""Upload tool file to server.
|
|
@@ -152,7 +151,8 @@ class ToolClient(BaseClient):
|
|
|
152
151
|
data=upload_data,
|
|
153
152
|
)
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
tool_response = ToolResponse(**response)
|
|
155
|
+
return Tool.from_response(tool_response, client=self)
|
|
156
156
|
|
|
157
157
|
def _build_create_payload(
|
|
158
158
|
self,
|
|
@@ -200,9 +200,7 @@ class ToolClient(BaseClient):
|
|
|
200
200
|
|
|
201
201
|
# Add any other kwargs (excluding already handled ones)
|
|
202
202
|
excluded_keys = {"tags", "version"}
|
|
203
|
-
|
|
204
|
-
if key not in excluded_keys:
|
|
205
|
-
payload[key] = value
|
|
203
|
+
add_kwargs_to_payload(payload, kwargs, excluded_keys)
|
|
206
204
|
|
|
207
205
|
return payload
|
|
208
206
|
|
|
@@ -276,6 +274,9 @@ class ToolClient(BaseClient):
|
|
|
276
274
|
or getattr(current_tool, "type", None)
|
|
277
275
|
or DEFAULT_TOOL_TYPE
|
|
278
276
|
)
|
|
277
|
+
# Convert enum to string value for API payload
|
|
278
|
+
if hasattr(current_type, "value"):
|
|
279
|
+
current_type = current_type.value
|
|
279
280
|
|
|
280
281
|
update_data = {
|
|
281
282
|
"name": name if name is not None else current_tool.name,
|
|
@@ -432,15 +433,187 @@ class ToolClient(BaseClient):
|
|
|
432
433
|
except OSError:
|
|
433
434
|
pass # Ignore cleanup errors
|
|
434
435
|
|
|
435
|
-
def update_tool(self, tool_id: str, **kwargs) -> Tool:
|
|
436
|
-
"""Update an existing tool.
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
def update_tool(self, tool_id: str | Tool, **kwargs) -> Tool:
|
|
437
|
+
"""Update an existing tool.
|
|
438
|
+
|
|
439
|
+
Notes:
|
|
440
|
+
- Payload construction is centralized via ``_build_update_payload`` to keep metadata
|
|
441
|
+
update and upload update flows consistent.
|
|
442
|
+
- Accepts either a tool ID or a ``Tool`` instance (avoids an extra fetch when callers
|
|
443
|
+
already have the current tool).
|
|
444
|
+
"""
|
|
445
|
+
# Backward-compatible: allow passing a Tool instance to avoid an extra fetch.
|
|
446
|
+
if isinstance(tool_id, Tool):
|
|
447
|
+
current_tool = tool_id
|
|
448
|
+
if not current_tool.id:
|
|
449
|
+
raise ValueError("Tool instance has no id; cannot update.")
|
|
450
|
+
tool_id_value = str(current_tool.id)
|
|
451
|
+
else:
|
|
452
|
+
current_tool = None
|
|
453
|
+
tool_id_value = tool_id
|
|
454
|
+
|
|
455
|
+
if not kwargs:
|
|
456
|
+
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json={})
|
|
457
|
+
response = ToolResponse(**data)
|
|
458
|
+
return Tool.from_response(response, client=self)
|
|
459
|
+
|
|
460
|
+
if current_tool is None:
|
|
461
|
+
current_tool = self.get_tool_by_id(tool_id_value)
|
|
462
|
+
|
|
463
|
+
payload_kwargs = kwargs.copy()
|
|
464
|
+
name = payload_kwargs.pop("name", None)
|
|
465
|
+
description = payload_kwargs.pop("description", None)
|
|
466
|
+
update_payload = self._build_update_payload(
|
|
467
|
+
current_tool=current_tool,
|
|
468
|
+
name=name,
|
|
469
|
+
description=description,
|
|
470
|
+
**payload_kwargs,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json=update_payload)
|
|
474
|
+
response = ToolResponse(**data)
|
|
475
|
+
return Tool.from_response(response, client=self)
|
|
439
476
|
|
|
440
477
|
def delete_tool(self, tool_id: str) -> None:
|
|
441
478
|
"""Delete a tool."""
|
|
442
479
|
self._request("DELETE", f"{TOOLS_ENDPOINT}{tool_id}")
|
|
443
480
|
|
|
481
|
+
def upsert_tool(
|
|
482
|
+
self,
|
|
483
|
+
identifier: str | Tool,
|
|
484
|
+
code: str | None = None,
|
|
485
|
+
description: str | None = None,
|
|
486
|
+
framework: str = "langchain",
|
|
487
|
+
**kwargs,
|
|
488
|
+
) -> Tool:
|
|
489
|
+
"""Create or update a tool by instance, ID, or name.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
identifier: Tool instance, ID (UUID string), or name
|
|
493
|
+
code: Python code containing the tool plugin (required for create)
|
|
494
|
+
description: Tool description
|
|
495
|
+
framework: Tool framework (defaults to "langchain")
|
|
496
|
+
**kwargs: Additional parameters (tags, version, etc.)
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
The created or updated tool.
|
|
500
|
+
|
|
501
|
+
Example:
|
|
502
|
+
>>> # By name with code (creates if not exists)
|
|
503
|
+
>>> tool = client.tools.upsert_tool(
|
|
504
|
+
... "greeting",
|
|
505
|
+
... code=bundled_source,
|
|
506
|
+
... description="A greeting tool",
|
|
507
|
+
... )
|
|
508
|
+
>>> # By instance
|
|
509
|
+
>>> tool = client.tools.upsert_tool(existing_tool, code=new_code)
|
|
510
|
+
>>> # By ID
|
|
511
|
+
>>> tool = client.tools.upsert_tool("uuid-here", code=new_code)
|
|
512
|
+
"""
|
|
513
|
+
# Handle Tool instance
|
|
514
|
+
if isinstance(identifier, Tool):
|
|
515
|
+
if identifier.id:
|
|
516
|
+
logger.info("Updating tool by instance: %s", identifier.name)
|
|
517
|
+
return self._do_tool_upsert_update(
|
|
518
|
+
identifier.id,
|
|
519
|
+
identifier.name,
|
|
520
|
+
code,
|
|
521
|
+
description,
|
|
522
|
+
framework,
|
|
523
|
+
**kwargs,
|
|
524
|
+
)
|
|
525
|
+
identifier = identifier.name
|
|
526
|
+
|
|
527
|
+
# Handle string (ID or name)
|
|
528
|
+
if isinstance(identifier, str):
|
|
529
|
+
if is_uuid(identifier):
|
|
530
|
+
logger.info("Updating tool by ID: %s", identifier)
|
|
531
|
+
existing = self.get_tool_by_id(identifier)
|
|
532
|
+
return self._do_tool_upsert_update(identifier, existing.name, code, description, framework, **kwargs)
|
|
533
|
+
|
|
534
|
+
# It's a name - find or create
|
|
535
|
+
return self._upsert_tool_by_name(identifier, code, description, framework, **kwargs)
|
|
536
|
+
|
|
537
|
+
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
538
|
+
|
|
539
|
+
def _do_tool_upsert_update(
|
|
540
|
+
self,
|
|
541
|
+
tool_id: str,
|
|
542
|
+
name: str | None,
|
|
543
|
+
code: str | None,
|
|
544
|
+
description: str | None,
|
|
545
|
+
framework: str,
|
|
546
|
+
**kwargs,
|
|
547
|
+
) -> Tool:
|
|
548
|
+
"""Perform the update part of tool upsert."""
|
|
549
|
+
if code:
|
|
550
|
+
# Update via file upload
|
|
551
|
+
with tempfile.NamedTemporaryFile(
|
|
552
|
+
mode="w",
|
|
553
|
+
suffix=".py",
|
|
554
|
+
prefix=f"{name or 'tool'}_",
|
|
555
|
+
delete=False,
|
|
556
|
+
encoding="utf-8",
|
|
557
|
+
) as temp_file:
|
|
558
|
+
temp_file.write(code)
|
|
559
|
+
temp_file_path = temp_file.name
|
|
560
|
+
|
|
561
|
+
try:
|
|
562
|
+
return self.update_tool_via_file(
|
|
563
|
+
tool_id,
|
|
564
|
+
temp_file_path,
|
|
565
|
+
name=name,
|
|
566
|
+
description=description,
|
|
567
|
+
framework=framework,
|
|
568
|
+
**kwargs,
|
|
569
|
+
)
|
|
570
|
+
finally:
|
|
571
|
+
try:
|
|
572
|
+
os.unlink(temp_file_path)
|
|
573
|
+
except OSError:
|
|
574
|
+
pass
|
|
575
|
+
else:
|
|
576
|
+
# Metadata-only update
|
|
577
|
+
update_kwargs = {"framework": framework, **kwargs}
|
|
578
|
+
if name:
|
|
579
|
+
update_kwargs["name"] = name
|
|
580
|
+
if description:
|
|
581
|
+
update_kwargs["description"] = description
|
|
582
|
+
return self.update_tool(tool_id, **update_kwargs)
|
|
583
|
+
|
|
584
|
+
def _upsert_tool_by_name(
|
|
585
|
+
self,
|
|
586
|
+
name: str,
|
|
587
|
+
code: str | None,
|
|
588
|
+
description: str | None,
|
|
589
|
+
framework: str,
|
|
590
|
+
**kwargs,
|
|
591
|
+
) -> Tool:
|
|
592
|
+
"""Find tool by name and update, or create if not found."""
|
|
593
|
+
existing = self.find_tools(name)
|
|
594
|
+
name_lower = name.lower()
|
|
595
|
+
exact_matches = [tool for tool in existing if tool.name and tool.name.lower() == name_lower]
|
|
596
|
+
|
|
597
|
+
if len(exact_matches) == 1:
|
|
598
|
+
logger.info("Updating existing tool: %s", name)
|
|
599
|
+
return self._do_tool_upsert_update(exact_matches[0].id, name, code, description, framework, **kwargs)
|
|
600
|
+
|
|
601
|
+
if len(exact_matches) > 1:
|
|
602
|
+
raise ValueError(f"Multiple tools found with name '{name}'")
|
|
603
|
+
|
|
604
|
+
# Create new tool - code is required
|
|
605
|
+
if not code:
|
|
606
|
+
raise ValueError(f"Tool '{name}' not found and no code provided for creation")
|
|
607
|
+
|
|
608
|
+
logger.info("Creating new tool: %s", name)
|
|
609
|
+
return self.create_tool_from_code(
|
|
610
|
+
name=name,
|
|
611
|
+
code=code,
|
|
612
|
+
framework=framework,
|
|
613
|
+
description=description,
|
|
614
|
+
**kwargs,
|
|
615
|
+
)
|
|
616
|
+
|
|
444
617
|
def get_tool_script(self, tool_id: str) -> str:
|
|
445
618
|
"""Get the tool script content.
|
|
446
619
|
|
|
@@ -509,8 +682,9 @@ class ToolClient(BaseClient):
|
|
|
509
682
|
data=update_payload,
|
|
510
683
|
)
|
|
511
684
|
|
|
512
|
-
|
|
685
|
+
tool_response = ToolResponse(**response)
|
|
686
|
+
return Tool.from_response(tool_response, client=self)
|
|
513
687
|
|
|
514
688
|
except Exception as e:
|
|
515
|
-
logger.error(
|
|
689
|
+
logger.error("Failed to update tool %s via file: %s", tool_id, e)
|
|
516
690
|
raise
|
glaip_sdk/config/constants.py
CHANGED
|
@@ -4,8 +4,28 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
|
|
7
|
+
# Lazy import cache for DEFAULT_MODEL to avoid circular dependency
|
|
8
|
+
_DEFAULT_MODEL: str | None = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def __getattr__(name: str) -> str:
|
|
12
|
+
"""Lazy import DEFAULT_MODEL from models.constants to avoid circular dependency.
|
|
13
|
+
|
|
14
|
+
Note: Prefer importing DEFAULT_MODEL directly from glaip_sdk.models.constants
|
|
15
|
+
as it is the canonical source. This re-export exists for backward compatibility.
|
|
16
|
+
"""
|
|
17
|
+
if name in ("DEFAULT_MODEL", "SDK_DEFAULT_MODEL"):
|
|
18
|
+
global _DEFAULT_MODEL
|
|
19
|
+
if _DEFAULT_MODEL is None:
|
|
20
|
+
from glaip_sdk.models.constants import ( # noqa: PLC0415
|
|
21
|
+
DEFAULT_MODEL as _MODEL,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_DEFAULT_MODEL = _MODEL
|
|
25
|
+
return _DEFAULT_MODEL
|
|
26
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
27
|
+
|
|
28
|
+
|
|
9
29
|
DEFAULT_AGENT_RUN_TIMEOUT = 300
|
|
10
30
|
|
|
11
31
|
# User agent and version
|
|
@@ -39,3 +59,14 @@ DEFAULT_MCP_TRANSPORT = "stdio"
|
|
|
39
59
|
|
|
40
60
|
# Default error messages
|
|
41
61
|
DEFAULT_ERROR_MESSAGE = "Unknown error"
|
|
62
|
+
|
|
63
|
+
# Agent configuration fields used for CLI args and payload building
|
|
64
|
+
AGENT_CONFIG_FIELDS = (
|
|
65
|
+
"name",
|
|
66
|
+
"instruction",
|
|
67
|
+
"model",
|
|
68
|
+
"tools",
|
|
69
|
+
"agents",
|
|
70
|
+
"mcps",
|
|
71
|
+
"timeout",
|
|
72
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Guardrails package for content filtering and safety checks.
|
|
2
|
+
|
|
3
|
+
This package provides modular guardrail engines and managers for filtering
|
|
4
|
+
harmful content in AI agent interactions. All components support lazy loading
|
|
5
|
+
from aip-agents to maintain Principle VII compliance.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from enum import StrEnum
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from aip_agents.guardrails.engines.nemo import NemoGuardrailEngine
|
|
16
|
+
from aip_agents.guardrails.engines.phrase_matcher import PhraseMatcherEngine
|
|
17
|
+
from aip_agents.guardrails.manager import GuardrailManager
|
|
18
|
+
from aip_agents.guardrails.schemas import GuardrailMode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ImportableName(StrEnum):
|
|
22
|
+
"""Names of the importable attributes."""
|
|
23
|
+
|
|
24
|
+
GUARDRAIL_MANAGER = "GuardrailManager"
|
|
25
|
+
PHRASE_MATCHER_ENGINE = "PhraseMatcherEngine"
|
|
26
|
+
NEMO_GUARDRAIL_ENGINE = "NemoGuardrailEngine"
|
|
27
|
+
GUARDRAIL_MODE = "GuardrailMode"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Lazy loading support - components are only imported when actually used
|
|
31
|
+
_LAZY_IMPORTS = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str) -> Any:
|
|
35
|
+
"""Lazy import to avoid eager loading of optional aip-agents dependency.
|
|
36
|
+
|
|
37
|
+
This function is called by Python when an attribute is not found in the module.
|
|
38
|
+
It performs the import from aip_agents.guardrails at runtime.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: The name of the attribute to get.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The attribute value from aip_agents.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
AttributeError: If the attribute doesn't exist.
|
|
48
|
+
ImportError: If aip-agents is not installed but a component is accessed.
|
|
49
|
+
"""
|
|
50
|
+
if name in _LAZY_IMPORTS:
|
|
51
|
+
return _LAZY_IMPORTS[name]
|
|
52
|
+
|
|
53
|
+
if name == ImportableName.GUARDRAIL_MANAGER:
|
|
54
|
+
from aip_agents.guardrails.manager import GuardrailManager # noqa: PLC0415
|
|
55
|
+
|
|
56
|
+
_LAZY_IMPORTS[name] = GuardrailManager
|
|
57
|
+
return GuardrailManager
|
|
58
|
+
|
|
59
|
+
if name == ImportableName.PHRASE_MATCHER_ENGINE:
|
|
60
|
+
from aip_agents.guardrails.engines.phrase_matcher import ( # noqa: PLC0415
|
|
61
|
+
PhraseMatcherEngine,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_LAZY_IMPORTS[name] = PhraseMatcherEngine
|
|
65
|
+
return PhraseMatcherEngine
|
|
66
|
+
|
|
67
|
+
if name == ImportableName.NEMO_GUARDRAIL_ENGINE:
|
|
68
|
+
from aip_agents.guardrails.engines.nemo import NemoGuardrailEngine # noqa: PLC0415
|
|
69
|
+
|
|
70
|
+
_LAZY_IMPORTS[name] = NemoGuardrailEngine
|
|
71
|
+
return NemoGuardrailEngine
|
|
72
|
+
|
|
73
|
+
if name == ImportableName.GUARDRAIL_MODE:
|
|
74
|
+
from aip_agents.guardrails.schemas import GuardrailMode # noqa: PLC0415
|
|
75
|
+
|
|
76
|
+
_LAZY_IMPORTS[name] = GuardrailMode
|
|
77
|
+
return GuardrailMode
|
|
78
|
+
|
|
79
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
80
|
+
raise AttributeError(msg)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Guardrail serialization logic.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to serialize GuardrailManager and its engines
|
|
4
|
+
into the JSON format expected by the GL AIP backend. This keeps the serialization
|
|
5
|
+
logic within the SDK rather than polluting the core aip-agents logic.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from glaip_sdk.guardrails import (
|
|
17
|
+
GuardrailManager,
|
|
18
|
+
NemoGuardrailEngine,
|
|
19
|
+
PhraseMatcherEngine,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _serialize_phrase_matcher(engine: PhraseMatcherEngine) -> dict[str, Any]:
|
|
24
|
+
"""Serialize a PhraseMatcherEngine configuration."""
|
|
25
|
+
config: dict[str, Any] = {}
|
|
26
|
+
|
|
27
|
+
# Extract config from BaseGuardrailEngineConfig
|
|
28
|
+
if hasattr(engine, "config") and engine.config:
|
|
29
|
+
config.update(engine.config.model_dump())
|
|
30
|
+
|
|
31
|
+
# Extract specific fields
|
|
32
|
+
if hasattr(engine, "banned_phrases"):
|
|
33
|
+
config["banned_phrases"] = engine.banned_phrases
|
|
34
|
+
|
|
35
|
+
return config
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _serialize_nemo(engine: NemoGuardrailEngine) -> dict[str, Any]:
|
|
39
|
+
"""Serialize a NemoGuardrailEngine configuration."""
|
|
40
|
+
config: dict[str, Any] = {}
|
|
41
|
+
|
|
42
|
+
# Extract config from BaseGuardrailEngineConfig
|
|
43
|
+
if hasattr(engine, "config") and engine.config:
|
|
44
|
+
config.update(engine.config.model_dump())
|
|
45
|
+
|
|
46
|
+
# Extract specific fields
|
|
47
|
+
nemo_fields = [
|
|
48
|
+
"topic_safety_mode",
|
|
49
|
+
"allowed_topics",
|
|
50
|
+
"denied_topics",
|
|
51
|
+
"include_core_restrictions",
|
|
52
|
+
"core_restriction_categories",
|
|
53
|
+
"config_dict",
|
|
54
|
+
"denial_phrases",
|
|
55
|
+
]
|
|
56
|
+
for field in nemo_fields:
|
|
57
|
+
if hasattr(engine, field):
|
|
58
|
+
val = getattr(engine, field)
|
|
59
|
+
if val is not None:
|
|
60
|
+
config[field] = val
|
|
61
|
+
|
|
62
|
+
return config
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def serialize_guardrail_manager(manager: GuardrailManager) -> dict[str, Any]:
|
|
66
|
+
"""Serialize a GuardrailManager into the backend JSON format.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
manager: The GuardrailManager instance to serialize.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A dictionary matching the agent_config.guardrails schema.
|
|
73
|
+
"""
|
|
74
|
+
from glaip_sdk.guardrails import NemoGuardrailEngine, PhraseMatcherEngine # noqa: PLC0415
|
|
75
|
+
|
|
76
|
+
engines_config = []
|
|
77
|
+
|
|
78
|
+
if hasattr(manager, "engines"):
|
|
79
|
+
for engine in manager.engines:
|
|
80
|
+
if isinstance(engine, PhraseMatcherEngine):
|
|
81
|
+
engines_config.append({"type": "phrase_matcher", "config": _serialize_phrase_matcher(engine)})
|
|
82
|
+
elif isinstance(engine, NemoGuardrailEngine):
|
|
83
|
+
engines_config.append({"type": "nemo", "config": _serialize_nemo(engine)})
|
|
84
|
+
else:
|
|
85
|
+
# Fallback for unknown engines
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
enabled = getattr(manager, "enabled", True)
|
|
89
|
+
return {"enabled": enabled, "engines": engines_config}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Human-in-the-Loop (HITL) utilities for glaip-sdk.
|
|
2
|
+
|
|
3
|
+
This package provides utilities for HITL approval workflows in both local
|
|
4
|
+
and remote agent execution modes.
|
|
5
|
+
|
|
6
|
+
For local development, LocalPromptHandler is automatically injected when
|
|
7
|
+
agent_config.hitl_enabled is True. No manual setup required.
|
|
8
|
+
|
|
9
|
+
For remote execution, use RemoteHITLHandler to handle HITL events programmatically.
|
|
10
|
+
|
|
11
|
+
Authors:
|
|
12
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
13
|
+
GLAIP SDK Team
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
# These don't require aip_agents, so import them directly
|
|
19
|
+
from glaip_sdk.hitl.base import HITLCallback, HITLDecision, HITLRequest, HITLResponse
|
|
20
|
+
from glaip_sdk.hitl.callback import PauseResumeCallback
|
|
21
|
+
from glaip_sdk.hitl.remote import RemoteHITLHandler
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from glaip_sdk.hitl.local import LocalPromptHandler
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"LocalPromptHandler",
|
|
28
|
+
"PauseResumeCallback",
|
|
29
|
+
"HITLCallback",
|
|
30
|
+
"HITLDecision",
|
|
31
|
+
"HITLRequest",
|
|
32
|
+
"HITLResponse",
|
|
33
|
+
"RemoteHITLHandler",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def __getattr__(name: str) -> Any: # noqa: ANN401
|
|
38
|
+
"""Lazy import for LocalPromptHandler.
|
|
39
|
+
|
|
40
|
+
This defers the import of aip_agents until LocalPromptHandler is actually accessed,
|
|
41
|
+
preventing ImportError when aip-agents is not installed but HITL is not being used.
|
|
42
|
+
"""
|
|
43
|
+
if name == "LocalPromptHandler":
|
|
44
|
+
from glaip_sdk.hitl.local import LocalPromptHandler # noqa: PLC0415
|
|
45
|
+
|
|
46
|
+
globals()["LocalPromptHandler"] = LocalPromptHandler
|
|
47
|
+
return LocalPromptHandler
|
|
48
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
glaip_sdk/hitl/base.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Base types for HITL approval handling.
|
|
3
|
+
|
|
4
|
+
Authors:
|
|
5
|
+
GLAIP SDK Team
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Protocol, runtime_checkable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HITLDecision(str, Enum):
|
|
14
|
+
"""HITL decision types."""
|
|
15
|
+
|
|
16
|
+
APPROVED = "approved"
|
|
17
|
+
REJECTED = "rejected"
|
|
18
|
+
SKIPPED = "skipped"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class HITLRequest:
|
|
23
|
+
"""HITL approval request from SSE stream."""
|
|
24
|
+
|
|
25
|
+
request_id: str
|
|
26
|
+
tool_name: str
|
|
27
|
+
tool_args: dict[str, Any]
|
|
28
|
+
timeout_at: str # ISO 8601, authoritative deadline
|
|
29
|
+
timeout_seconds: int # Informational, fallback only
|
|
30
|
+
|
|
31
|
+
# Raw metadata for advanced use cases
|
|
32
|
+
hitl_metadata: dict[str, Any]
|
|
33
|
+
tool_metadata: dict[str, Any]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class HITLResponse:
|
|
38
|
+
"""HITL decision response."""
|
|
39
|
+
|
|
40
|
+
decision: HITLDecision
|
|
41
|
+
operator_input: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@runtime_checkable
|
|
45
|
+
class HITLCallback(Protocol):
|
|
46
|
+
"""Protocol for HITL approval callbacks.
|
|
47
|
+
|
|
48
|
+
Callbacks should complete within the computed callback timeout.
|
|
49
|
+
Callbacks should handle exceptions internally or let them propagate.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __call__(self, request: HITLRequest) -> HITLResponse:
|
|
53
|
+
"""Handle HITL approval request.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
request: HITL request with tool info and metadata
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
HITLResponse with decision and optional operator input
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
Any exception will be caught, logged, and treated as REJECTED.
|
|
63
|
+
"""
|
|
64
|
+
...
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Pause/resume callback for HITL renderer control.
|
|
2
|
+
|
|
3
|
+
This module provides PauseResumeCallback which allows HITL prompt handlers
|
|
4
|
+
to control the live renderer without directly coupling to the renderer implementation.
|
|
5
|
+
|
|
6
|
+
Author:
|
|
7
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PauseResumeCallback:
|
|
14
|
+
"""Simple callback object for pausing/resuming the live renderer.
|
|
15
|
+
|
|
16
|
+
This allows the LocalPromptHandler to control the renderer without
|
|
17
|
+
directly coupling to the renderer implementation.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
"""Initialize the callback."""
|
|
22
|
+
self._renderer: Any | None = None
|
|
23
|
+
|
|
24
|
+
def set_renderer(self, renderer: Any) -> None:
|
|
25
|
+
"""Set the renderer instance.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
renderer: RichStreamRenderer instance with pause_live() and resume_live() methods.
|
|
29
|
+
"""
|
|
30
|
+
self._renderer = renderer
|
|
31
|
+
|
|
32
|
+
def pause(self) -> None:
|
|
33
|
+
"""Pause the live renderer before prompting."""
|
|
34
|
+
if self._renderer and hasattr(self._renderer, "_shutdown_live"):
|
|
35
|
+
self._renderer._shutdown_live()
|
|
36
|
+
|
|
37
|
+
def resume(self) -> None:
|
|
38
|
+
"""Resume the live renderer after prompting."""
|
|
39
|
+
if self._renderer and hasattr(self._renderer, "_ensure_live"):
|
|
40
|
+
self._renderer._ensure_live()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["PauseResumeCallback"]
|