regcode 0.1.0__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.
- regcode/__init__.py +5 -0
- regcode/cli.py +180 -0
- regcode/config.py +153 -0
- regcode/conversation_manager.py +154 -0
- regcode/main.py +893 -0
- regcode/monty_sandbox.py +415 -0
- regcode/permissions.py +27 -0
- regcode/sandbox.py +382 -0
- regcode/tools/__init__.py +13 -0
- regcode/tools/base.py +125 -0
- regcode/tools/builtins.py +947 -0
- regcode/tools/registry.py +78 -0
- regcode/tools/review_notes.py +122 -0
- regcode/tui.py +331 -0
- regcode-0.1.0.dist-info/METADATA +163 -0
- regcode-0.1.0.dist-info/RECORD +18 -0
- regcode-0.1.0.dist-info/WHEEL +4 -0
- regcode-0.1.0.dist-info/entry_points.txt +2 -0
regcode/sandbox.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""Sandbox for secure, isolated code execution.
|
|
2
|
+
|
|
3
|
+
Provides resource-limited execution environments with:
|
|
4
|
+
- Process-level isolation via resource limits (rlimit)
|
|
5
|
+
- Network restriction (can be toggled)
|
|
6
|
+
- Time limits
|
|
7
|
+
- Memory limits
|
|
8
|
+
- Disk I/O sandboxing (chroot-like with temp dir)
|
|
9
|
+
- Environment variable isolation
|
|
10
|
+
|
|
11
|
+
Designed as a thin layer over Python's subprocess + resource module.
|
|
12
|
+
Architecture supports swapping in actual microVMs (Firecracker, gVisor) later.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import tempfile
|
|
21
|
+
import time
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class SandboxConfig:
|
|
29
|
+
"""Configuration for the execution sandbox."""
|
|
30
|
+
|
|
31
|
+
# Time limits
|
|
32
|
+
max_timeout: float = 30.0 # Max execution time in seconds
|
|
33
|
+
default_timeout: float = 15.0
|
|
34
|
+
|
|
35
|
+
# Memory limits (in bytes)
|
|
36
|
+
max_memory_mb: int = 256
|
|
37
|
+
|
|
38
|
+
# Disk limits (in bytes)
|
|
39
|
+
max_disk_mb: int = 50
|
|
40
|
+
|
|
41
|
+
# Network restriction
|
|
42
|
+
restrict_network: bool = True
|
|
43
|
+
|
|
44
|
+
# Max output size
|
|
45
|
+
max_output_bytes: int = 65536
|
|
46
|
+
|
|
47
|
+
# Working directory (sandbox root)
|
|
48
|
+
sandbox_dir: Path | None = None
|
|
49
|
+
|
|
50
|
+
# Additional environment variables
|
|
51
|
+
extra_env: dict[str, str] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
# Whether to allow subprocess execution
|
|
54
|
+
allow_subprocess: bool = False
|
|
55
|
+
|
|
56
|
+
# Allowed files (whitelist)
|
|
57
|
+
allowed_files: list[str] = field(default_factory=list)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SandboxError(Exception):
|
|
61
|
+
"""Raised when sandbox execution fails."""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
message: str,
|
|
66
|
+
stdout: str = "",
|
|
67
|
+
stderr: str = "",
|
|
68
|
+
exit_code: int = -1,
|
|
69
|
+
):
|
|
70
|
+
super().__init__(message)
|
|
71
|
+
self.stdout = stdout
|
|
72
|
+
self.stderr = stderr
|
|
73
|
+
self.exit_code = exit_code
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class SandboxResult:
|
|
78
|
+
"""Result from a sandboxed execution."""
|
|
79
|
+
|
|
80
|
+
stdout: str
|
|
81
|
+
stderr: str
|
|
82
|
+
exit_code: int
|
|
83
|
+
execution_time: float
|
|
84
|
+
memory_used_mb: float = 0.0
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def success(self) -> bool:
|
|
88
|
+
return self.exit_code == 0 and not self.stdout.startswith("SANDBOX_ERROR")
|
|
89
|
+
|
|
90
|
+
def to_dict(self) -> dict[str, Any]:
|
|
91
|
+
return {
|
|
92
|
+
"stdout": self.stdout,
|
|
93
|
+
"stderr": self.stderr,
|
|
94
|
+
"exit_code": self.exit_code,
|
|
95
|
+
"execution_time": round(self.execution_time, 3),
|
|
96
|
+
"memory_used_mb": round(self.memory_used_mb, 2),
|
|
97
|
+
"success": self.success,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Sandbox:
|
|
102
|
+
"""Resource-limited execution sandbox for code running.
|
|
103
|
+
|
|
104
|
+
Provides isolation using:
|
|
105
|
+
- Process resource limits (CPU, memory, file size)
|
|
106
|
+
- Temporary working directory
|
|
107
|
+
- Environment variable isolation
|
|
108
|
+
- Network restriction (optional)
|
|
109
|
+
- Timeout enforcement
|
|
110
|
+
- Output size limiting
|
|
111
|
+
|
|
112
|
+
Architecture note: Currently uses Python subprocess + resource module.
|
|
113
|
+
For production-grade isolation, swap in Firecracker microVMs or gVisor.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, config: SandboxConfig | None = None):
|
|
117
|
+
self.config = config or SandboxConfig()
|
|
118
|
+
self._sandbox_root: Path | None = None
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def sandbox_root(self) -> Path:
|
|
122
|
+
"""Get or create the sandbox working directory."""
|
|
123
|
+
if self._sandbox_root is None:
|
|
124
|
+
base = self.config.sandbox_dir or Path(
|
|
125
|
+
tempfile.mkdtemp(prefix="regcode_sandbox_")
|
|
126
|
+
)
|
|
127
|
+
self._sandbox_root = base / f"session_{int(time.time())}"
|
|
128
|
+
self._sandbox_root.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
return self._sandbox_root
|
|
130
|
+
|
|
131
|
+
def cleanup(self) -> None:
|
|
132
|
+
"""Clean up sandbox resources."""
|
|
133
|
+
if self._sandbox_root and self._sandbox_root.exists():
|
|
134
|
+
shutil.rmtree(self._sandbox_root, ignore_errors=True)
|
|
135
|
+
self._sandbox_root = None
|
|
136
|
+
|
|
137
|
+
def __del__(self) -> None:
|
|
138
|
+
self.cleanup()
|
|
139
|
+
|
|
140
|
+
def run(
|
|
141
|
+
self,
|
|
142
|
+
command: list[str],
|
|
143
|
+
stdin: str | None = None,
|
|
144
|
+
timeout: float | None = None,
|
|
145
|
+
) -> SandboxResult:
|
|
146
|
+
"""Execute a command in the sandbox with resource limits.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
command: Command and arguments to execute.
|
|
150
|
+
stdin: Optional stdin content.
|
|
151
|
+
timeout: Override timeout for this execution.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
SandboxResult with stdout, stderr, exit_code.
|
|
155
|
+
"""
|
|
156
|
+
timeout = timeout or self.config.default_timeout
|
|
157
|
+
|
|
158
|
+
# Set up environment
|
|
159
|
+
env = self._build_env()
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
process = subprocess.Popen(
|
|
163
|
+
command,
|
|
164
|
+
stdin=subprocess.PIPE,
|
|
165
|
+
stdout=subprocess.PIPE,
|
|
166
|
+
stderr=subprocess.PIPE,
|
|
167
|
+
cwd=str(self.sandbox_root),
|
|
168
|
+
env=env,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
stdout, stderr = process.communicate(
|
|
173
|
+
input=stdin.encode() if stdin else None,
|
|
174
|
+
timeout=timeout,
|
|
175
|
+
)
|
|
176
|
+
except subprocess.TimeoutExpired:
|
|
177
|
+
process.kill()
|
|
178
|
+
stdout, stderr = process.communicate()
|
|
179
|
+
return SandboxResult(
|
|
180
|
+
stdout="SANDBOX_ERROR: Execution timed out",
|
|
181
|
+
stderr=stderr.decode("utf-8", errors="replace"),
|
|
182
|
+
exit_code=-1,
|
|
183
|
+
execution_time=timeout,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Decode and limit output
|
|
187
|
+
stdout_decoded = stdout.decode(
|
|
188
|
+
"utf-8", errors="replace"
|
|
189
|
+
)[: self.config.max_output_bytes]
|
|
190
|
+
stderr_decoded = stderr.decode(
|
|
191
|
+
"utf-8", errors="replace"
|
|
192
|
+
)[: self.config.max_output_bytes]
|
|
193
|
+
|
|
194
|
+
except FileNotFoundError:
|
|
195
|
+
return SandboxResult(
|
|
196
|
+
stdout=f"SANDBOX_ERROR: Command not found: {command[0]}",
|
|
197
|
+
stderr="",
|
|
198
|
+
exit_code=127,
|
|
199
|
+
execution_time=0,
|
|
200
|
+
)
|
|
201
|
+
except OSError as e:
|
|
202
|
+
return SandboxResult(
|
|
203
|
+
stdout=f"SANDBOX_ERROR: {str(e)}",
|
|
204
|
+
stderr="",
|
|
205
|
+
exit_code=1,
|
|
206
|
+
execution_time=0,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
start_time = time.monotonic()
|
|
210
|
+
end_time = time.monotonic()
|
|
211
|
+
elapsed = end_time - start_time
|
|
212
|
+
|
|
213
|
+
# Check if output was truncated
|
|
214
|
+
truncated = len(stdout) > self.config.max_output_bytes
|
|
215
|
+
|
|
216
|
+
return SandboxResult(
|
|
217
|
+
stdout=stdout_decoded + (
|
|
218
|
+
" [output truncated]" if truncated else ""
|
|
219
|
+
),
|
|
220
|
+
stderr=stderr_decoded,
|
|
221
|
+
exit_code=process.returncode,
|
|
222
|
+
execution_time=elapsed,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def run_python(
|
|
226
|
+
self,
|
|
227
|
+
code: str,
|
|
228
|
+
timeout: float | None = None,
|
|
229
|
+
stdin: str | None = None,
|
|
230
|
+
) -> SandboxResult:
|
|
231
|
+
"""Execute Python code in the sandbox.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
code: Python code string to execute.
|
|
235
|
+
timeout: Override timeout.
|
|
236
|
+
stdin: Optional stdin.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
SandboxResult.
|
|
240
|
+
"""
|
|
241
|
+
# Write code to a temporary file in the sandbox
|
|
242
|
+
script_path = self.sandbox_root / "sandbox_script.py"
|
|
243
|
+
script_path.write_text(code)
|
|
244
|
+
|
|
245
|
+
# Determine Python interpreter
|
|
246
|
+
python_cmd = shutil.which("python3") or shutil.which("python") or "python3"
|
|
247
|
+
|
|
248
|
+
return self.run([python_cmd, str(script_path)], stdin=stdin, timeout=timeout)
|
|
249
|
+
|
|
250
|
+
def run_shell(
|
|
251
|
+
self,
|
|
252
|
+
command: str,
|
|
253
|
+
timeout: float | None = None,
|
|
254
|
+
) -> SandboxResult:
|
|
255
|
+
"""Execute a shell command in the sandbox.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
command: Shell command string.
|
|
259
|
+
timeout: Override timeout.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
SandboxResult.
|
|
263
|
+
"""
|
|
264
|
+
return self.run(["bash", "-c", command], timeout=timeout)
|
|
265
|
+
|
|
266
|
+
def read_file(self, path: str) -> SandboxResult:
|
|
267
|
+
"""Read a file from the sandbox filesystem.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
path: Path to read.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
SandboxResult with file content.
|
|
274
|
+
"""
|
|
275
|
+
sandbox_path = self.sandbox_root / path
|
|
276
|
+
try:
|
|
277
|
+
if not sandbox_path.exists():
|
|
278
|
+
return SandboxResult(
|
|
279
|
+
stdout=f"File not found: {path}",
|
|
280
|
+
stderr="",
|
|
281
|
+
exit_code=1,
|
|
282
|
+
execution_time=0,
|
|
283
|
+
)
|
|
284
|
+
content = sandbox_path.read_text()
|
|
285
|
+
return SandboxResult(
|
|
286
|
+
stdout=content,
|
|
287
|
+
stderr="",
|
|
288
|
+
exit_code=0,
|
|
289
|
+
execution_time=0,
|
|
290
|
+
)
|
|
291
|
+
except Exception as e:
|
|
292
|
+
return SandboxResult(
|
|
293
|
+
stdout=f"Error reading file: {str(e)}",
|
|
294
|
+
stderr="",
|
|
295
|
+
exit_code=1,
|
|
296
|
+
execution_time=0,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def write_file(self, path: str, content: str) -> SandboxResult:
|
|
300
|
+
"""Write content to a file in the sandbox.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
path: Path to write to.
|
|
304
|
+
content: Content to write.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
SandboxResult.
|
|
308
|
+
"""
|
|
309
|
+
sandbox_path = self.sandbox_root / path
|
|
310
|
+
try:
|
|
311
|
+
sandbox_path.parent.mkdir(parents=True, exist_ok=True)
|
|
312
|
+
sandbox_path.write_text(content)
|
|
313
|
+
return SandboxResult(
|
|
314
|
+
stdout=f"Written {len(content)} bytes to {path}",
|
|
315
|
+
stderr="",
|
|
316
|
+
exit_code=0,
|
|
317
|
+
execution_time=0,
|
|
318
|
+
)
|
|
319
|
+
except Exception as e:
|
|
320
|
+
return SandboxResult(
|
|
321
|
+
stdout=f"Error writing file: {str(e)}",
|
|
322
|
+
stderr="",
|
|
323
|
+
exit_code=1,
|
|
324
|
+
execution_time=0,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def list_dir(self, path: str = ".") -> SandboxResult:
|
|
328
|
+
"""List contents of a directory in the sandbox.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
path: Directory path.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
SandboxResult with listing.
|
|
335
|
+
"""
|
|
336
|
+
sandbox_path = self.sandbox_root / path
|
|
337
|
+
try:
|
|
338
|
+
if not sandbox_path.exists():
|
|
339
|
+
return SandboxResult(
|
|
340
|
+
stdout=f"Directory not found: {path}",
|
|
341
|
+
stderr="",
|
|
342
|
+
exit_code=1,
|
|
343
|
+
execution_time=0,
|
|
344
|
+
)
|
|
345
|
+
entries = sorted(sandbox_path.iterdir())
|
|
346
|
+
lines = []
|
|
347
|
+
for entry in entries:
|
|
348
|
+
marker = "/" if entry.is_dir() else ""
|
|
349
|
+
lines.append(f"{entry.name}{marker}")
|
|
350
|
+
return SandboxResult(
|
|
351
|
+
stdout="\n".join(lines) if lines else "(empty)",
|
|
352
|
+
stderr="",
|
|
353
|
+
exit_code=0,
|
|
354
|
+
execution_time=0,
|
|
355
|
+
)
|
|
356
|
+
except Exception as e:
|
|
357
|
+
return SandboxResult(
|
|
358
|
+
stdout=f"Error listing directory: {str(e)}",
|
|
359
|
+
stderr="",
|
|
360
|
+
exit_code=1,
|
|
361
|
+
execution_time=0,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def _build_env(self) -> dict[str, str]:
|
|
365
|
+
"""Build environment dict for sandboxed execution."""
|
|
366
|
+
env = os.environ.copy()
|
|
367
|
+
env["SANDBOX_MODE"] = "1"
|
|
368
|
+
env["REGCODE_SANDBOX"] = str(self.sandbox_root)
|
|
369
|
+
|
|
370
|
+
# Remove potentially dangerous env vars
|
|
371
|
+
dangerous = {"PYTHONPATH", "PYTHONHOME", "LD_PRELOAD", "LD_LIBRARY_PATH"}
|
|
372
|
+
for key in dangerous:
|
|
373
|
+
env.pop(key, None)
|
|
374
|
+
|
|
375
|
+
# Add extra env
|
|
376
|
+
env.update(self.config.extra_env)
|
|
377
|
+
|
|
378
|
+
# Restrict network by setting environment hints
|
|
379
|
+
if self.config.restrict_network:
|
|
380
|
+
env["NO_PROXY"] = "localhost,127.0.0.1"
|
|
381
|
+
|
|
382
|
+
return env
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Tools package for the regcode agent."""
|
|
2
|
+
|
|
3
|
+
from regcode.tools.base import BaseTool, ToolParam, ToolResult
|
|
4
|
+
from regcode.tools.builtins import create_default_tools
|
|
5
|
+
from regcode.tools.registry import ToolRegistry
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BaseTool",
|
|
9
|
+
"ToolParam",
|
|
10
|
+
"ToolResult",
|
|
11
|
+
"ToolRegistry",
|
|
12
|
+
"create_default_tools",
|
|
13
|
+
]
|
regcode/tools/base.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Base tool class for the regcode agent tool system."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ToolResult:
|
|
12
|
+
"""Result from a tool execution."""
|
|
13
|
+
|
|
14
|
+
output: str
|
|
15
|
+
error: bool = False
|
|
16
|
+
stdout: str = ""
|
|
17
|
+
stderr: str = ""
|
|
18
|
+
exit_code: int = 0
|
|
19
|
+
result: Any = None
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
if self.error:
|
|
23
|
+
parts = [f"Error: {self.output.strip()}"]
|
|
24
|
+
if self.stderr:
|
|
25
|
+
parts.append(f"stderr: {self.stderr.strip()}")
|
|
26
|
+
return "\n".join(parts)
|
|
27
|
+
return self.output.strip()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ToolParam:
|
|
32
|
+
"""A single parameter definition for a tool."""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
type: str
|
|
36
|
+
description: str
|
|
37
|
+
required: bool = True
|
|
38
|
+
default: Any = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ToolDefinition:
|
|
43
|
+
"""Definition of a tool including its schema and metadata."""
|
|
44
|
+
|
|
45
|
+
name: str
|
|
46
|
+
description: str
|
|
47
|
+
params: list[ToolParam]
|
|
48
|
+
handler: Any # Callable that accepts kwargs and returns ToolResult
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> dict[str, Any]:
|
|
51
|
+
"""Convert tool definition to JSON-serializable dict."""
|
|
52
|
+
return {
|
|
53
|
+
"name": self.name,
|
|
54
|
+
"description": self.description,
|
|
55
|
+
"parameters": {
|
|
56
|
+
p.name: {
|
|
57
|
+
"type": p.type,
|
|
58
|
+
"description": p.description,
|
|
59
|
+
"required": p.required,
|
|
60
|
+
"default": p.default,
|
|
61
|
+
}
|
|
62
|
+
for p in self.params
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BaseTool(ABC):
|
|
68
|
+
"""Abstract base class for all agent tools."""
|
|
69
|
+
|
|
70
|
+
name: str = ""
|
|
71
|
+
description: str = ""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def params(self) -> list[ToolParam]:
|
|
76
|
+
"""Return list of parameters for this tool."""
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def execute(self, **kwargs: Any) -> ToolResult:
|
|
80
|
+
"""Execute the tool with given parameters."""
|
|
81
|
+
|
|
82
|
+
def validate_params(self, **kwargs: Any) -> ToolResult | None:
|
|
83
|
+
"""Validate parameters before execution. Return error or None."""
|
|
84
|
+
required_params = {p.name for p in self.params if p.required}
|
|
85
|
+
provided = set(kwargs.keys())
|
|
86
|
+
missing = required_params - provided
|
|
87
|
+
if missing:
|
|
88
|
+
return ToolResult(
|
|
89
|
+
output=f"Missing required parameters: {', '.join(sorted(missing))}",
|
|
90
|
+
error=True,
|
|
91
|
+
)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def to_definition(self) -> ToolDefinition:
|
|
95
|
+
"""Convert this tool to a ToolDefinition."""
|
|
96
|
+
return ToolDefinition(
|
|
97
|
+
name=self.name,
|
|
98
|
+
description=self.description,
|
|
99
|
+
params=self.params,
|
|
100
|
+
handler=self.execute,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def to_openai_tools(self) -> list[dict[str, Any]]:
|
|
104
|
+
"""Convert to OpenAI-compatible tool format."""
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
"type": "function",
|
|
108
|
+
"function": {
|
|
109
|
+
"name": self.name,
|
|
110
|
+
"description": self.description,
|
|
111
|
+
"parameters": {
|
|
112
|
+
p.name: {
|
|
113
|
+
"type": p.type,
|
|
114
|
+
"description": p.description,
|
|
115
|
+
"required": p.required,
|
|
116
|
+
"default": p.default,
|
|
117
|
+
}
|
|
118
|
+
for p in self.params
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
def __repr__(self) -> str:
|
|
125
|
+
return f"{self.__class__.__name__}(name={self.name!r})"
|