aip-agents-binary 0.5.21__py3-none-macosx_13_0_arm64.whl → 0.6.8__py3-none-macosx_13_0_arm64.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 (149) hide show
  1. aip_agents/agent/__init__.py +44 -4
  2. aip_agents/agent/base_langgraph_agent.py +169 -74
  3. aip_agents/agent/base_langgraph_agent.pyi +3 -2
  4. aip_agents/agent/langgraph_memory_enhancer_agent.py +368 -34
  5. aip_agents/agent/langgraph_memory_enhancer_agent.pyi +3 -2
  6. aip_agents/agent/langgraph_react_agent.py +424 -35
  7. aip_agents/agent/langgraph_react_agent.pyi +46 -2
  8. aip_agents/examples/{hello_world_langgraph_bosa_twitter.py → hello_world_langgraph_gl_connector_twitter.py} +10 -7
  9. aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +5 -0
  10. aip_agents/examples/hello_world_ptc.py +49 -0
  11. aip_agents/examples/hello_world_ptc.pyi +5 -0
  12. aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
  13. aip_agents/examples/hello_world_ptc_custom_tools.pyi +7 -0
  14. aip_agents/examples/hello_world_sentry.py +2 -2
  15. aip_agents/examples/hello_world_tool_output_client.py +9 -0
  16. aip_agents/examples/tools/multiply_tool.py +43 -0
  17. aip_agents/examples/tools/multiply_tool.pyi +18 -0
  18. aip_agents/guardrails/__init__.py +83 -0
  19. aip_agents/guardrails/__init__.pyi +6 -0
  20. aip_agents/guardrails/engines/__init__.py +69 -0
  21. aip_agents/guardrails/engines/__init__.pyi +4 -0
  22. aip_agents/guardrails/engines/base.py +90 -0
  23. aip_agents/guardrails/engines/base.pyi +61 -0
  24. aip_agents/guardrails/engines/nemo.py +101 -0
  25. aip_agents/guardrails/engines/nemo.pyi +46 -0
  26. aip_agents/guardrails/engines/phrase_matcher.py +113 -0
  27. aip_agents/guardrails/engines/phrase_matcher.pyi +48 -0
  28. aip_agents/guardrails/exceptions.py +39 -0
  29. aip_agents/guardrails/exceptions.pyi +23 -0
  30. aip_agents/guardrails/manager.py +163 -0
  31. aip_agents/guardrails/manager.pyi +42 -0
  32. aip_agents/guardrails/middleware.py +199 -0
  33. aip_agents/guardrails/middleware.pyi +87 -0
  34. aip_agents/guardrails/schemas.py +63 -0
  35. aip_agents/guardrails/schemas.pyi +43 -0
  36. aip_agents/guardrails/utils.py +45 -0
  37. aip_agents/guardrails/utils.pyi +19 -0
  38. aip_agents/mcp/client/__init__.py +38 -2
  39. aip_agents/mcp/client/connection_manager.py +36 -1
  40. aip_agents/mcp/client/connection_manager.pyi +3 -0
  41. aip_agents/mcp/client/persistent_session.py +318 -65
  42. aip_agents/mcp/client/persistent_session.pyi +9 -0
  43. aip_agents/mcp/client/transports.py +52 -4
  44. aip_agents/mcp/client/transports.pyi +9 -0
  45. aip_agents/memory/adapters/base_adapter.py +98 -0
  46. aip_agents/memory/adapters/base_adapter.pyi +25 -0
  47. aip_agents/middleware/base.py +8 -0
  48. aip_agents/middleware/base.pyi +4 -0
  49. aip_agents/middleware/manager.py +22 -0
  50. aip_agents/middleware/manager.pyi +4 -0
  51. aip_agents/ptc/__init__.py +87 -0
  52. aip_agents/ptc/__init__.pyi +14 -0
  53. aip_agents/ptc/custom_tools.py +473 -0
  54. aip_agents/ptc/custom_tools.pyi +184 -0
  55. aip_agents/ptc/custom_tools_payload.py +400 -0
  56. aip_agents/ptc/custom_tools_payload.pyi +31 -0
  57. aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
  58. aip_agents/ptc/custom_tools_templates/__init__.pyi +0 -0
  59. aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +23 -0
  60. aip_agents/ptc/custom_tools_templates/custom_init.py.template +15 -0
  61. aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +60 -0
  62. aip_agents/ptc/custom_tools_templates/custom_registry.py.template +87 -0
  63. aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
  64. aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +19 -0
  65. aip_agents/ptc/doc_gen.py +122 -0
  66. aip_agents/ptc/doc_gen.pyi +40 -0
  67. aip_agents/ptc/exceptions.py +57 -0
  68. aip_agents/ptc/exceptions.pyi +37 -0
  69. aip_agents/ptc/executor.py +261 -0
  70. aip_agents/ptc/executor.pyi +99 -0
  71. aip_agents/ptc/mcp/__init__.py +45 -0
  72. aip_agents/ptc/mcp/__init__.pyi +7 -0
  73. aip_agents/ptc/mcp/sandbox_bridge.py +668 -0
  74. aip_agents/ptc/mcp/sandbox_bridge.pyi +47 -0
  75. aip_agents/ptc/mcp/templates/__init__.py +1 -0
  76. aip_agents/ptc/mcp/templates/__init__.pyi +0 -0
  77. aip_agents/ptc/mcp/templates/mcp_client.py.template +239 -0
  78. aip_agents/ptc/naming.py +196 -0
  79. aip_agents/ptc/naming.pyi +85 -0
  80. aip_agents/ptc/payload.py +26 -0
  81. aip_agents/ptc/payload.pyi +15 -0
  82. aip_agents/ptc/prompt_builder.py +673 -0
  83. aip_agents/ptc/prompt_builder.pyi +59 -0
  84. aip_agents/ptc/ptc_helper.py +16 -0
  85. aip_agents/ptc/ptc_helper.pyi +1 -0
  86. aip_agents/ptc/sandbox_bridge.py +256 -0
  87. aip_agents/ptc/sandbox_bridge.pyi +38 -0
  88. aip_agents/ptc/template_utils.py +33 -0
  89. aip_agents/ptc/template_utils.pyi +13 -0
  90. aip_agents/ptc/templates/__init__.py +1 -0
  91. aip_agents/ptc/templates/__init__.pyi +0 -0
  92. aip_agents/ptc/templates/ptc_helper.py.template +134 -0
  93. aip_agents/ptc/tool_def_helpers.py +101 -0
  94. aip_agents/ptc/tool_def_helpers.pyi +38 -0
  95. aip_agents/ptc/tool_enrichment.py +163 -0
  96. aip_agents/ptc/tool_enrichment.pyi +60 -0
  97. aip_agents/sandbox/__init__.py +43 -0
  98. aip_agents/sandbox/__init__.pyi +5 -0
  99. aip_agents/sandbox/defaults.py +205 -0
  100. aip_agents/sandbox/defaults.pyi +30 -0
  101. aip_agents/sandbox/e2b_runtime.py +295 -0
  102. aip_agents/sandbox/e2b_runtime.pyi +57 -0
  103. aip_agents/sandbox/template_builder.py +131 -0
  104. aip_agents/sandbox/template_builder.pyi +36 -0
  105. aip_agents/sandbox/types.py +24 -0
  106. aip_agents/sandbox/types.pyi +14 -0
  107. aip_agents/sandbox/validation.py +50 -0
  108. aip_agents/sandbox/validation.pyi +20 -0
  109. aip_agents/sentry/__init__.py +1 -1
  110. aip_agents/sentry/sentry.py +33 -12
  111. aip_agents/sentry/sentry.pyi +5 -4
  112. aip_agents/tools/__init__.py +20 -3
  113. aip_agents/tools/__init__.pyi +4 -2
  114. aip_agents/tools/browser_use/browser_use_tool.py +8 -0
  115. aip_agents/tools/browser_use/streaming.py +2 -0
  116. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +80 -31
  117. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +25 -9
  118. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +6 -6
  119. aip_agents/tools/constants.py +24 -12
  120. aip_agents/tools/constants.pyi +14 -11
  121. aip_agents/tools/date_range_tool.py +554 -0
  122. aip_agents/tools/date_range_tool.pyi +21 -0
  123. aip_agents/tools/execute_ptc_code.py +357 -0
  124. aip_agents/tools/execute_ptc_code.pyi +90 -0
  125. aip_agents/tools/gl_connector/__init__.py +1 -1
  126. aip_agents/tools/gl_connector/tool.py +62 -30
  127. aip_agents/tools/gl_connector/tool.pyi +3 -3
  128. aip_agents/tools/gl_connector_tools.py +119 -0
  129. aip_agents/tools/gl_connector_tools.pyi +39 -0
  130. aip_agents/tools/memory_search/__init__.py +8 -1
  131. aip_agents/tools/memory_search/__init__.pyi +3 -3
  132. aip_agents/tools/memory_search/mem0.py +114 -1
  133. aip_agents/tools/memory_search/mem0.pyi +11 -1
  134. aip_agents/tools/memory_search/schema.py +33 -0
  135. aip_agents/tools/memory_search/schema.pyi +10 -0
  136. aip_agents/tools/memory_search_tool.py +8 -0
  137. aip_agents/tools/memory_search_tool.pyi +2 -2
  138. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +26 -1
  139. aip_agents/utils/langgraph/tool_output_management.py +80 -0
  140. aip_agents/utils/langgraph/tool_output_management.pyi +37 -0
  141. {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/METADATA +14 -22
  142. {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/RECORD +144 -58
  143. {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/WHEEL +1 -1
  144. aip_agents/examples/demo_memory_recall.py +0 -401
  145. aip_agents/examples/demo_memory_recall.pyi +0 -58
  146. aip_agents/examples/hello_world_langgraph_bosa_twitter.pyi +0 -5
  147. aip_agents/tools/bosa_tools.py +0 -105
  148. aip_agents/tools/bosa_tools.pyi +0 -37
  149. {aip_agents_binary-0.5.21.dist-info → aip_agents_binary-0.6.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,295 @@
1
+ """E2B Sandbox Runtime for PTC.
2
+
3
+ This module provides direct E2B SDK integration for sandbox code execution.
4
+
5
+ Authors:
6
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
7
+ """
8
+
9
+ from collections.abc import Callable
10
+
11
+ from e2b_code_interpreter import AsyncSandbox, OutputMessage
12
+
13
+ from aip_agents.sandbox.defaults import DEFAULT_PTC_PACKAGES, DEFAULT_PTC_TEMPLATE
14
+ from aip_agents.sandbox.types import SandboxExecutionResult
15
+ from aip_agents.sandbox.validation import validate_package_names
16
+ from aip_agents.utils.logger import get_logger
17
+
18
+ # Type alias for package selector callback.
19
+ # Called after sandbox creation with actual template status (None if template failed).
20
+ PackageSelector = Callable[[str | None], list[str] | None]
21
+
22
+ logger = get_logger(__name__)
23
+
24
+ SANDBOX_NOT_INITIALIZED_ERROR = "Sandbox not initialized"
25
+
26
+
27
+ class E2BSandboxRuntime:
28
+ """E2B Sandbox runtime for executing code in isolated environments.
29
+
30
+ This runtime manages per-run sandbox lifecycle:
31
+ - Create sandbox on first execute
32
+ - Reuse sandbox for subsequent executes
33
+ - Destroy sandbox on cleanup
34
+
35
+ Example:
36
+ runtime = E2BSandboxRuntime()
37
+ result = await runtime.execute(
38
+ code="print('Hello')",
39
+ timeout=60.0,
40
+ files={"tools/mcp.py": "# MCP client code"},
41
+ )
42
+ await runtime.cleanup()
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ template: str | None = None,
48
+ ptc_packages: list[str] | None = None,
49
+ package_selector: PackageSelector | None = None,
50
+ ) -> None:
51
+ """Initialize E2B sandbox runtime.
52
+
53
+ Args:
54
+ template: Optional E2B template ID for custom sandbox environments.
55
+ ptc_packages: Packages to install in sandbox. If None or empty, skip install.
56
+ Overwritten by package_selector if provided.
57
+ package_selector: Optional callback to select packages after sandbox creation.
58
+ Called with actual template (None if template failed) to enable smart
59
+ package selection based on whether template was successfully created.
60
+ """
61
+ self._template = template
62
+ self._ptc_packages = ptc_packages
63
+ self._package_selector = package_selector
64
+ self._sandbox: AsyncSandbox | None = None
65
+ self._sandbox_created_with_template = False
66
+
67
+ async def execute(
68
+ self,
69
+ code: str,
70
+ *,
71
+ timeout: float = 300.0,
72
+ files: dict[str, str] | None = None,
73
+ env: dict[str, str] | None = None,
74
+ template: str | None = None,
75
+ ) -> SandboxExecutionResult:
76
+ """Execute code inside the sandbox.
77
+
78
+ Args:
79
+ code: Python code to execute.
80
+ timeout: Execution timeout in seconds.
81
+ files: Files to upload to the sandbox (path -> content).
82
+ env: Environment variables to set.
83
+ template: Optional template override for this execution.
84
+
85
+ Returns:
86
+ SandboxExecutionResult with stdout, stderr, and exit_code.
87
+ """
88
+ # Create sandbox if not exists
89
+ if self._sandbox is None:
90
+ await self._create_sandbox(template or self._template)
91
+
92
+ # Upload files if provided
93
+ if files:
94
+ await self._upload_files(files)
95
+
96
+ # Execute code
97
+ return await self._run_code(code, timeout, env)
98
+
99
+ async def cleanup(self) -> None:
100
+ """Destroy the sandbox and release resources."""
101
+ if self._sandbox is not None:
102
+ try:
103
+ logger.info("Destroying E2B sandbox")
104
+ await self._sandbox.kill()
105
+ except Exception as e:
106
+ logger.warning(f"Error destroying sandbox: {e}")
107
+ finally:
108
+ self._sandbox = None
109
+
110
+ self._reset_async_transport()
111
+
112
+ @property
113
+ def is_active(self) -> bool:
114
+ """Check if a sandbox is currently active."""
115
+ return self._sandbox is not None
116
+
117
+ def _reset_async_transport(self) -> None:
118
+ try:
119
+ from e2b.api.client_async import AsyncTransportWithLogger
120
+
121
+ AsyncTransportWithLogger.singleton = None
122
+ except Exception:
123
+ return
124
+
125
+ def _should_skip_default_ptc_install(self, template: str | None) -> bool:
126
+ if not self._sandbox_created_with_template:
127
+ return False
128
+
129
+ if template != DEFAULT_PTC_TEMPLATE:
130
+ return False
131
+
132
+ return self._ptc_packages == list(DEFAULT_PTC_PACKAGES)
133
+
134
+ async def _create_sandbox(self, template: str | None = None) -> None:
135
+ """Create a new E2B sandbox.
136
+
137
+ Implements canonical runtime rules:
138
+ - If template provided, try creating sandbox with template
139
+ - On any error, fall back to default sandbox (without template)
140
+ - Install ptc_packages regardless of template usage (even after fallback)
141
+
142
+ Note: Package installation occurs after sandbox creation, so fallback
143
+ to default sandbox does not skip package installation.
144
+
145
+ Args:
146
+ template: Optional template ID.
147
+ """
148
+ logger.info(f"Creating E2B sandbox (template={template})")
149
+
150
+ async def create_default_sandbox() -> None:
151
+ self._sandbox = await AsyncSandbox.create()
152
+ logger.info(f"E2B sandbox created (default): {self._sandbox.sandbox_id}")
153
+
154
+ if template:
155
+ try:
156
+ self._sandbox = await AsyncSandbox.create(template=template)
157
+ self._sandbox_created_with_template = True
158
+ logger.info(f"E2B sandbox created: {self._sandbox.sandbox_id}")
159
+ except Exception as e:
160
+ logger.warning(f"Template creation failed ({template}): {e}")
161
+ logger.info("Falling back to default sandbox")
162
+ self._sandbox_created_with_template = False
163
+ await create_default_sandbox()
164
+ else:
165
+ self._sandbox_created_with_template = False
166
+ await create_default_sandbox()
167
+
168
+ # Determine packages to install
169
+ # If package_selector is provided, use it with actual template status
170
+ # This enables smart package selection based on whether template succeeded
171
+ if self._package_selector is not None:
172
+ actual_template = template if self._sandbox_created_with_template else None
173
+ try:
174
+ self._ptc_packages = self._package_selector(actual_template)
175
+ logger.info(
176
+ f"Package selector called with actual_template={actual_template}, "
177
+ f"selected packages: {self._ptc_packages}"
178
+ )
179
+ except Exception as e:
180
+ logger.error(f"Package selector failed: {e}")
181
+ await self.cleanup()
182
+ raise
183
+
184
+ # Install ptc_packages if non-empty
185
+ if self._ptc_packages:
186
+ if self._should_skip_default_ptc_install(template):
187
+ logger.info("Skipping PTC package install (default template already includes defaults)")
188
+ else:
189
+ await self._install_ptc_packages()
190
+
191
+ async def _install_ptc_packages(self) -> None:
192
+ """Install PTC packages in the sandbox."""
193
+ if self._sandbox is None:
194
+ raise RuntimeError(SANDBOX_NOT_INITIALIZED_ERROR)
195
+
196
+ # Validate all packages before constructing command
197
+ validate_package_names(self._ptc_packages)
198
+
199
+ # Note: packages_str is safe because ptc_packages is a controlled list from
200
+ # configuration, not user input. E2B SDK's commands.run() only accepts str,
201
+ # not list, so string joining is required.
202
+ packages_str = " ".join(self._ptc_packages)
203
+ logger.info(f"Installing PTC packages in sandbox: {packages_str}")
204
+
205
+ try:
206
+ result = await self._sandbox.commands.run(
207
+ f"pip install -q {packages_str}",
208
+ timeout=120,
209
+ )
210
+ except Exception as e:
211
+ logger.error(f"Error installing PTC packages: {e}")
212
+ raise
213
+
214
+ if result.exit_code != 0:
215
+ logger.error(f"Failed to install PTC packages: {result.stderr}")
216
+ raise RuntimeError(f"Failed to install PTC packages: {result.stderr}")
217
+
218
+ logger.info("PTC packages installed successfully")
219
+
220
+ async def _upload_files(self, files: dict[str, str]) -> None:
221
+ """Upload files to the sandbox.
222
+
223
+ Args:
224
+ files: Mapping of path -> content.
225
+ """
226
+ if self._sandbox is None:
227
+ raise RuntimeError(SANDBOX_NOT_INITIALIZED_ERROR)
228
+
229
+ for path, content in files.items():
230
+ logger.debug(f"Uploading file to sandbox: {path}")
231
+ await self._sandbox.files.write(path, content)
232
+
233
+ async def _run_code(
234
+ self,
235
+ code: str,
236
+ timeout: float,
237
+ env: dict[str, str] | None = None,
238
+ ) -> SandboxExecutionResult:
239
+ """Run code in the sandbox.
240
+
241
+ Args:
242
+ code: Python code to execute.
243
+ timeout: Execution timeout in seconds.
244
+ env: Environment variables.
245
+
246
+ Returns:
247
+ SandboxExecutionResult with execution output.
248
+ """
249
+ if self._sandbox is None:
250
+ raise RuntimeError(SANDBOX_NOT_INITIALIZED_ERROR)
251
+
252
+ stdout_lines: list[str] = []
253
+ stderr_lines: list[str] = []
254
+
255
+ def on_stdout(msg: OutputMessage) -> None:
256
+ if hasattr(msg, "line"):
257
+ stdout_lines.append(msg.line)
258
+
259
+ def on_stderr(msg: OutputMessage) -> None:
260
+ if hasattr(msg, "line"):
261
+ stderr_lines.append(msg.line)
262
+
263
+ try:
264
+ execution = await self._sandbox.run_code(
265
+ code=code,
266
+ language="python",
267
+ on_stdout=on_stdout,
268
+ on_stderr=on_stderr,
269
+ envs=env,
270
+ timeout=timeout,
271
+ )
272
+
273
+ # Determine exit code
274
+ exit_code = 0
275
+ if execution.error:
276
+ exit_code = 1
277
+ # Add error to stderr
278
+ error_msg = f"{execution.error.name}: {execution.error.value}"
279
+ if execution.error.traceback:
280
+ error_msg = f"{execution.error.traceback}\n{error_msg}"
281
+ stderr_lines.append(error_msg)
282
+
283
+ return SandboxExecutionResult(
284
+ stdout="\n".join(stdout_lines),
285
+ stderr="\n".join(stderr_lines),
286
+ exit_code=exit_code,
287
+ )
288
+
289
+ except Exception as e:
290
+ logger.error(f"Sandbox execution failed: {e}")
291
+ return SandboxExecutionResult(
292
+ stdout="",
293
+ stderr=str(e),
294
+ exit_code=1,
295
+ )
@@ -0,0 +1,57 @@
1
+ from _typeshed import Incomplete
2
+ from aip_agents.sandbox.defaults import DEFAULT_PTC_PACKAGES as DEFAULT_PTC_PACKAGES, DEFAULT_PTC_TEMPLATE as DEFAULT_PTC_TEMPLATE
3
+ from aip_agents.sandbox.types import SandboxExecutionResult as SandboxExecutionResult
4
+ from aip_agents.sandbox.validation import validate_package_names as validate_package_names
5
+ from aip_agents.utils.logger import get_logger as get_logger
6
+ from collections.abc import Callable
7
+
8
+ PackageSelector = Callable[[str | None], list[str] | None]
9
+ logger: Incomplete
10
+ SANDBOX_NOT_INITIALIZED_ERROR: str
11
+
12
+ class E2BSandboxRuntime:
13
+ '''E2B Sandbox runtime for executing code in isolated environments.
14
+
15
+ This runtime manages per-run sandbox lifecycle:
16
+ - Create sandbox on first execute
17
+ - Reuse sandbox for subsequent executes
18
+ - Destroy sandbox on cleanup
19
+
20
+ Example:
21
+ runtime = E2BSandboxRuntime()
22
+ result = await runtime.execute(
23
+ code="print(\'Hello\')",
24
+ timeout=60.0,
25
+ files={"tools/mcp.py": "# MCP client code"},
26
+ )
27
+ await runtime.cleanup()
28
+ '''
29
+ def __init__(self, template: str | None = None, ptc_packages: list[str] | None = None, package_selector: PackageSelector | None = None) -> None:
30
+ """Initialize E2B sandbox runtime.
31
+
32
+ Args:
33
+ template: Optional E2B template ID for custom sandbox environments.
34
+ ptc_packages: Packages to install in sandbox. If None or empty, skip install.
35
+ Overwritten by package_selector if provided.
36
+ package_selector: Optional callback to select packages after sandbox creation.
37
+ Called with actual template (None if template failed) to enable smart
38
+ package selection based on whether template was successfully created.
39
+ """
40
+ async def execute(self, code: str, *, timeout: float = 300.0, files: dict[str, str] | None = None, env: dict[str, str] | None = None, template: str | None = None) -> SandboxExecutionResult:
41
+ """Execute code inside the sandbox.
42
+
43
+ Args:
44
+ code: Python code to execute.
45
+ timeout: Execution timeout in seconds.
46
+ files: Files to upload to the sandbox (path -> content).
47
+ env: Environment variables to set.
48
+ template: Optional template override for this execution.
49
+
50
+ Returns:
51
+ SandboxExecutionResult with stdout, stderr, and exit_code.
52
+ """
53
+ async def cleanup(self) -> None:
54
+ """Destroy the sandbox and release resources."""
55
+ @property
56
+ def is_active(self) -> bool:
57
+ """Check if a sandbox is currently active."""
@@ -0,0 +1,131 @@
1
+ """Template builder for PTC sandbox templates.
2
+
3
+ This module provides utilities for creating and managing E2B sandbox templates
4
+ for programmatic tool calling (PTC) environments.
5
+
6
+ Authors:
7
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
8
+ """
9
+
10
+ from e2b import Template, default_build_logger
11
+
12
+ from aip_agents.sandbox.validation import validate_package_names
13
+ from aip_agents.utils.logger import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ def create_ptc_template(base_template: str, ptc_packages: list[str] | None) -> Template:
19
+ """Create a PTC template definition based on a base template.
20
+
21
+ Args:
22
+ base_template: Base template alias to build from (e.g., "code-interpreter-v1").
23
+ ptc_packages: List of packages to install in the template.
24
+ If None or empty, skips pip install step.
25
+
26
+ Returns:
27
+ Template: A configured template ready to be built.
28
+ """
29
+ logger.info(f"Creating template from base: {base_template}")
30
+ template = Template().from_template(base_template)
31
+
32
+ if ptc_packages:
33
+ # Validate all packages before constructing command
34
+ validate_package_names(ptc_packages)
35
+
36
+ # Note: packages_str is safe because ptc_packages is a controlled list from
37
+ # configuration, not user input. Template.run_cmd() only accepts str.
38
+ packages_str = " ".join(ptc_packages)
39
+ logger.info(f"Installing packages: {packages_str}")
40
+ template.run_cmd(f"pip install -q {packages_str}")
41
+
42
+ return template
43
+
44
+
45
+ def _template_exists(template_id: str) -> bool:
46
+ """Check if a template alias exists.
47
+
48
+ Args:
49
+ template_id: The template alias to check.
50
+
51
+ Returns:
52
+ bool: True if alias exists, False otherwise.
53
+ """
54
+ try:
55
+ return Template.exists(template_id)
56
+ except Exception:
57
+ logger.warning(f"Template exists check failed for: {template_id}")
58
+ return False
59
+
60
+
61
+ def _build_template(template: Template, template_id: str) -> bool:
62
+ """Build a template with the given alias.
63
+
64
+ Args:
65
+ template: The template to build.
66
+ template_id: The alias to assign to the built template.
67
+
68
+ Returns:
69
+ bool: True if build succeeded, False otherwise.
70
+ """
71
+ try:
72
+ logger.info(f"Building template: {template_id}")
73
+ Template.build(
74
+ template,
75
+ alias=template_id,
76
+ on_build_logs=default_build_logger(),
77
+ )
78
+ logger.info(f"Template built successfully: {template_id}")
79
+ return True
80
+ except Exception as e:
81
+ logger.warning(f"Template build failed for {template_id}: {e}")
82
+ return False
83
+
84
+
85
+ def ensure_ptc_template(
86
+ template_id: str,
87
+ base_template: str,
88
+ ptc_packages: list[str] | None,
89
+ force_rebuild: bool = False,
90
+ ) -> str | None:
91
+ """Ensure a PTC sandbox template exists, creating it if necessary.
92
+
93
+ This is an explicit helper that apps can call at startup to ensure the
94
+ template exists. It is never run implicitly by the SDK.
95
+
96
+ Args:
97
+ template_id: Unique alias for the template (e.g., "aip-agents-ptc-v1").
98
+ base_template: Base template alias to build from
99
+ (e.g., "code-interpreter-v1").
100
+ ptc_packages: List of packages to install in the template.
101
+ If None or empty, skips pip install step.
102
+ force_rebuild: If True, rebuild even if alias exists.
103
+
104
+ Returns:
105
+ The template_id on success, None if creation failed.
106
+ Never raises exceptions.
107
+ """
108
+ # Fast path: template already exists and we're not forcing rebuild
109
+ if not force_rebuild and _template_exists(template_id):
110
+ logger.info(f"Template already exists: {template_id}")
111
+ return template_id
112
+
113
+ # Create and build the template
114
+ try:
115
+ template = create_ptc_template(base_template, ptc_packages)
116
+ except Exception as e:
117
+ logger.warning(f"Template creation failed for {template_id}: {e}")
118
+ return None
119
+
120
+ # Build the template
121
+ is_success = _build_template(template, template_id)
122
+ if is_success:
123
+ return template_id
124
+
125
+ # Build failed. Check if template exists anyway (race condition: another
126
+ # process may have built it while we were trying)
127
+ if _template_exists(template_id):
128
+ logger.info(f"Template already exists after failed build: {template_id}")
129
+ return template_id
130
+
131
+ return None
@@ -0,0 +1,36 @@
1
+ from _typeshed import Incomplete
2
+ from aip_agents.sandbox.validation import validate_package_names as validate_package_names
3
+ from aip_agents.utils.logger import get_logger as get_logger
4
+ from e2b import Template
5
+
6
+ logger: Incomplete
7
+
8
+ def create_ptc_template(base_template: str, ptc_packages: list[str] | None) -> Template:
9
+ '''Create a PTC template definition based on a base template.
10
+
11
+ Args:
12
+ base_template: Base template alias to build from (e.g., "code-interpreter-v1").
13
+ ptc_packages: List of packages to install in the template.
14
+ If None or empty, skips pip install step.
15
+
16
+ Returns:
17
+ Template: A configured template ready to be built.
18
+ '''
19
+ def ensure_ptc_template(template_id: str, base_template: str, ptc_packages: list[str] | None, force_rebuild: bool = False) -> str | None:
20
+ '''Ensure a PTC sandbox template exists, creating it if necessary.
21
+
22
+ This is an explicit helper that apps can call at startup to ensure the
23
+ template exists. It is never run implicitly by the SDK.
24
+
25
+ Args:
26
+ template_id: Unique alias for the template (e.g., "aip-agents-ptc-v1").
27
+ base_template: Base template alias to build from
28
+ (e.g., "code-interpreter-v1").
29
+ ptc_packages: List of packages to install in the template.
30
+ If None or empty, skips pip install step.
31
+ force_rebuild: If True, rebuild even if alias exists.
32
+
33
+ Returns:
34
+ The template_id on success, None if creation failed.
35
+ Never raises exceptions.
36
+ '''
@@ -0,0 +1,24 @@
1
+ """Sandbox execution result types.
2
+
3
+ This module defines types for sandbox execution results.
4
+
5
+ Authors:
6
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass
13
+ class SandboxExecutionResult:
14
+ """Result of a sandbox code execution.
15
+
16
+ Attributes:
17
+ stdout: Standard output from the execution.
18
+ stderr: Standard error from the execution.
19
+ exit_code: Exit code (0 for success, non-zero for failure).
20
+ """
21
+
22
+ stdout: str
23
+ stderr: str
24
+ exit_code: int
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass
2
+
3
+ @dataclass
4
+ class SandboxExecutionResult:
5
+ """Result of a sandbox code execution.
6
+
7
+ Attributes:
8
+ stdout: Standard output from the execution.
9
+ stderr: Standard error from the execution.
10
+ exit_code: Exit code (0 for success, non-zero for failure).
11
+ """
12
+ stdout: str
13
+ stderr: str
14
+ exit_code: int
@@ -0,0 +1,50 @@
1
+ """Validation utilities for sandbox operations.
2
+
3
+ This module provides validation functions for sandbox-related operations
4
+ such as package name validation.
5
+
6
+ Authors:
7
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
8
+ """
9
+
10
+ import re
11
+
12
+ _PACKAGE_SPEC_PATTERN = re.compile(
13
+ r"^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?"
14
+ r"(?:\[[A-Za-z0-9._-]+(?:,[A-Za-z0-9._-]+)*\])?"
15
+ r"(?:"
16
+ r"(?:==|!=|<=|>=|~=|<|>)[0-9][A-Za-z0-9.*+!_-]*"
17
+ r"(?:,(?:==|!=|<=|>=|~=|<|>)[0-9][A-Za-z0-9.*+!_-]*)*"
18
+ r")?$"
19
+ )
20
+
21
+
22
+ def validate_package_name(package: str) -> bool:
23
+ """Validate package name/specifier format for pip install.
24
+
25
+ Allows standard pip formats: package, package==version, package[extra].
26
+
27
+ Args:
28
+ package: Package name or specifier to validate.
29
+
30
+ Returns:
31
+ True if package name is valid, False otherwise.
32
+ """
33
+ if not package:
34
+ return False
35
+
36
+ return bool(_PACKAGE_SPEC_PATTERN.fullmatch(package))
37
+
38
+
39
+ def validate_package_names(packages: list[str]) -> None:
40
+ """Validate all package names in a list.
41
+
42
+ Args:
43
+ packages: List of package names or specifiers to validate.
44
+
45
+ Raises:
46
+ ValueError: If any package name is invalid.
47
+ """
48
+ for pkg in packages:
49
+ if not validate_package_name(pkg):
50
+ raise ValueError(f"Invalid package name format: {pkg}")
@@ -0,0 +1,20 @@
1
+ def validate_package_name(package: str) -> bool:
2
+ """Validate package name/specifier format for pip install.
3
+
4
+ Allows standard pip formats: package, package==version, package[extra].
5
+
6
+ Args:
7
+ package: Package name or specifier to validate.
8
+
9
+ Returns:
10
+ True if package name is valid, False otherwise.
11
+ """
12
+ def validate_package_names(packages: list[str]) -> None:
13
+ """Validate all package names in a list.
14
+
15
+ Args:
16
+ packages: List of package names or specifiers to validate.
17
+
18
+ Raises:
19
+ ValueError: If any package name is invalid.
20
+ """
@@ -1,4 +1,4 @@
1
- """This module provides a centralized way to handle Sentry and OpenTelemetry configuration for BOSA SDK.
1
+ """This module provides a centralized way to handle Sentry and OpenTelemetry configuration for GL Connectors SDK.
2
2
 
3
3
  Authors:
4
4
  Saul Sayers (saul.sayers@gdplabs.id)