sandboxy 0.0.1__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 (60) hide show
  1. sandboxy/__init__.py +3 -0
  2. sandboxy/agents/__init__.py +21 -0
  3. sandboxy/agents/base.py +66 -0
  4. sandboxy/agents/llm_prompt.py +308 -0
  5. sandboxy/agents/loader.py +222 -0
  6. sandboxy/api/__init__.py +5 -0
  7. sandboxy/api/app.py +76 -0
  8. sandboxy/api/routes/__init__.py +1 -0
  9. sandboxy/api/routes/agents.py +92 -0
  10. sandboxy/api/routes/local.py +1388 -0
  11. sandboxy/api/routes/tools.py +106 -0
  12. sandboxy/cli/__init__.py +1 -0
  13. sandboxy/cli/main.py +1196 -0
  14. sandboxy/cli/type_detector.py +48 -0
  15. sandboxy/config.py +49 -0
  16. sandboxy/core/__init__.py +1 -0
  17. sandboxy/core/async_runner.py +824 -0
  18. sandboxy/core/mdl_parser.py +441 -0
  19. sandboxy/core/runner.py +599 -0
  20. sandboxy/core/safe_eval.py +165 -0
  21. sandboxy/core/state.py +234 -0
  22. sandboxy/datasets/__init__.py +20 -0
  23. sandboxy/datasets/loader.py +193 -0
  24. sandboxy/datasets/runner.py +442 -0
  25. sandboxy/errors.py +166 -0
  26. sandboxy/local/context.py +235 -0
  27. sandboxy/local/results.py +173 -0
  28. sandboxy/logging.py +31 -0
  29. sandboxy/mcp/__init__.py +25 -0
  30. sandboxy/mcp/client.py +360 -0
  31. sandboxy/mcp/wrapper.py +99 -0
  32. sandboxy/providers/__init__.py +34 -0
  33. sandboxy/providers/anthropic_provider.py +271 -0
  34. sandboxy/providers/base.py +123 -0
  35. sandboxy/providers/http_client.py +101 -0
  36. sandboxy/providers/openai_provider.py +282 -0
  37. sandboxy/providers/openrouter.py +958 -0
  38. sandboxy/providers/registry.py +199 -0
  39. sandboxy/scenarios/__init__.py +11 -0
  40. sandboxy/scenarios/comparison.py +491 -0
  41. sandboxy/scenarios/loader.py +262 -0
  42. sandboxy/scenarios/runner.py +468 -0
  43. sandboxy/scenarios/unified.py +1434 -0
  44. sandboxy/session/__init__.py +21 -0
  45. sandboxy/session/manager.py +278 -0
  46. sandboxy/tools/__init__.py +34 -0
  47. sandboxy/tools/base.py +127 -0
  48. sandboxy/tools/loader.py +270 -0
  49. sandboxy/tools/yaml_tools.py +708 -0
  50. sandboxy/ui/__init__.py +27 -0
  51. sandboxy/ui/dist/assets/index-CgAkYWrJ.css +1 -0
  52. sandboxy/ui/dist/assets/index-D4zoGFcr.js +347 -0
  53. sandboxy/ui/dist/index.html +14 -0
  54. sandboxy/utils/__init__.py +3 -0
  55. sandboxy/utils/time.py +20 -0
  56. sandboxy-0.0.1.dist-info/METADATA +241 -0
  57. sandboxy-0.0.1.dist-info/RECORD +60 -0
  58. sandboxy-0.0.1.dist-info/WHEEL +4 -0
  59. sandboxy-0.0.1.dist-info/entry_points.txt +3 -0
  60. sandboxy-0.0.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,270 @@
