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,198 @@
|
|
|
1
|
+
"""Validation utilities for AIP SDK.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from glaip_sdk.exceptions import AmbiguousResourceError, NotFoundError, ValidationError
|
|
11
|
+
from glaip_sdk.models import Tool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ResourceValidator:
|
|
15
|
+
"""Validates and resolves resource references."""
|
|
16
|
+
|
|
17
|
+
RESERVED_NAMES = {
|
|
18
|
+
"research-agent",
|
|
19
|
+
"github-agent",
|
|
20
|
+
"aws-pricing-filter-generator-agent",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def is_reserved_name(cls, name: str) -> bool:
|
|
25
|
+
"""Check if a name is reserved."""
|
|
26
|
+
return name in cls.RESERVED_NAMES
|
|
27
|
+
|
|
28
|
+
def _is_uuid_string(self, value: str) -> bool:
|
|
29
|
+
"""Check if a string is a valid UUID."""
|
|
30
|
+
try:
|
|
31
|
+
UUID(value)
|
|
32
|
+
return True
|
|
33
|
+
except ValueError:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
def _resolve_tool_by_name(self, tool_name: str, client: Any) -> str:
|
|
37
|
+
"""Resolve tool name to ID."""
|
|
38
|
+
found_tools = client.find_tools(name=tool_name)
|
|
39
|
+
if len(found_tools) == 1:
|
|
40
|
+
return str(found_tools[0].id)
|
|
41
|
+
elif len(found_tools) > 1:
|
|
42
|
+
raise AmbiguousResourceError(f"Multiple tools found with name '{tool_name}': {[t.id for t in found_tools]}")
|
|
43
|
+
else:
|
|
44
|
+
raise NotFoundError(f"Tool not found: {tool_name}")
|
|
45
|
+
|
|
46
|
+
def _resolve_tool_by_name_attribute(self, tool: Tool, client: Any) -> str:
|
|
47
|
+
"""Resolve tool by name attribute."""
|
|
48
|
+
found_tools = client.find_tools(name=tool.name)
|
|
49
|
+
if len(found_tools) == 1:
|
|
50
|
+
return str(found_tools[0].id)
|
|
51
|
+
elif len(found_tools) > 1:
|
|
52
|
+
raise AmbiguousResourceError(f"Multiple tools found with name '{tool.name}': {[t.id for t in found_tools]}")
|
|
53
|
+
else:
|
|
54
|
+
raise NotFoundError(f"Tool not found: {tool.name}")
|
|
55
|
+
|
|
56
|
+
def _process_tool_string(self, tool: str, client: Any) -> str:
|
|
57
|
+
"""Process a string tool reference."""
|
|
58
|
+
if self._is_uuid_string(tool):
|
|
59
|
+
return tool # Already a UUID string
|
|
60
|
+
else:
|
|
61
|
+
return self._resolve_tool_by_name(tool, client)
|
|
62
|
+
|
|
63
|
+
def _process_tool_object(self, tool: Tool, client: Any) -> str:
|
|
64
|
+
"""Process a Tool object reference."""
|
|
65
|
+
if hasattr(tool, "id") and tool.id is not None:
|
|
66
|
+
return str(tool.id)
|
|
67
|
+
elif isinstance(tool, UUID):
|
|
68
|
+
return str(tool)
|
|
69
|
+
elif hasattr(tool, "name") and tool.name is not None:
|
|
70
|
+
return self._resolve_tool_by_name_attribute(tool, client)
|
|
71
|
+
else:
|
|
72
|
+
raise ValidationError(f"Invalid tool reference: {tool} - must have 'id' or 'name' attribute")
|
|
73
|
+
|
|
74
|
+
def _process_single_tool(self, tool: str | Tool, client: Any) -> str:
|
|
75
|
+
"""Process a single tool reference and return its ID."""
|
|
76
|
+
if isinstance(tool, str):
|
|
77
|
+
return self._process_tool_string(tool, client)
|
|
78
|
+
else:
|
|
79
|
+
return self._process_tool_object(tool, client)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def extract_tool_ids(cls, tools: list[str | Tool], client: Any) -> list[str]:
|
|
83
|
+
"""Extract tool IDs from a list of tool names, IDs, or Tool objects.
|
|
84
|
+
|
|
85
|
+
For agent creation, the backend expects tool IDs (UUIDs).
|
|
86
|
+
This method handles:
|
|
87
|
+
- Tool objects (extracts their ID)
|
|
88
|
+
- UUID strings (passes through)
|
|
89
|
+
- Tool names (finds tool and extracts ID)
|
|
90
|
+
"""
|
|
91
|
+
tool_ids = []
|
|
92
|
+
for tool in tools:
|
|
93
|
+
try:
|
|
94
|
+
tool_id = cls()._process_single_tool(tool, client)
|
|
95
|
+
tool_ids.append(tool_id)
|
|
96
|
+
except (AmbiguousResourceError, NotFoundError) as err:
|
|
97
|
+
# Determine the tool name for the error message
|
|
98
|
+
tool_name = tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
|
|
99
|
+
raise ValidationError(f"Failed to resolve tool name '{tool_name}' to ID: {err}") from err
|
|
100
|
+
except Exception as err:
|
|
101
|
+
# For other exceptions, wrap them appropriately
|
|
102
|
+
tool_name = tool if isinstance(tool, str) else getattr(tool, "name", str(tool))
|
|
103
|
+
raise ValidationError(f"Failed to resolve tool name '{tool_name}' to ID: {err}") from err
|
|
104
|
+
|
|
105
|
+
return tool_ids
|
|
106
|
+
|
|
107
|
+
def _resolve_agent_by_name(self, agent_name: str, client: Any) -> str:
|
|
108
|
+
"""Resolve agent name to ID."""
|
|
109
|
+
found_agents = client.find_agents(name=agent_name)
|
|
110
|
+
if len(found_agents) == 1:
|
|
111
|
+
return str(found_agents[0].id)
|
|
112
|
+
elif len(found_agents) > 1:
|
|
113
|
+
raise AmbiguousResourceError(
|
|
114
|
+
f"Multiple agents found with name '{agent_name}': {[a.id for a in found_agents]}"
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
raise NotFoundError(f"Agent not found: {agent_name}")
|
|
118
|
+
|
|
119
|
+
def _resolve_agent_by_name_attribute(self, agent: Any, client: Any) -> str:
|
|
120
|
+
"""Resolve agent by name attribute."""
|
|
121
|
+
found_agents = client.find_agents(name=agent.name)
|
|
122
|
+
if len(found_agents) == 1:
|
|
123
|
+
return str(found_agents[0].id)
|
|
124
|
+
elif len(found_agents) > 1:
|
|
125
|
+
raise AmbiguousResourceError(
|
|
126
|
+
f"Multiple agents found with name '{agent.name}': {[a.id for a in found_agents]}"
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
raise NotFoundError(f"Agent not found: {agent.name}")
|
|
130
|
+
|
|
131
|
+
def _process_agent_string(self, agent: str, client: Any) -> str:
|
|
132
|
+
"""Process a string agent reference."""
|
|
133
|
+
if self._is_uuid_string(agent):
|
|
134
|
+
return agent # Already a UUID string
|
|
135
|
+
else:
|
|
136
|
+
return self._resolve_agent_by_name(agent, client)
|
|
137
|
+
|
|
138
|
+
def _process_agent_object(self, agent: Any, client: Any) -> str:
|
|
139
|
+
"""Process an Agent object reference."""
|
|
140
|
+
if hasattr(agent, "id") and agent.id is not None:
|
|
141
|
+
return str(agent.id)
|
|
142
|
+
elif isinstance(agent, UUID):
|
|
143
|
+
return str(agent)
|
|
144
|
+
elif hasattr(agent, "name") and agent.name is not None:
|
|
145
|
+
return self._resolve_agent_by_name_attribute(agent, client)
|
|
146
|
+
else:
|
|
147
|
+
raise ValidationError(f"Invalid agent reference: {agent} - must have 'id' or 'name' attribute")
|
|
148
|
+
|
|
149
|
+
def _process_single_agent(self, agent: str | Any, client: Any) -> str:
|
|
150
|
+
"""Process a single agent reference and return its ID."""
|
|
151
|
+
if isinstance(agent, str):
|
|
152
|
+
return self._process_agent_string(agent, client)
|
|
153
|
+
else:
|
|
154
|
+
return self._process_agent_object(agent, client)
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def extract_agent_ids(cls, agents: list[str | Any], client: Any) -> list[str]:
|
|
158
|
+
"""Extract agent IDs from a list of agent names, IDs, or agent objects.
|
|
159
|
+
|
|
160
|
+
For agent creation, the backend expects agent IDs (UUIDs).
|
|
161
|
+
This method handles:
|
|
162
|
+
- Agent objects (extracts their ID)
|
|
163
|
+
- UUID strings (passes through)
|
|
164
|
+
- Agent names (finds agent and extracts ID)
|
|
165
|
+
"""
|
|
166
|
+
agent_ids = []
|
|
167
|
+
for agent in agents:
|
|
168
|
+
try:
|
|
169
|
+
agent_id = cls()._process_single_agent(agent, client)
|
|
170
|
+
agent_ids.append(agent_id)
|
|
171
|
+
except (AmbiguousResourceError, NotFoundError) as err:
|
|
172
|
+
# Determine the agent name for the error message
|
|
173
|
+
agent_name = agent if isinstance(agent, str) else getattr(agent, "name", str(agent))
|
|
174
|
+
raise ValidationError(f"Failed to resolve agent name '{agent_name}' to ID: {err}") from err
|
|
175
|
+
except Exception as err:
|
|
176
|
+
# For other exceptions, wrap them appropriately
|
|
177
|
+
agent_name = agent if isinstance(agent, str) else getattr(agent, "name", str(agent))
|
|
178
|
+
raise ValidationError(f"Failed to resolve agent name '{agent_name}' to ID: {err}") from err
|
|
179
|
+
|
|
180
|
+
return agent_ids
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def validate_tools_exist(cls, tool_ids: list[str], client: Any) -> None:
|
|
184
|
+
"""Validate that all tool IDs exist."""
|
|
185
|
+
for tool_id in tool_ids:
|
|
186
|
+
try:
|
|
187
|
+
client.get_tool_by_id(tool_id)
|
|
188
|
+
except NotFoundError as err:
|
|
189
|
+
raise ValidationError(f"Tool not found: {tool_id}") from err
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def validate_agents_exist(cls, agent_ids: list[str], client: Any) -> None:
|
|
193
|
+
"""Validate that all agent IDs exist."""
|
|
194
|
+
for agent_id in agent_ids:
|
|
195
|
+
try:
|
|
196
|
+
client.get_agent_by_id(agent_id)
|
|
197
|
+
except NotFoundError as err:
|
|
198
|
+
raise ValidationError(f"Agent not found: {agent_id}") from err
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Configuration constants for the AIP SDK.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Default language model configuration
|
|
8
|
+
DEFAULT_MODEL = "gpt-5-nano"
|
|
9
|
+
DEFAULT_AGENT_RUN_TIMEOUT = 300
|
|
10
|
+
|
|
11
|
+
# User agent and version
|
|
12
|
+
SDK_NAME = "glaip-sdk"
|
|
13
|
+
|
|
14
|
+
# Reserved names that cannot be used for agents/tools
|
|
15
|
+
RESERVED_NAMES = {
|
|
16
|
+
"system",
|
|
17
|
+
"admin",
|
|
18
|
+
"root",
|
|
19
|
+
"test",
|
|
20
|
+
"example",
|
|
21
|
+
"demo",
|
|
22
|
+
"sample",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Agent creation/update constants
|
|
26
|
+
DEFAULT_AGENT_TYPE = "config"
|
|
27
|
+
DEFAULT_AGENT_FRAMEWORK = "langchain"
|
|
28
|
+
DEFAULT_AGENT_VERSION = "1.0"
|
|
29
|
+
DEFAULT_AGENT_PROVIDER = "openai"
|
|
30
|
+
|
|
31
|
+
# Tool creation/update constants
|
|
32
|
+
DEFAULT_TOOL_TYPE = "custom"
|
|
33
|
+
DEFAULT_TOOL_FRAMEWORK = "langchain"
|
|
34
|
+
DEFAULT_TOOL_VERSION = "1.0"
|
|
35
|
+
|
|
36
|
+
# MCP creation/update constants
|
|
37
|
+
DEFAULT_MCP_TYPE = "server"
|
|
38
|
+
DEFAULT_MCP_TRANSPORT = "stdio"
|
|
39
|
+
|
|
40
|
+
# Default error messages
|
|
41
|
+
DEFAULT_ERROR_MESSAGE = "Unknown error"
|
|
42
|
+
|
|
43
|
+
# Agent configuration fields used for CLI args and payload building
|
|
44
|
+
AGENT_CONFIG_FIELDS = (
|
|
45
|
+
"name",
|
|
46
|
+
"instruction",
|
|
47
|
+
"model",
|
|
48
|
+
"tools",
|
|
49
|
+
"agents",
|
|
50
|
+
"mcps",
|
|
51
|
+
"timeout",
|
|
52
|
+
)
|
glaip_sdk/exceptions.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Custom exceptions for AIP SDK.
|
|
3
|
+
|
|
4
|
+
Authors:
|
|
5
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AIPError(Exception):
|
|
12
|
+
"""Base exception for AIP SDK."""
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class APIError(AIPError):
|
|
18
|
+
"""Base API exception with rich context."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
message: str,
|
|
23
|
+
*,
|
|
24
|
+
status_code: int | None = None,
|
|
25
|
+
error_type: str | None = None,
|
|
26
|
+
payload: Any = None,
|
|
27
|
+
request_id: str | None = None,
|
|
28
|
+
):
|
|
29
|
+
"""Initialize the API error.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
message: The error message
|
|
33
|
+
status_code: HTTP status code
|
|
34
|
+
error_type: Type of error
|
|
35
|
+
payload: Additional error payload
|
|
36
|
+
request_id: Request identifier
|
|
37
|
+
"""
|
|
38
|
+
super().__init__(message)
|
|
39
|
+
self.status_code = status_code
|
|
40
|
+
self.error_type = error_type
|
|
41
|
+
self.payload = payload
|
|
42
|
+
self.request_id = request_id
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AuthenticationError(APIError):
|
|
46
|
+
"""Authentication failed."""
|
|
47
|
+
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ValidationError(APIError):
|
|
52
|
+
"""Validation failed."""
|
|
53
|
+
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ForbiddenError(APIError):
|
|
58
|
+
"""Access forbidden."""
|
|
59
|
+
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class NotFoundError(APIError):
|
|
64
|
+
"""Resource not found."""
|
|
65
|
+
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ConflictError(APIError):
|
|
70
|
+
"""Resource conflict."""
|
|
71
|
+
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AmbiguousResourceError(APIError):
|
|
76
|
+
"""Multiple resources match the query."""
|
|
77
|
+
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ServerError(APIError):
|
|
82
|
+
"""Server error."""
|
|
83
|
+
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class RateLimitError(APIError):
|
|
88
|
+
"""Rate limit exceeded."""
|
|
89
|
+
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TimeoutError(APIError):
|
|
94
|
+
"""Request timeout."""
|
|
95
|
+
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AgentTimeoutError(TimeoutError):
|
|
100
|
+
"""Agent execution timeout with specific duration information."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, timeout_seconds: float, agent_name: str = None):
|
|
103
|
+
"""Initialize the agent timeout error.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
timeout_seconds: The timeout duration in seconds
|
|
107
|
+
agent_name: Optional name of the agent that timed out
|
|
108
|
+
"""
|
|
109
|
+
agent_info = f" for agent '{agent_name}'" if agent_name else ""
|
|
110
|
+
message = f"Agent execution timed out after {timeout_seconds} seconds{agent_info}"
|
|
111
|
+
super().__init__(message)
|
|
112
|
+
self.timeout_seconds = timeout_seconds
|
|
113
|
+
self.agent_name = agent_name
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
Authors:
|
|
10
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from glaip_sdk.hitl.local import LocalPromptHandler, PauseResumeCallback
|
|
14
|
+
|
|
15
|
+
__all__ = ["LocalPromptHandler", "PauseResumeCallback"]
|
glaip_sdk/hitl/local.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Local HITL prompt handler with interactive console support.
|
|
2
|
+
|
|
3
|
+
Author:
|
|
4
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from aip_agents.agent.hitl.prompt.base import BasePromptHandler
|
|
12
|
+
from aip_agents.schema.hitl import ApprovalDecision, ApprovalDecisionType, ApprovalRequest
|
|
13
|
+
except ImportError as e:
|
|
14
|
+
raise ImportError("aip_agents is required for local HITL. Install with: pip install 'glaip-sdk[local]'") from e
|
|
15
|
+
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.prompt import Prompt
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LocalPromptHandler(BasePromptHandler):
|
|
21
|
+
"""Local HITL prompt handler with interactive console prompts.
|
|
22
|
+
|
|
23
|
+
Experimental local HITL implementation with known limitations:
|
|
24
|
+
- Timeouts are not enforced (interactive prompts wait indefinitely)
|
|
25
|
+
- Relies on private renderer methods for pause/resume
|
|
26
|
+
- Only supports interactive terminal environments
|
|
27
|
+
|
|
28
|
+
The key insight from Rich documentation is that Live must be stopped before
|
|
29
|
+
using Prompt/input(), otherwise the input won't render properly.
|
|
30
|
+
|
|
31
|
+
Environment variables:
|
|
32
|
+
GLAIP_HITL_AUTO_APPROVE: Set to "true" (case-insensitive) to auto-approve
|
|
33
|
+
all requests without user interaction. Useful for integration tests and CI.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, *, pause_resume_callback: Any | None = None) -> None:
|
|
37
|
+
"""Initialize the prompt handler.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
pause_resume_callback: Optional callable with pause() and resume() methods
|
|
41
|
+
to control the live renderer during prompts. This is needed because
|
|
42
|
+
Rich Live interferes with Prompt/input().
|
|
43
|
+
"""
|
|
44
|
+
super().__init__()
|
|
45
|
+
self._pause_resume = pause_resume_callback
|
|
46
|
+
self._console = Console()
|
|
47
|
+
|
|
48
|
+
async def prompt_for_decision(
|
|
49
|
+
self,
|
|
50
|
+
request: ApprovalRequest,
|
|
51
|
+
timeout_seconds: int,
|
|
52
|
+
context_keys: list[str] | None = None,
|
|
53
|
+
) -> ApprovalDecision:
|
|
54
|
+
"""Prompt for approval decision with live renderer pause/resume.
|
|
55
|
+
|
|
56
|
+
Supports auto-approval via GLAIP_HITL_AUTO_APPROVE environment variable
|
|
57
|
+
for integration testing and CI environments. Set to "true" (case-insensitive) to enable.
|
|
58
|
+
"""
|
|
59
|
+
_ = (timeout_seconds, context_keys) # Suppress unused parameter warnings.
|
|
60
|
+
|
|
61
|
+
# Check for auto-approve mode (for integration tests/CI)
|
|
62
|
+
auto_approve = os.getenv("GLAIP_HITL_AUTO_APPROVE", "").lower() == "true"
|
|
63
|
+
|
|
64
|
+
if auto_approve:
|
|
65
|
+
# Auto-approve without user interaction
|
|
66
|
+
return ApprovalDecision(
|
|
67
|
+
request_id=request.request_id,
|
|
68
|
+
decision=ApprovalDecisionType.APPROVED,
|
|
69
|
+
operator_input="auto-approved",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Pause the live renderer if callback is available
|
|
73
|
+
if self._pause_resume:
|
|
74
|
+
self._pause_resume.pause()
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
# POC/MVP: Show what we're approving (still auto-approve for now)
|
|
78
|
+
self._print_request_info(request)
|
|
79
|
+
|
|
80
|
+
# POC/MVP: For testing, we can do actual input here
|
|
81
|
+
# Uncomment to enable real prompting:
|
|
82
|
+
response = Prompt.ask(
|
|
83
|
+
"\n[yellow]Approve this tool call?[/yellow] [dim](y/n/s)[/dim]",
|
|
84
|
+
console=self._console,
|
|
85
|
+
default="y",
|
|
86
|
+
)
|
|
87
|
+
response = response.lower().strip()
|
|
88
|
+
|
|
89
|
+
if response in ("y", "yes"):
|
|
90
|
+
decision = ApprovalDecisionType.APPROVED
|
|
91
|
+
elif response in ("n", "no"):
|
|
92
|
+
decision = ApprovalDecisionType.REJECTED
|
|
93
|
+
else:
|
|
94
|
+
decision = ApprovalDecisionType.SKIPPED
|
|
95
|
+
|
|
96
|
+
return ApprovalDecision(
|
|
97
|
+
request_id=request.request_id,
|
|
98
|
+
decision=decision,
|
|
99
|
+
operator_input=response if decision != ApprovalDecisionType.SKIPPED else None,
|
|
100
|
+
)
|
|
101
|
+
finally:
|
|
102
|
+
# Always resume the live renderer
|
|
103
|
+
if self._pause_resume:
|
|
104
|
+
self._pause_resume.resume()
|
|
105
|
+
|
|
106
|
+
def _print_request_info(self, request: ApprovalRequest) -> None:
|
|
107
|
+
"""Print the approval request information."""
|
|
108
|
+
self._console.print()
|
|
109
|
+
self._console.rule("[yellow]HITL Approval Request[/yellow]", style="yellow")
|
|
110
|
+
|
|
111
|
+
tool_name = request.tool_name or "unknown"
|
|
112
|
+
self._console.print(f"[cyan]Tool:[/cyan] {tool_name}")
|
|
113
|
+
|
|
114
|
+
if hasattr(request, "arguments_preview") and request.arguments_preview:
|
|
115
|
+
self._console.print(f"[cyan]Arguments:[/cyan] {request.arguments_preview}")
|
|
116
|
+
|
|
117
|
+
if request.context:
|
|
118
|
+
self._console.print(f"[dim]Context: {request.context}[/dim]")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class PauseResumeCallback:
|
|
122
|
+
"""Simple callback object for pausing/resuming the live renderer.
|
|
123
|
+
|
|
124
|
+
This allows the LocalPromptHandler to control the renderer without
|
|
125
|
+
directly coupling to the renderer implementation.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(self) -> None:
|
|
129
|
+
"""Initialize the callback."""
|
|
130
|
+
self._renderer: Any | None = None
|
|
131
|
+
|
|
132
|
+
def set_renderer(self, renderer: Any) -> None:
|
|
133
|
+
"""Set the renderer instance.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
renderer: RichStreamRenderer instance with pause_live() and resume_live() methods.
|
|
137
|
+
"""
|
|
138
|
+
self._renderer = renderer
|
|
139
|
+
|
|
140
|
+
def pause(self) -> None:
|
|
141
|
+
"""Pause the live renderer before prompting."""
|
|
142
|
+
if self._renderer and hasattr(self._renderer, "_shutdown_live"):
|
|
143
|
+
self._renderer._shutdown_live()
|
|
144
|
+
|
|
145
|
+
def resume(self) -> None:
|
|
146
|
+
"""Resume the live renderer after prompting."""
|
|
147
|
+
if self._renderer and hasattr(self._renderer, "_ensure_live"):
|
|
148
|
+
self._renderer._ensure_live()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = ["LocalPromptHandler", "PauseResumeCallback"]
|
glaip_sdk/icons.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Lightweight icon definitions used across the CLI.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
ICON_AGENT = "🤖"
|
|
8
|
+
ICON_AGENT_STEP = "🤖"
|
|
9
|
+
ICON_TOOL = "🔧"
|
|
10
|
+
ICON_TOOL_STEP = "🔧"
|
|
11
|
+
ICON_DELEGATE = ICON_AGENT_STEP
|
|
12
|
+
ICON_STATUS_SUCCESS = "✓"
|
|
13
|
+
ICON_STATUS_FAILED = "✗"
|
|
14
|
+
ICON_STATUS_WARNING = "âš "
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ICON_AGENT",
|
|
18
|
+
"ICON_AGENT_STEP",
|
|
19
|
+
"ICON_TOOL",
|
|
20
|
+
"ICON_TOOL_STEP",
|
|
21
|
+
"ICON_DELEGATE",
|
|
22
|
+
"ICON_STATUS_SUCCESS",
|
|
23
|
+
"ICON_STATUS_FAILED",
|
|
24
|
+
"ICON_STATUS_WARNING",
|
|
25
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) package for GL AIP platform.
|
|
2
|
+
|
|
3
|
+
This package provides the MCP class and MCPRegistry for managing
|
|
4
|
+
Model Context Protocol configurations on the GL AIP platform.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from glaip_sdk.mcps import MCP, get_mcp_registry
|
|
8
|
+
>>> mcp = MCP.from_native("arxiv-search")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from glaip_sdk.mcps.base import MCP, MCPConfigValue
|
|
14
|
+
from glaip_sdk.registry.mcp import MCPRegistry, get_mcp_registry
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"MCP",
|
|
18
|
+
"MCPConfigValue",
|
|
19
|
+
"MCPRegistry",
|
|
20
|
+
"get_mcp_registry",
|
|
21
|
+
]
|