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
@@ -1,4 +1,4 @@
1
- """This file contains the Sentry and OpenTelemetry configuration for BOSA SDK.
1
+ """This file contains the Sentry and OpenTelemetry configuration for GL Connectors SDK.
2
2
 
3
3
  Authors:
4
4
  Saul Sayers (saul.sayers@gdplabs.id)
@@ -6,6 +6,7 @@ Authors:
6
6
 
7
7
  import inspect
8
8
  import os
9
+ from typing import Any
9
10
 
10
11
  from bosa_core.telemetry import (
11
12
  FastAPIConfig,
@@ -20,7 +21,7 @@ from bosa_core.telemetry.opentelemetry.instrument.functions import (
20
21
  from dotenv import load_dotenv
21
22
  from fastapi import FastAPI
22
23
 
23
- from aip_agents.agent import BaseAgent, GoogleADKAgent, LangChainAgent, LangGraphAgent
24
+ from aip_agents.agent import BaseAgent, LangChainAgent, LangGraphAgent
24
25
  from aip_agents.utils.logger import get_logger
25
26
 
26
27
  load_dotenv()
@@ -35,12 +36,32 @@ VERSION_NUMBER = os.getenv("VERSION_NUMBER", "0.0.0")
35
36
  BUILD_NUMBER = os.getenv("BUILD_NUMBER", "0")
36
37
  USE_OPENTELEMETRY = os.getenv("USE_OPENTELEMETRY", "true").lower() == "true"
37
38
 
38
- CLASSES_TO_INSTRUMENT = [
39
- BaseAgent,
40
- LangGraphAgent,
41
- LangChainAgent,
42
- GoogleADKAgent,
43
- ]
39
+ # Lazy import of GoogleADKAgent to avoid heavy dependencies when not needed.
40
+ # This is initialized lazily by _get_classes_to_instrument() and can be
41
+ # patched by tests for mocking purposes.
42
+ CLASSES_TO_INSTRUMENT: list[type[Any]] | None = None
43
+
44
+
45
+ def _get_classes_to_instrument() -> list[type[Any]]:
46
+ """Get the list of classes to instrument.
47
+
48
+ This lazily imports GoogleADKAgent only when telemetry is being set up,
49
+ avoiding the heavy Google ADK dependencies during module import.
50
+
51
+ Returns:
52
+ List of agent classes to instrument.
53
+ """
54
+ global CLASSES_TO_INSTRUMENT
55
+ if CLASSES_TO_INSTRUMENT is None:
56
+ from aip_agents.agent import GoogleADKAgent
57
+
58
+ CLASSES_TO_INSTRUMENT = [
59
+ BaseAgent,
60
+ LangGraphAgent,
61
+ LangChainAgent,
62
+ GoogleADKAgent,
63
+ ]
64
+ return CLASSES_TO_INSTRUMENT
44
65
 
45
66
 
46
67
  def get_all_methods(cls: type) -> list:
@@ -61,12 +82,12 @@ def get_all_methods(cls: type) -> list:
61
82
  return methods
62
83
 
63
84
 
64
- def instrument_bosa_functions() -> None:
65
- """Instrument BOSA functions."""
85
+ def instrument_gl_functions() -> None:
86
+ """Instrument GL functions."""
66
87
  if BOSAFunctionsInstrumentor is None:
67
88
  return
68
89
  agent_methods = []
69
- for cls in CLASSES_TO_INSTRUMENT:
90
+ for cls in _get_classes_to_instrument():
70
91
  agent_methods.extend(get_all_methods(cls))
71
92
  BOSAFunctionsInstrumentor().instrument(methods=agent_methods)
72
93
 
@@ -148,4 +169,4 @@ def setup_telemetry(app: FastAPI) -> None:
148
169
  setup_sentry_with_open_telemetry(app)
149
170
  else:
150
171
  setup_sentry_only()
151
- instrument_bosa_functions()
172
+ instrument_gl_functions()
@@ -1,7 +1,8 @@
1
1
  from _typeshed import Incomplete
2
- from aip_agents.agent import BaseAgent as BaseAgent, GoogleADKAgent as GoogleADKAgent, LangChainAgent as LangChainAgent, LangGraphAgent as LangGraphAgent
2
+ from aip_agents.agent import BaseAgent as BaseAgent, LangChainAgent as LangChainAgent, LangGraphAgent as LangGraphAgent
3
3
  from aip_agents.utils.logger import get_logger as get_logger
4
4
  from fastapi import FastAPI
5
+ from typing import Any
5
6
 
6
7
  logger: Incomplete
7
8
  SENTRY_DSN: Incomplete
@@ -10,7 +11,7 @@ SENTRY_PROJECT: Incomplete
10
11
  VERSION_NUMBER: Incomplete
11
12
  BUILD_NUMBER: Incomplete
12
13
  USE_OPENTELEMETRY: Incomplete
13
- CLASSES_TO_INSTRUMENT: Incomplete
14
+ CLASSES_TO_INSTRUMENT: list[type[Any]] | None
14
15
 
15
16
  def get_all_methods(cls) -> list:
16
17
  """Get all methods from a class.
@@ -21,8 +22,8 @@ def get_all_methods(cls) -> list:
21
22
  Returns:
22
23
  list: A list of methods.
23
24
  """
24
- def instrument_bosa_functions() -> None:
25
- """Instrument BOSA functions."""
25
+ def instrument_gl_functions() -> None:
26
+ """Instrument GL functions."""
26
27
  def traces_sampler(*args) -> float:
27
28
  """Determine appropriate sampling rate for Sentry transactions.
28
29
 
@@ -3,15 +3,26 @@
3
3
  from importlib import import_module
4
4
  from typing import TYPE_CHECKING
5
5
 
6
- from aip_agents.tools.bosa_tools import BOSA_AUTOMATED_TOOLS
6
+ from aip_agents.tools.date_range_tool import DateRangeTool
7
7
  from aip_agents.tools.gl_connector import GLConnectorTool
8
+ from aip_agents.tools.gl_connector_tools import (
9
+ BOSA_AUTOMATED_TOOLS,
10
+ GL_CONNECTORS_AUTOMATED_TOOLS,
11
+ )
8
12
  from aip_agents.tools.time_tool import TimeTool
9
13
  from aip_agents.tools.web_search import GoogleSerperTool
10
14
  from aip_agents.utils.logger import get_logger
11
15
 
12
16
  logger = get_logger(__name__)
13
17
 
14
- __all__ = ["BOSA_AUTOMATED_TOOLS", "GLConnectorTool", "GoogleSerperTool", "TimeTool"]
18
+ __all__ = [
19
+ "BOSA_AUTOMATED_TOOLS",
20
+ "GL_CONNECTORS_AUTOMATED_TOOLS",
21
+ "GLConnectorTool",
22
+ "GoogleSerperTool",
23
+ "TimeTool",
24
+ "DateRangeTool",
25
+ ]
15
26
 
16
27
 
17
28
  def _register_optional(module_path: str, export_name: str) -> None:
@@ -40,8 +51,14 @@ _register_optional("aip_agents.tools.code_sandbox", "E2BCodeSandboxTool")
40
51
  _register_optional("aip_agents.tools.document_loader", "DocxReaderTool")
41
52
  _register_optional("aip_agents.tools.document_loader", "ExcelReaderTool")
42
53
  _register_optional("aip_agents.tools.document_loader", "PDFReaderTool")
54
+ _register_optional("aip_agents.tools.execute_ptc_code", "create_execute_ptc_code_tool")
43
55
 
44
56
  if TYPE_CHECKING:
45
57
  from aip_agents.tools.browser_use import BrowserUseTool
46
58
  from aip_agents.tools.code_sandbox import E2BCodeSandboxTool
47
- from aip_agents.tools.document_loader import DocxReaderTool, ExcelReaderTool, PDFReaderTool
59
+ from aip_agents.tools.document_loader import (
60
+ DocxReaderTool,
61
+ ExcelReaderTool,
62
+ PDFReaderTool,
63
+ )
64
+ from aip_agents.tools.execute_ptc_code import create_execute_ptc_code_tool
@@ -1,9 +1,11 @@
1
- from aip_agents.tools.bosa_tools import BOSA_AUTOMATED_TOOLS as BOSA_AUTOMATED_TOOLS
2
1
  from aip_agents.tools.browser_use import BrowserUseTool as BrowserUseTool
3
2
  from aip_agents.tools.code_sandbox import E2BCodeSandboxTool as E2BCodeSandboxTool
3
+ from aip_agents.tools.date_range_tool import DateRangeTool as DateRangeTool
4
4
  from aip_agents.tools.document_loader import DocxReaderTool as DocxReaderTool, ExcelReaderTool as ExcelReaderTool, PDFReaderTool as PDFReaderTool
5
+ from aip_agents.tools.execute_ptc_code import create_execute_ptc_code_tool as create_execute_ptc_code_tool
5
6
  from aip_agents.tools.gl_connector import GLConnectorTool as GLConnectorTool
7
+ from aip_agents.tools.gl_connector_tools import BOSA_AUTOMATED_TOOLS as BOSA_AUTOMATED_TOOLS, GL_CONNECTORS_AUTOMATED_TOOLS as GL_CONNECTORS_AUTOMATED_TOOLS
6
8
  from aip_agents.tools.time_tool import TimeTool as TimeTool
7
9
  from aip_agents.tools.web_search import GoogleSerperTool as GoogleSerperTool
8
10
 
9
- __all__ = ['BOSA_AUTOMATED_TOOLS', 'GLConnectorTool', 'GoogleSerperTool', 'TimeTool', 'BrowserUseTool', 'E2BCodeSandboxTool', 'DocxReaderTool', 'ExcelReaderTool', 'PDFReaderTool']
11
+ __all__ = ['BOSA_AUTOMATED_TOOLS', 'GL_CONNECTORS_AUTOMATED_TOOLS', 'GLConnectorTool', 'GoogleSerperTool', 'TimeTool', 'DateRangeTool', 'BrowserUseTool', 'E2BCodeSandboxTool', 'DocxReaderTool', 'ExcelReaderTool', 'PDFReaderTool', 'create_execute_ptc_code_tool']
@@ -93,6 +93,7 @@ References:
93
93
  """
94
94
 
95
95
  import asyncio
96
+ import copy
96
97
  import json
97
98
  from collections.abc import Callable
98
99
  from typing import Any, Literal
@@ -556,13 +557,20 @@ class BrowserUseTool(BaseTool):
556
557
  iframe_event = yield_iframe_activity(streaming_state.debug_url, "Receive streaming URL")
557
558
  self._log_stream_event("iframe_start", iframe_event)
558
559
  yield iframe_event
560
+ last_done_event: dict | None = None
559
561
  async for event in self._stream_agent_with_markers(agent, streaming_state, recorder):
560
562
  self._log_stream_event("agent_event", event)
563
+ tool_info = event.get("tool_info") if isinstance(event, dict) else None
564
+ tool_calls = tool_info.get("tool_calls", []) if isinstance(tool_info, dict) else []
565
+ if any(call.get("name") == "done" for call in tool_calls if isinstance(call, dict)):
566
+ last_done_event = event
561
567
  yield event
562
568
  recording_event = self._recording_event(streaming_state)
563
569
  if recording_event:
564
570
  self._log_stream_event("recording_event", recording_event)
565
571
  yield recording_event
572
+ if last_done_event:
573
+ yield copy.deepcopy(last_done_event)
566
574
  finally:
567
575
  await self._release_session(session, client)
568
576
 
@@ -223,6 +223,8 @@ def create_step_response(
223
223
  if is_done:
224
224
  final_tool = _get_done_tool_for_final_response(agent, tool_calls_dict)
225
225
  final_output = final_tool.get("output") or TASK_COMPLETED_MESSAGE
226
+ if not any(call.get("name") == "done" for call in tool_calls_dict):
227
+ tool_calls_dict.append(final_tool)
226
228
  tool_info = {
227
229
  "name": PRIMARY_TOOL_NAME,
228
230
  "args": final_tool.get("args", {}),
@@ -11,12 +11,10 @@ from http import HTTPStatus
11
11
  from typing import Any
12
12
 
13
13
  import requests
14
+ from e2b_code_interpreter import Sandbox
14
15
  from gllm_inference.schema import Attachment
15
- from gllm_tools.code_interpreter.code_sandbox.e2b_cloud_sandbox import E2BCloudSandbox
16
- from gllm_tools.code_interpreter.code_sandbox.models import (
17
- ExecutionResult,
18
- ExecutionStatus,
19
- )
16
+ from gllm_tools.code_interpreter.code_sandbox.e2b_sandbox import E2BSandbox
17
+ from gllm_tools.code_interpreter.code_sandbox.models import ExecutionResult, ExecutionStatus
20
18
  from gllm_tools.code_interpreter.code_sandbox.utils import calculate_duration_ms
21
19
 
22
20
  from aip_agents.tools.code_sandbox.constant import DATA_FILE_PATH
@@ -123,19 +121,57 @@ class SandboxFileWatcher:
123
121
  return self._created_files.copy()
124
122
 
125
123
 
126
- class MyE2BCloudSandbox(E2BCloudSandbox):
127
- """Extended E2B Cloud Sandbox with filesystem monitoring capabilities."""
124
+ class MyE2BCloudSandbox(E2BSandbox):
125
+ """Extended E2B sandbox with filesystem monitoring capabilities.
128
126
 
129
- def __init__(self, *args, **kwargs):
130
- """Initialize the sandbox with monitoring capabilities.
127
+ Use `create()` in production to build a fully initialized sandbox wrapper.
128
+ Direct construction is intentionally blocked to prevent partially initialized
129
+ instances that lack the underlying E2B sandbox clients.
130
+ """
131
+
132
+ def __init__(self, language: str = "python", *, _unsafe_allow_init: bool = False) -> None:
133
+ """Initialize the sandbox wrapper.
131
134
 
132
135
  Args:
133
- *args: Positional arguments forwarded to ``E2BCloudSandbox``.
134
- **kwargs: Keyword arguments forwarded to ``E2BCloudSandbox``.
136
+ language (str): Language to execute inside the sandbox.
137
+ _unsafe_allow_init (bool): Escape hatch for tests/mocks only.
138
+
139
+ Raises:
140
+ RuntimeError: When instantiated directly without `create()`.
135
141
  """
136
- super().__init__(*args, **kwargs)
142
+ if not _unsafe_allow_init:
143
+ raise RuntimeError("Use MyE2BCloudSandbox.create(...) to initialize a sandbox instance.")
144
+ super().__init__(language=language)
137
145
  self.file_watcher: SandboxFileWatcher | None = None
138
146
 
147
+ @classmethod
148
+ async def create(
149
+ cls,
150
+ api_key: str,
151
+ domain: str | None = None,
152
+ template: str | None = None,
153
+ language: str = "python",
154
+ additional_packages: list[str] | None = None,
155
+ **kwargs: Any,
156
+ ) -> "MyE2BCloudSandbox":
157
+ """Create a fully initialized sandbox wrapper.
158
+
159
+ This is the supported construction path for production usage. It wires
160
+ the E2B sandbox instance and its filesystem/command clients, then
161
+ installs language dependencies.
162
+ """
163
+ sandbox = Sandbox.create(api_key=api_key, domain=domain, template=template, **kwargs)
164
+
165
+ instance = cls(language=language, _unsafe_allow_init=True)
166
+ instance.sandbox = sandbox
167
+ instance.files = sandbox.files
168
+ instance.commands = sandbox.commands
169
+ instance.additional_packages = additional_packages or []
170
+
171
+ instance._install_language_dependencies()
172
+
173
+ return instance
174
+
139
175
  async def execute_code(
140
176
  self,
141
177
  code: str,
@@ -161,11 +197,10 @@ class MyE2BCloudSandbox(E2BCloudSandbox):
161
197
  Raises:
162
198
  RuntimeError: If sandbox is not initialized.
163
199
  """
164
- if not self.sandbox:
200
+ if not self.sandbox or not self.files or not self.commands:
165
201
  raise RuntimeError("Sandbox is not initialized")
166
202
 
167
203
  start_time = time.time()
168
-
169
204
  try:
170
205
  # Initialize filesystem monitoring
171
206
  self.file_watcher = SandboxFileWatcher(self.sandbox)
@@ -176,8 +211,12 @@ class MyE2BCloudSandbox(E2BCloudSandbox):
176
211
  # Pre-populate the variable `df` for direct use in the code
177
212
  if files:
178
213
  logger.info("Pre-populating the variable `df` with the data from the file.")
179
- self.sandbox.run_code(f"import pandas as pd; df = pd.read_csv('{DATA_FILE_PATH}')", timeout=timeout)
180
- execution = self.sandbox.run_code(code, timeout=timeout)
214
+ self.sandbox.run_code(
215
+ f"import pandas as pd; df = pd.read_csv('{DATA_FILE_PATH}')",
216
+ language=self.language,
217
+ timeout=timeout,
218
+ )
219
+ execution = self.sandbox.run_code(code, language=self.language, timeout=timeout)
181
220
  duration_ms = calculate_duration_ms(start_time)
182
221
  status = ExecutionStatus.ERROR if execution.error else ExecutionStatus.SUCCESS
183
222
 
@@ -191,8 +230,8 @@ class MyE2BCloudSandbox(E2BCloudSandbox):
191
230
  return ExecutionResult.create(
192
231
  status=status,
193
232
  code=code,
194
- stdout=(execution.logs.stdout[0] if execution.logs and execution.logs.stdout else ""),
195
- stderr=(execution.logs.stderr[0] if execution.logs and execution.logs.stderr else ""),
233
+ stdout=("\n".join(execution.logs.stdout) if execution.logs and execution.logs.stdout else ""),
234
+ stderr=("\n".join(execution.logs.stderr) if execution.logs and execution.logs.stderr else ""),
196
235
  error=(str(execution.error) if execution.error else ""), # Convert to string here
197
236
  duration_ms=duration_ms,
198
237
  )
@@ -218,8 +257,8 @@ class MyE2BCloudSandbox(E2BCloudSandbox):
218
257
  def download_file(self, file_path: str) -> bytes | None:
219
258
  """Download file content from the sandbox.
220
259
 
221
- Uses download_url method to get a direct URL and downloads via HTTP,
222
- which avoids the binary corruption issue with files.read().
260
+ Uses download_url when available to avoid binary corruption issues.
261
+ Falls back to the filesystem API when download_url fails or is unavailable.
223
262
 
224
263
  Args:
225
264
  file_path (str): Path to the file in the sandbox.
@@ -237,18 +276,28 @@ class MyE2BCloudSandbox(E2BCloudSandbox):
237
276
  if hasattr(self.sandbox, "download_url"):
238
277
  logger.info(f"Downloading {file_path} via download_url method")
239
278
 
240
- # Get the download URL
241
- url = self.sandbox.download_url(file_path)
242
- logger.debug(f"Got download URL: {url}")
243
-
244
- response = requests.get(url, timeout=30)
245
-
246
- if response.status_code == HTTPStatus.OK:
247
- content = response.content
248
- logger.info(f"Successfully downloaded {len(content)} bytes via URL")
249
- return content
279
+ try:
280
+ url = self.sandbox.download_url(file_path)
281
+ except Exception as e:
282
+ logger.warning(f"Failed to get download URL: {str(e)}")
250
283
  else:
251
- logger.warning(f"URL download failed with status {response.status_code}")
284
+ logger.debug(f"Got download URL: {url}")
285
+
286
+ try:
287
+ response = requests.get(url, timeout=30)
288
+ except Exception as e:
289
+ logger.warning(f"URL download failed with error: {str(e)}")
290
+ else:
291
+ if response.status_code == HTTPStatus.OK:
292
+ content = response.content
293
+ logger.info(f"Successfully downloaded {len(content)} bytes via URL")
294
+ return content
295
+ logger.warning(f"URL download failed with status {response.status_code}")
296
+
297
+ if self.files:
298
+ logger.info(f"Downloading {file_path} via filesystem API")
299
+ content = self.files.read(file_path, format="bytes")
300
+ return bytes(content)
252
301
 
253
302
  return None
254
303
 
@@ -2,7 +2,7 @@ from _typeshed import Incomplete
2
2
  from aip_agents.tools.code_sandbox.constant import DATA_FILE_PATH as DATA_FILE_PATH
3
3
  from aip_agents.utils.logger import get_logger as get_logger
4
4
  from gllm_inference.schema import Attachment as Attachment
5
- from gllm_tools.code_interpreter.code_sandbox.e2b_cloud_sandbox import E2BCloudSandbox
5
+ from gllm_tools.code_interpreter.code_sandbox.e2b_sandbox import E2BSandbox
6
6
  from gllm_tools.code_interpreter.code_sandbox.models import ExecutionResult
7
7
  from typing import Any
8
8
 
@@ -34,15 +34,31 @@ class SandboxFileWatcher:
34
34
  list[str]: List of file paths that were created.
35
35
  """
36
36
 
37
- class MyE2BCloudSandbox(E2BCloudSandbox):
38
- """Extended E2B Cloud Sandbox with filesystem monitoring capabilities."""
37
+ class MyE2BCloudSandbox(E2BSandbox):
38
+ """Extended E2B sandbox with filesystem monitoring capabilities.
39
+
40
+ Use `create()` in production to build a fully initialized sandbox wrapper.
41
+ Direct construction is intentionally blocked to prevent partially initialized
42
+ instances that lack the underlying E2B sandbox clients.
43
+ """
39
44
  file_watcher: SandboxFileWatcher | None
40
- def __init__(self, *args, **kwargs) -> None:
41
- """Initialize the sandbox with monitoring capabilities.
45
+ def __init__(self, language: str = 'python', *, _unsafe_allow_init: bool = False) -> None:
46
+ """Initialize the sandbox wrapper.
42
47
 
43
48
  Args:
44
- *args: Positional arguments forwarded to ``E2BCloudSandbox``.
45
- **kwargs: Keyword arguments forwarded to ``E2BCloudSandbox``.
49
+ language (str): Language to execute inside the sandbox.
50
+ _unsafe_allow_init (bool): Escape hatch for tests/mocks only.
51
+
52
+ Raises:
53
+ RuntimeError: When instantiated directly without `create()`.
54
+ """
55
+ @classmethod
56
+ async def create(cls, api_key: str, domain: str | None = None, template: str | None = None, language: str = 'python', additional_packages: list[str] | None = None, **kwargs: Any) -> MyE2BCloudSandbox:
57
+ """Create a fully initialized sandbox wrapper.
58
+
59
+ This is the supported construction path for production usage. It wires
60
+ the E2B sandbox instance and its filesystem/command clients, then
61
+ installs language dependencies.
46
62
  """
47
63
  async def execute_code(self, code: str, timeout: int = 30, files: list[Attachment] | None = None, **kwargs: Any) -> ExecutionResult:
48
64
  """Execute code in the E2B Cloud sandbox with filesystem monitoring.
@@ -72,8 +88,8 @@ class MyE2BCloudSandbox(E2BCloudSandbox):
72
88
  def download_file(self, file_path: str) -> bytes | None:
73
89
  """Download file content from the sandbox.
74
90
 
75
- Uses download_url method to get a direct URL and downloads via HTTP,
76
- which avoids the binary corruption issue with files.read().
91
+ Uses download_url when available to avoid binary corruption issues.
92
+ Falls back to the filesystem API when download_url fails or is unavailable.
77
93
 
78
94
  Args:
79
95
  file_path (str): Path to the file in the sandbox.
@@ -12,7 +12,7 @@ from typing import Any
12
12
 
13
13
  import pandas as pd
14
14
  from gllm_inference.schema import Attachment
15
- from gllm_tools.code_interpreter.code_sandbox.e2b_cloud_sandbox import E2BCloudSandbox
15
+ from gllm_tools.code_interpreter.code_sandbox.sandbox import BaseSandbox
16
16
  from langchain_core.tools import BaseTool
17
17
  from langgraph.types import Command
18
18
  from pydantic import BaseModel, Field
@@ -260,7 +260,7 @@ class E2BCodeSandboxTool(BaseTool):
260
260
  """
261
261
  unique_packages = self._prepare_packages(additional_packages)
262
262
 
263
- return MyE2BCloudSandbox(
263
+ return await MyE2BCloudSandbox.create(
264
264
  api_key=self.api_key,
265
265
  language=language,
266
266
  additional_packages=unique_packages,
@@ -304,11 +304,11 @@ class E2BCodeSandboxTool(BaseTool):
304
304
  except Exception as e:
305
305
  logger.warning(f"Error terminating sandbox: {str(e)}")
306
306
 
307
- def _create_artifacts_from_files(self, sandbox: E2BCloudSandbox, file_paths: list[str]) -> list[dict[str, Any]]:
307
+ def _create_artifacts_from_files(self, sandbox: BaseSandbox, file_paths: list[str]) -> list[dict[str, Any]]:
308
308
  """Create artifacts from a list of file paths using ArtifactHandler.
309
309
 
310
310
  Args:
311
- sandbox (E2BCloudSandbox): The active sandbox instance.
311
+ sandbox (BaseSandbox): The active sandbox instance.
312
312
  file_paths (list[str]): List of file paths to download.
313
313
 
314
314
  Returns:
@@ -358,11 +358,11 @@ class E2BCodeSandboxTool(BaseTool):
358
358
 
359
359
  return execution_summary
360
360
 
361
- def _download_and_create_artifact(self, sandbox: E2BCloudSandbox, file_path: str) -> dict[str, Any] | None:
361
+ def _download_and_create_artifact(self, sandbox: BaseSandbox, file_path: str) -> dict[str, Any] | None:
362
362
  """Download a single file and create an artifact using ArtifactHandler.
363
363
 
364
364
  Args:
365
- sandbox (E2BCloudSandbox): The active sandbox instance.
365
+ sandbox (BaseSandbox): The active sandbox instance.
366
366
  file_path (str): Path to the file in the sandbox.
367
367
 
368
368
  Returns:
@@ -1,4 +1,4 @@
1
- """Constants for tools using BOSA Connector.
1
+ """Constants for tools using GL Connectors.
2
2
 
3
3
  Authors:
4
4
  Saul Sayers (saul.sayers@gdplabs.id)
@@ -7,20 +7,32 @@ Authors:
7
7
  import os
8
8
  from enum import Enum, StrEnum
9
9
 
10
- BOSA_API_BASE_URL = os.getenv("BOSA_API_BASE_URL")
11
- BOSA_API_KEY = os.getenv("BOSA_API_KEY")
12
- BOSA_FETCH_MAX_RETRIES = 3
10
+ GL_CONNECTORS_BASE_URL = (
11
+ os.getenv("GL_CONNECTORS_BASE_URL")
12
+ or os.getenv("GL_CONNECTORS_API_BASE_URL")
13
+ or os.getenv("BOSA_API_BASE_URL")
14
+ or os.getenv("BOSA_BASE_URL")
15
+ )
16
+ GL_CONNECTORS_API_KEY = os.getenv("GL_CONNECTORS_API_KEY") or os.getenv("BOSA_API_KEY")
17
+ GL_CONNECTORS_FETCH_MAX_RETRIES = int(
18
+ os.getenv("GL_CONNECTORS_FETCH_MAX_RETRIES") or os.getenv("BOSA_FETCH_MAX_RETRIES") or 3
19
+ )
20
+
21
+ # For backward compatibility
22
+ BOSA_API_BASE_URL = GL_CONNECTORS_BASE_URL
23
+ BOSA_API_KEY = GL_CONNECTORS_API_KEY
24
+ BOSA_FETCH_MAX_RETRIES = GL_CONNECTORS_FETCH_MAX_RETRIES
13
25
 
14
26
 
15
27
  class ToolType(StrEnum):
16
- """Tool types for BOSA Connector."""
28
+ """Tool types for GL Connectors."""
17
29
 
18
30
  GLLM = "gllm"
19
31
  LANGCHAIN = "langchain"
20
32
 
21
33
 
22
34
  class Action(Enum):
23
- """Actions for BOSA Connector."""
35
+ """Actions for GL Connectors."""
24
36
 
25
37
  GITHUB = "github"
26
38
  GOOGLE = "google"
@@ -30,7 +42,7 @@ class Action(Enum):
30
42
 
31
43
 
32
44
  class GitHubEndpoint(Enum):
33
- """GitHub endpoints for BOSA Connector."""
45
+ """GitHub endpoints for GL Connectors."""
34
46
 
35
47
  INTEGRATIONS = "integrations"
36
48
  USER_HAS_INTEGRATION = "integration-exists"
@@ -59,7 +71,7 @@ class GitHubEndpoint(Enum):
59
71
 
60
72
 
61
73
  class GoogleDriveEndpoint(Enum):
62
- """Google Drive endpoints for BOSA Connector."""
74
+ """Google Drive endpoints for GL Connectors."""
63
75
 
64
76
  INTEGRATIONS = "integrations"
65
77
  USER_HAS_INTEGRATION = "integration-exists"
@@ -84,7 +96,7 @@ class GoogleDriveEndpoint(Enum):
84
96
 
85
97
 
86
98
  class GoogleDocsEndpoint(Enum):
87
- """Google Docs endpoints for BOSA Connector."""
99
+ """Google Docs endpoints for GL Connectors."""
88
100
 
89
101
  INTEGRATIONS = "integrations"
90
102
  USER_HAS_INTEGRATION = "integration-exists"
@@ -100,7 +112,7 @@ class GoogleDocsEndpoint(Enum):
100
112
 
101
113
 
102
114
  class GoogleEndpoint(Enum):
103
- """Google endpoints for BOSA Connector."""
115
+ """Google endpoints for GL Connectors."""
104
116
 
105
117
  INTEGRATIONS = "integrations"
106
118
  USER_HAS_INTEGRATION = "integration-exists"
@@ -109,7 +121,7 @@ class GoogleEndpoint(Enum):
109
121
 
110
122
 
111
123
  class TwitterEndpoint(Enum):
112
- """Twitter endpoints for BOSA Connector."""
124
+ """Twitter endpoints for GL Connectors."""
113
125
 
114
126
  INTEGRATIONS = "integrations"
115
127
  USER_HAS_INTEGRATION = "integration-exists"
@@ -121,7 +133,7 @@ class TwitterEndpoint(Enum):
121
133
 
122
134
 
123
135
  class GoogleMailEndpoint(Enum):
124
- """Google Mail endpoints for BOSA Connector."""
136
+ """Google Mail endpoints for GL Connectors."""
125
137
 
126
138
  INTEGRATIONS = "integrations"
127
139
  USER_HAS_INTEGRATION = "integration-exists"
@@ -1,17 +1,20 @@
1
1
  from _typeshed import Incomplete
2
2
  from enum import Enum, StrEnum
3
3
 
4
- BOSA_API_BASE_URL: Incomplete
5
- BOSA_API_KEY: Incomplete
6
- BOSA_FETCH_MAX_RETRIES: int
4
+ GL_CONNECTORS_BASE_URL: Incomplete
5
+ GL_CONNECTORS_API_KEY: Incomplete
6
+ GL_CONNECTORS_FETCH_MAX_RETRIES: Incomplete
7
+ BOSA_API_BASE_URL = GL_CONNECTORS_BASE_URL
8
+ BOSA_API_KEY = GL_CONNECTORS_API_KEY
9
+ BOSA_FETCH_MAX_RETRIES = GL_CONNECTORS_FETCH_MAX_RETRIES
7
10
 
8
11
  class ToolType(StrEnum):
9
- """Tool types for BOSA Connector."""
12
+ """Tool types for GL Connectors."""
10
13
  GLLM: str
11
14
  LANGCHAIN: str
12
15
 
13
16
  class Action(Enum):
14
- """Actions for BOSA Connector."""
17
+ """Actions for GL Connectors."""
15
18
  GITHUB: str
16
19
  GOOGLE: str
17
20
  GOOGLE_DRIVE: str
@@ -19,7 +22,7 @@ class Action(Enum):
19
22
  TWITTER: str
20
23
 
21
24
  class GitHubEndpoint(Enum):
22
- """GitHub endpoints for BOSA Connector."""
25
+ """GitHub endpoints for GL Connectors."""
23
26
  INTEGRATIONS: str
24
27
  USER_HAS_INTEGRATION: str
25
28
  SUCCESS_AUTHORIZE_CALLBACK: str
@@ -46,7 +49,7 @@ class GitHubEndpoint(Enum):
46
49
  LIST_PROJECTS: str
47
50
 
48
51
  class GoogleDriveEndpoint(Enum):
49
- """Google Drive endpoints for BOSA Connector."""
52
+ """Google Drive endpoints for GL Connectors."""
50
53
  INTEGRATIONS: str
51
54
  USER_HAS_INTEGRATION: str
52
55
  SUCCESS_AUTHORIZE_CALLBACK: str
@@ -69,7 +72,7 @@ class GoogleDriveEndpoint(Enum):
69
72
  DOWNLOAD_FILE: str
70
73
 
71
74
  class GoogleDocsEndpoint(Enum):
72
- """Google Docs endpoints for BOSA Connector."""
75
+ """Google Docs endpoints for GL Connectors."""
73
76
  INTEGRATIONS: str
74
77
  USER_HAS_INTEGRATION: str
75
78
  SUCCESS_AUTHORIZE_CALLBACK: str
@@ -83,14 +86,14 @@ class GoogleDocsEndpoint(Enum):
83
86
  SUMMARIZE_COMMENTS: str
84
87
 
85
88
  class GoogleEndpoint(Enum):
86
- """Google endpoints for BOSA Connector."""
89
+ """Google endpoints for GL Connectors."""
87
90
  INTEGRATIONS: str
88
91
  USER_HAS_INTEGRATION: str
89
92
  SUCCESS_AUTHORIZE_CALLBACK: str
90
93
  USERINFO: str
91
94
 
92
95
  class TwitterEndpoint(Enum):
93
- """Twitter endpoints for BOSA Connector."""
96
+ """Twitter endpoints for GL Connectors."""
94
97
  INTEGRATIONS: str
95
98
  USER_HAS_INTEGRATION: str
96
99
  SUCCESS_AUTHORIZE_CALLBACK: str
@@ -100,7 +103,7 @@ class TwitterEndpoint(Enum):
100
103
  GET_USERS: str
101
104
 
102
105
  class GoogleMailEndpoint(Enum):
103
- """Google Mail endpoints for BOSA Connector."""
106
+ """Google Mail endpoints for GL Connectors."""
104
107
  INTEGRATIONS: str
105
108
  USER_HAS_INTEGRATION: str
106
109
  SUCCESS_AUTHORIZE_CALLBACK: str