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.
Files changed (207) hide show
  1. glaip_sdk/__init__.py +52 -0
  2. glaip_sdk/_version.py +81 -0
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1227 -0
  5. glaip_sdk/branding.py +211 -0
  6. glaip_sdk/cli/__init__.py +9 -0
  7. glaip_sdk/cli/account_store.py +540 -0
  8. glaip_sdk/cli/agent_config.py +78 -0
  9. glaip_sdk/cli/auth.py +705 -0
  10. glaip_sdk/cli/commands/__init__.py +5 -0
  11. glaip_sdk/cli/commands/accounts.py +746 -0
  12. glaip_sdk/cli/commands/agents/__init__.py +119 -0
  13. glaip_sdk/cli/commands/agents/_common.py +561 -0
  14. glaip_sdk/cli/commands/agents/create.py +151 -0
  15. glaip_sdk/cli/commands/agents/delete.py +64 -0
  16. glaip_sdk/cli/commands/agents/get.py +89 -0
  17. glaip_sdk/cli/commands/agents/list.py +129 -0
  18. glaip_sdk/cli/commands/agents/run.py +264 -0
  19. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  20. glaip_sdk/cli/commands/agents/update.py +112 -0
  21. glaip_sdk/cli/commands/common_config.py +104 -0
  22. glaip_sdk/cli/commands/configure.py +895 -0
  23. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  24. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  25. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  26. glaip_sdk/cli/commands/mcps/create.py +152 -0
  27. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  28. glaip_sdk/cli/commands/mcps/get.py +212 -0
  29. glaip_sdk/cli/commands/mcps/list.py +69 -0
  30. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  31. glaip_sdk/cli/commands/mcps/update.py +190 -0
  32. glaip_sdk/cli/commands/models.py +67 -0
  33. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  34. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  35. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  36. glaip_sdk/cli/commands/tools/_common.py +80 -0
  37. glaip_sdk/cli/commands/tools/create.py +228 -0
  38. glaip_sdk/cli/commands/tools/delete.py +61 -0
  39. glaip_sdk/cli/commands/tools/get.py +103 -0
  40. glaip_sdk/cli/commands/tools/list.py +69 -0
  41. glaip_sdk/cli/commands/tools/script.py +49 -0
  42. glaip_sdk/cli/commands/tools/update.py +102 -0
  43. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  44. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  45. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  46. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  47. glaip_sdk/cli/commands/transcripts_original.py +756 -0
  48. glaip_sdk/cli/commands/update.py +192 -0
  49. glaip_sdk/cli/config.py +95 -0
  50. glaip_sdk/cli/constants.py +38 -0
  51. glaip_sdk/cli/context.py +150 -0
  52. glaip_sdk/cli/core/__init__.py +79 -0
  53. glaip_sdk/cli/core/context.py +124 -0
  54. glaip_sdk/cli/core/output.py +851 -0
  55. glaip_sdk/cli/core/prompting.py +649 -0
  56. glaip_sdk/cli/core/rendering.py +187 -0
  57. glaip_sdk/cli/display.py +355 -0
  58. glaip_sdk/cli/hints.py +57 -0
  59. glaip_sdk/cli/io.py +112 -0
  60. glaip_sdk/cli/main.py +686 -0
  61. glaip_sdk/cli/masking.py +136 -0
  62. glaip_sdk/cli/mcp_validators.py +287 -0
  63. glaip_sdk/cli/pager.py +266 -0
  64. glaip_sdk/cli/parsers/__init__.py +7 -0
  65. glaip_sdk/cli/parsers/json_input.py +177 -0
  66. glaip_sdk/cli/resolution.py +68 -0
  67. glaip_sdk/cli/rich_helpers.py +27 -0
  68. glaip_sdk/cli/slash/__init__.py +15 -0
  69. glaip_sdk/cli/slash/accounts_controller.py +580 -0
  70. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  71. glaip_sdk/cli/slash/agent_session.py +285 -0
  72. glaip_sdk/cli/slash/prompt.py +256 -0
  73. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  74. glaip_sdk/cli/slash/session.py +1724 -0
  75. glaip_sdk/cli/slash/tui/__init__.py +34 -0
  76. glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
  77. glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
  78. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  79. glaip_sdk/cli/slash/tui/clipboard.py +147 -0
  80. glaip_sdk/cli/slash/tui/context.py +59 -0
  81. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  82. glaip_sdk/cli/slash/tui/loading.py +58 -0
  83. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  84. glaip_sdk/cli/slash/tui/terminal.py +402 -0
  85. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  86. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  87. glaip_sdk/cli/slash/tui/theme/manager.py +86 -0
  88. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  89. glaip_sdk/cli/slash/tui/toast.py +123 -0
  90. glaip_sdk/cli/transcript/__init__.py +31 -0
  91. glaip_sdk/cli/transcript/cache.py +536 -0
  92. glaip_sdk/cli/transcript/capture.py +329 -0
  93. glaip_sdk/cli/transcript/export.py +38 -0
  94. glaip_sdk/cli/transcript/history.py +815 -0
  95. glaip_sdk/cli/transcript/launcher.py +77 -0
  96. glaip_sdk/cli/transcript/viewer.py +374 -0
  97. glaip_sdk/cli/update_notifier.py +369 -0
  98. glaip_sdk/cli/validators.py +238 -0
  99. glaip_sdk/client/__init__.py +12 -0
  100. glaip_sdk/client/_schedule_payloads.py +89 -0
  101. glaip_sdk/client/agent_runs.py +147 -0
  102. glaip_sdk/client/agents.py +1353 -0
  103. glaip_sdk/client/base.py +502 -0
  104. glaip_sdk/client/main.py +253 -0
  105. glaip_sdk/client/mcps.py +401 -0
  106. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  107. glaip_sdk/client/payloads/agent/requests.py +495 -0
  108. glaip_sdk/client/payloads/agent/responses.py +43 -0
  109. glaip_sdk/client/run_rendering.py +747 -0
  110. glaip_sdk/client/schedules.py +439 -0
  111. glaip_sdk/client/shared.py +21 -0
  112. glaip_sdk/client/tools.py +690 -0
  113. glaip_sdk/client/validators.py +198 -0
  114. glaip_sdk/config/constants.py +52 -0
  115. glaip_sdk/exceptions.py +113 -0
  116. glaip_sdk/hitl/__init__.py +15 -0
  117. glaip_sdk/hitl/local.py +151 -0
  118. glaip_sdk/icons.py +25 -0
  119. glaip_sdk/mcps/__init__.py +21 -0
  120. glaip_sdk/mcps/base.py +345 -0
  121. glaip_sdk/models/__init__.py +107 -0
  122. glaip_sdk/models/agent.py +47 -0
  123. glaip_sdk/models/agent_runs.py +117 -0
  124. glaip_sdk/models/common.py +42 -0
  125. glaip_sdk/models/mcp.py +33 -0
  126. glaip_sdk/models/schedule.py +224 -0
  127. glaip_sdk/models/tool.py +33 -0
  128. glaip_sdk/payload_schemas/__init__.py +7 -0
  129. glaip_sdk/payload_schemas/agent.py +85 -0
  130. glaip_sdk/registry/__init__.py +55 -0
  131. glaip_sdk/registry/agent.py +164 -0
  132. glaip_sdk/registry/base.py +139 -0
  133. glaip_sdk/registry/mcp.py +253 -0
  134. glaip_sdk/registry/tool.py +393 -0
  135. glaip_sdk/rich_components.py +125 -0
  136. glaip_sdk/runner/__init__.py +59 -0
  137. glaip_sdk/runner/base.py +84 -0
  138. glaip_sdk/runner/deps.py +112 -0
  139. glaip_sdk/runner/langgraph.py +870 -0
  140. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  141. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  142. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  143. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  144. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  145. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  146. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  147. glaip_sdk/schedules/__init__.py +22 -0
  148. glaip_sdk/schedules/base.py +291 -0
  149. glaip_sdk/tools/__init__.py +22 -0
  150. glaip_sdk/tools/base.py +466 -0
  151. glaip_sdk/utils/__init__.py +86 -0
  152. glaip_sdk/utils/a2a/__init__.py +34 -0
  153. glaip_sdk/utils/a2a/event_processor.py +188 -0
  154. glaip_sdk/utils/agent_config.py +194 -0
  155. glaip_sdk/utils/bundler.py +267 -0
  156. glaip_sdk/utils/client.py +111 -0
  157. glaip_sdk/utils/client_utils.py +486 -0
  158. glaip_sdk/utils/datetime_helpers.py +58 -0
  159. glaip_sdk/utils/discovery.py +78 -0
  160. glaip_sdk/utils/display.py +135 -0
  161. glaip_sdk/utils/export.py +143 -0
  162. glaip_sdk/utils/general.py +61 -0
  163. glaip_sdk/utils/import_export.py +168 -0
  164. glaip_sdk/utils/import_resolver.py +530 -0
  165. glaip_sdk/utils/instructions.py +101 -0
  166. glaip_sdk/utils/rendering/__init__.py +115 -0
  167. glaip_sdk/utils/rendering/formatting.py +264 -0
  168. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  169. glaip_sdk/utils/rendering/layout/panels.py +156 -0
  170. glaip_sdk/utils/rendering/layout/progress.py +202 -0
  171. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  172. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  173. glaip_sdk/utils/rendering/models.py +85 -0
  174. glaip_sdk/utils/rendering/renderer/__init__.py +55 -0
  175. glaip_sdk/utils/rendering/renderer/base.py +1082 -0
  176. glaip_sdk/utils/rendering/renderer/config.py +27 -0
  177. glaip_sdk/utils/rendering/renderer/console.py +55 -0
  178. glaip_sdk/utils/rendering/renderer/debug.py +178 -0
  179. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  180. glaip_sdk/utils/rendering/renderer/stream.py +202 -0
  181. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  182. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  183. glaip_sdk/utils/rendering/renderer/toggle.py +182 -0
  184. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  185. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  186. glaip_sdk/utils/rendering/state.py +204 -0
  187. glaip_sdk/utils/rendering/step_tree_state.py +100 -0
  188. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  189. glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
  190. glaip_sdk/utils/rendering/steps/format.py +176 -0
  191. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  192. glaip_sdk/utils/rendering/timing.py +36 -0
  193. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  194. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  195. glaip_sdk/utils/resource_refs.py +195 -0
  196. glaip_sdk/utils/run_renderer.py +41 -0
  197. glaip_sdk/utils/runtime_config.py +425 -0
  198. glaip_sdk/utils/serialization.py +424 -0
  199. glaip_sdk/utils/sync.py +142 -0
  200. glaip_sdk/utils/tool_detection.py +33 -0
  201. glaip_sdk/utils/tool_storage_provider.py +140 -0
  202. glaip_sdk/utils/validation.py +264 -0
  203. glaip_sdk-0.0.0b99.dist-info/METADATA +239 -0
  204. glaip_sdk-0.0.0b99.dist-info/RECORD +207 -0
  205. glaip_sdk-0.0.0b99.dist-info/WHEEL +5 -0
  206. glaip_sdk-0.0.0b99.dist-info/entry_points.txt +2 -0
  207. 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
+ )
@@ -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"]
@@ -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
+ ]