1
+ """Tool loader - dynamically loads tool implementations from specs.
2
+
3
+ This module handles:
4
+ - Loading tool specifications from YAML files
5
+ - Creating tool instances from environment configuration
6
+ - Loading YAML tool libraries
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import importlib
12
+ import logging
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ import yaml
17
+
18
+ from sandboxy.core.state import EnvConfig
19
+ from sandboxy.tools.base import BaseTool, Tool, ToolConfig
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Default directories to search for tool specs
24
+ TOOLS_DIRS = [
25
+ Path("tools/core"),
26
+ Path("tools/community"),
27
+ ]
28
+
29
+ # Default directories for YAML tool libraries
30
+ YAML_TOOL_DIRS = [
31
+ Path("tools"),
32
+ Path("sandboxy/tools/libraries"),
33
+ ]
34
+
35
+
36
+ def get_tool_dirs() -> list[Path]:
37
+ """Get tool directories, including local context if available.
38
+
39
+ Returns:
40
+ List of directories to search for YAML tool libraries.
41
+ """
42
+ from sandboxy.local.context import get_local_context, is_local_mode
43
+
44
+ dirs: list[Path] = []
45
+
46
+ if is_local_mode():
47
+ ctx = get_local_context()
48
+ if ctx and ctx.tools_dir.exists():
49
+ dirs.append(ctx.tools_dir)
50
+
51
+ dirs.extend(YAML_TOOL_DIRS)
52
+ return dirs
53
+
54
+
55
+ # Built-in tool mappings (type -> module:class)
56
+ BUILTIN_TOOLS: dict[str, str] = {
57
+ "mock_lemonade": "sandboxy.tools.mock_lemonade:MockLemonadeTool",
58
+ "mock_store": "sandboxy.tools.mock_store:MockStoreTool",
59
+ "mock_wedding": "sandboxy.tools.mock_wedding:MockWeddingTool",
60
+ "mock_customer_support": "sandboxy.tools.mock_customer_support:MockCustomerSupportTool",
61
+ "mock_mechanic": "sandboxy.tools.mock_mechanic:MockMechanicTool",
62
+ }
63
+
64
+
65
+ def _load_tool_specs(dirs: list[Path] | None = None) -> dict[str, dict[str, Any]]:
66
+ """Load tool specifications from YAML files.
67
+
68
+ Args:
69
+ dirs: Directories to search for tool specs. Uses TOOLS_DIRS if None.
70
+
71
+ Returns:
72
+ Dictionary mapping tool type to spec.
73
+ """
74
+ if dirs is None:
75
+ dirs = TOOLS_DIRS
76
+
77
+ specs: dict[str, dict[str, Any]] = {}
78
+ for d in dirs:
79
+ if not d.exists():
80
+ continue
81
+ for path in d.glob("**/*.yaml"):
82
+ try:
83
+ raw = yaml.safe_load(path.read_text())
84
+ if raw and "type" in raw:
85
+ specs[raw["type"]] = raw
86
+ except yaml.YAMLError as e:
87
+ logger.warning("Failed to parse tool spec %s: %s", path, e)
88
+ continue
89
+ for path in d.glob("**/*.yml"):
90
+ try:
91
+ raw = yaml.safe_load(path.read_text())
92
+ if raw and "type" in raw:
93
+ specs[raw["type"]] = raw
94
+ except yaml.YAMLError as e:
95
+ logger.warning("Failed to parse tool spec %s: %s", path, e)
96
+ continue
97
+
98
+ return specs
99
+
100
+
101
+ def load_tool_class(module_path: str) -> type[BaseTool]:
102
+ """Load a tool class from a module path.
103
+
104
+ Args:
105
+ module_path: Path in format "module.path:ClassName".
106
+
107
+ Returns:
108
+ Tool class.
109
+
110
+ Raises:
111
+ ImportError: If module cannot be imported.
112
+ AttributeError: If class not found in module.
113
+ """
114
+ module_name, class_name = module_path.split(":")
115
+ mod = importlib.import_module(module_name)
116
+ return getattr(mod, class_name)
117
+
118
+
119
+ class ToolLoader:
120
+ """Loader for creating tool instances from environment config."""
121
+
122
+ @classmethod
123
+ def from_env_config(
124
+ cls,
125
+ env: EnvConfig,
126
+ tool_dirs: list[Path] | None = None,
127
+ ) -> dict[str, Tool]:
128
+ """Create tool instances from environment configuration.
129
+
130
+ Args:
131
+ env: Environment configuration containing tool references.
132
+ tool_dirs: Optional directories to search for tool specs.
133
+
134
+ Returns:
135
+ Dictionary mapping tool name to tool instance.
136
+
137
+ Raises:
138
+ ValueError: If a tool type cannot be found.
139
+ """
140
+ specs = _load_tool_specs(tool_dirs)
141
+ tools: dict[str, Tool] = {}
142
+
143
+ for tool_ref in env.tools:
144
+ # First check built-in tools
145
+ if tool_ref.type in BUILTIN_TOOLS:
146
+ module_path = BUILTIN_TOOLS[tool_ref.type]
147
+ # Then check loaded specs
148
+ elif tool_ref.type in specs:
149
+ spec = specs[tool_ref.type]
150
+ if "impl" not in spec or "module" not in spec["impl"]:
151
+ msg = f"Tool spec for '{tool_ref.type}' missing impl.module"
152
+ raise ValueError(msg)
153
+ module_path = spec["impl"]["module"]
154
+ else:
155
+ msg = f"Unknown tool type: {tool_ref.type}"
156
+ raise ValueError(msg)
157
+
158
+ # Load and instantiate the tool class
159
+ tool_cls = load_tool_class(module_path)
160
+ config = ToolConfig(
161
+ name=tool_ref.name,
162
+ type=tool_ref.type,
163
+ description=tool_ref.description,
164
+ config=tool_ref.config,
165
+ )
166
+ tools[tool_ref.name] = tool_cls(config)
167
+
168
+ return tools
169
+
170
+ @classmethod
171
+ def get_available_tools(cls, tool_dirs: list[Path] | None = None) -> list[str]:
172
+ """Get list of available tool types.
173
+
174
+ Args:
175
+ tool_dirs: Optional directories to search for tool specs.
176
+
177
+ Returns:
178
+ List of available tool type names.
179
+ """
180
+ specs = _load_tool_specs(tool_dirs)
181
+ available = list(BUILTIN_TOOLS.keys())
182
+ available.extend(specs.keys())
183
+
184
+ # Also list YAML tool libraries
185
+ yaml_libs = get_yaml_tool_libraries()
186
+ available.extend(yaml_libs)
187
+
188
+ return sorted(set(available))
189
+
190
+
191
+ # -----------------------------------------------------------------------------
192
+ # YAML Tool Library Support
193
+ # -----------------------------------------------------------------------------
194
+
195
+
196
+ def get_yaml_tool_libraries(tool_dirs: list[Path] | None = None) -> list[str]:
197
+ """Get list of available YAML tool library names.
198
+
199
+ Args:
200
+ tool_dirs: Directories to search. Uses YAML_TOOL_DIRS if None.
201
+
202
+ Returns:
203
+ List of library names (without extension).
204
+ """
205
+ if tool_dirs is None:
206
+ tool_dirs = YAML_TOOL_DIRS
207
+
208
+ libraries: list[str] = []
209
+ for d in tool_dirs:
210
+ if not d.exists():
211
+ continue
212
+ for ext in (".yml", ".yaml"):
213
+ for path in d.glob(f"*{ext}"):
214
+ # Only include files that start with mock_ or have tools: key
215
+ try:
216
+ raw = yaml.safe_load(path.read_text())
217
+ if raw and ("tools" in raw or path.stem.startswith("mock_")):
218
+ libraries.append(path.stem)
219
+ except yaml.YAMLError as e:
220
+ logger.warning("Failed to parse tool library %s: %s", path, e)
221
+ continue
222
+
223
+ return libraries
224
+
225
+
226
+ def load_yaml_tools_from_scenario(
227
+ scenario_data: dict[str, Any],
228
+ tool_dirs: list[Path] | None = None,
229
+ ) -> dict[str, Tool]:
230
+ """Load YAML-defined tools from a scenario definition.
231
+
232
+ This handles both `tools_from` library references and inline `tools` definitions.
233
+ Use this when loading scenarios that define their own tools.
234
+
235
+ Args:
236
+ scenario_data: Parsed scenario YAML containing tools and/or tools_from
237
+ tool_dirs: Optional directories to search for tool libraries
238
+
239
+ Returns:
240
+ Dictionary of tool name to tool instance
241
+ """
242
+ from sandboxy.tools.yaml_tools import load_scenario_tools
243
+
244
+ if tool_dirs is None:
245
+ tool_dirs = YAML_TOOL_DIRS
246
+
247
+ return load_scenario_tools(scenario_data, tool_dirs)
248
+
249
+
250
+ def load_yaml_tool_library(
251
+ library_name: str,
252
+ tool_dirs: list[Path] | None = None,
253
+ ) -> dict[str, Tool]:
254
+ """Load all tools from a YAML tool library.
255
+
256
+ Args:
257
+ library_name: Name of the library (without extension)
258
+ tool_dirs: Optional directories to search
259
+
260
+ Returns:
261
+ Dictionary of tool name to tool instance
262
+ """
263
+ from sandboxy.tools.yaml_tools import YamlToolLoader
264
+
265
+ if tool_dirs is None:
266
+ tool_dirs = YAML_TOOL_DIRS
267
+
268
+ loader = YamlToolLoader(tool_dirs)
269
+ library = loader.load_library(library_name)
270
+ return loader.create_tool_instances(library.tools)