meshagent-tools 0.24.1__tar.gz → 0.24.3__tar.gz
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.
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/CHANGELOG.md +6 -0
- {meshagent_tools-0.24.1/meshagent_tools.egg-info → meshagent_tools-0.24.3}/PKG-INFO +2 -2
- meshagent_tools-0.24.3/meshagent/tools/script.py +313 -0
- meshagent_tools-0.24.3/meshagent/tools/storage.py +744 -0
- meshagent_tools-0.24.3/meshagent/tools/version.py +1 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3/meshagent_tools.egg-info}/PKG-INFO +2 -2
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent_tools.egg-info/SOURCES.txt +1 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent_tools.egg-info/requires.txt +1 -1
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/pyproject.toml +1 -1
- meshagent_tools-0.24.1/meshagent/tools/storage.py +0 -210
- meshagent_tools-0.24.1/meshagent/tools/version.py +0 -1
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/LICENSE +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/MANIFEST.in +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/README.md +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/__init__.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/blob.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/config.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/database.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/datetime.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/discovery.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/document_tools.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/hosting.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/multi_tool.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/pydantic.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/strict_schema.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/tool.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/toolkit.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/uuid.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent/tools/web_toolkit.py +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent_tools.egg-info/dependency_links.txt +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/meshagent_tools.egg-info/top_level.txt +0 -0
- {meshagent_tools-0.24.1 → meshagent_tools-0.24.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-tools
|
|
3
|
-
Version: 0.24.
|
|
3
|
+
Version: 0.24.3
|
|
4
4
|
Summary: Tools for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -12,7 +12,7 @@ License-File: LICENSE
|
|
|
12
12
|
Requires-Dist: pyjwt~=2.10
|
|
13
13
|
Requires-Dist: pytest~=8.4
|
|
14
14
|
Requires-Dist: pytest-asyncio~=0.26
|
|
15
|
-
Requires-Dist: meshagent-api~=0.24.
|
|
15
|
+
Requires-Dist: meshagent-api~=0.24.3
|
|
16
16
|
Requires-Dist: aiohttp~=3.10
|
|
17
17
|
Requires-Dist: opentelemetry-distro~=0.54b1
|
|
18
18
|
Dynamic: license-file
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from meshagent.api import RoomClient
|
|
2
|
+
from meshagent.tools import Toolkit, ToolContext, Tool
|
|
3
|
+
|
|
4
|
+
from meshagent.agents.adapter import (
|
|
5
|
+
ToolkitBuilder,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from meshagent.api.specs.service import ContainerMountSpec, RoomStorageMountSpec
|
|
9
|
+
from typing import Literal
|
|
10
|
+
import os
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import asyncio
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("script_tool")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
DEFAULT_CONTAINER_MOUNT_SPEC = ContainerMountSpec(
|
|
21
|
+
room=[RoomStorageMountSpec(path="/data")]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ScriptToolConfig(BaseModel):
|
|
26
|
+
name: Literal["script"] = "script"
|
|
27
|
+
service_id: Optional[str] = None
|
|
28
|
+
commands: list[str] = None
|
|
29
|
+
description: Optional[str] = None
|
|
30
|
+
title: Optional[str] = None
|
|
31
|
+
tool_name: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ScriptToolkitBuilder(ToolkitBuilder):
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
name: str = "script",
|
|
39
|
+
commands: Optional[list[str]] = None,
|
|
40
|
+
working_directory: Optional[str] = None,
|
|
41
|
+
image: Optional[str] = "python:3.13",
|
|
42
|
+
mounts: Optional[ContainerMountSpec] = DEFAULT_CONTAINER_MOUNT_SPEC,
|
|
43
|
+
input_schema: Optional[dict] = None,
|
|
44
|
+
):
|
|
45
|
+
super().__init__(name=name, type=ScriptToolConfig)
|
|
46
|
+
|
|
47
|
+
self.working_directory = working_directory
|
|
48
|
+
self.image = image
|
|
49
|
+
self.mounts = mounts
|
|
50
|
+
self.commands = commands
|
|
51
|
+
self.input_schema = input_schema
|
|
52
|
+
|
|
53
|
+
async def make(self, *, room: RoomClient, model: str, config: ScriptToolConfig):
|
|
54
|
+
return Toolkit(
|
|
55
|
+
name=self.name,
|
|
56
|
+
tools=[
|
|
57
|
+
ScriptTool(
|
|
58
|
+
name=config.tool_name,
|
|
59
|
+
description=config.description,
|
|
60
|
+
title=config.title,
|
|
61
|
+
service_id=config.service_id,
|
|
62
|
+
working_directory=self.working_directory,
|
|
63
|
+
image=self.image,
|
|
64
|
+
commands=self.commands or config.commands,
|
|
65
|
+
mounts=self.mounts,
|
|
66
|
+
input_schema=self.input_schema,
|
|
67
|
+
)
|
|
68
|
+
],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ScriptTool(Tool):
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
*,
|
|
76
|
+
name: str,
|
|
77
|
+
commands: list[str],
|
|
78
|
+
description: Optional[str] = None,
|
|
79
|
+
title: Optional[str] = None,
|
|
80
|
+
service_id: Optional[str] = None,
|
|
81
|
+
working_directory: Optional[str] = None,
|
|
82
|
+
image: Optional[str] = "python:3.13",
|
|
83
|
+
mounts: Optional[ContainerMountSpec] = DEFAULT_CONTAINER_MOUNT_SPEC,
|
|
84
|
+
env: Optional[dict[str, str]] = None,
|
|
85
|
+
input_schema: Optional[dict] = None,
|
|
86
|
+
max_output_length: int = 32000,
|
|
87
|
+
timeout_ms: int = 30 * 60 * 1000,
|
|
88
|
+
):
|
|
89
|
+
self.service_id = service_id
|
|
90
|
+
self.working_directory = working_directory
|
|
91
|
+
self.image = image
|
|
92
|
+
self.mounts = mounts
|
|
93
|
+
self._container_id = None
|
|
94
|
+
self.env = env
|
|
95
|
+
self.max_output_length = max_output_length
|
|
96
|
+
self.timeout_ms = timeout_ms
|
|
97
|
+
self.service_id = service_id
|
|
98
|
+
self.commands = commands
|
|
99
|
+
|
|
100
|
+
super().__init__(
|
|
101
|
+
name=name,
|
|
102
|
+
description=description,
|
|
103
|
+
title=title,
|
|
104
|
+
input_schema=input_schema
|
|
105
|
+
or {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"required": ["prompt"],
|
|
108
|
+
"additionalProperties": False,
|
|
109
|
+
"properties": {"prompt": {"type": "string"}},
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
async def execute(
|
|
114
|
+
self,
|
|
115
|
+
context: ToolContext,
|
|
116
|
+
**kwargs,
|
|
117
|
+
):
|
|
118
|
+
merged_env = {**os.environ}
|
|
119
|
+
|
|
120
|
+
results = []
|
|
121
|
+
encoding = os.device_encoding(1) or "utf-8"
|
|
122
|
+
|
|
123
|
+
left = self.max_output_length
|
|
124
|
+
|
|
125
|
+
def limit(s: str):
|
|
126
|
+
nonlocal left
|
|
127
|
+
if left is not None:
|
|
128
|
+
s = s[0:left]
|
|
129
|
+
left -= len(s)
|
|
130
|
+
return s
|
|
131
|
+
else:
|
|
132
|
+
return s
|
|
133
|
+
|
|
134
|
+
timeout = float(self.timeout_ms) / 1000.0 if self.timeout_ms else 20 * 1000.0
|
|
135
|
+
|
|
136
|
+
if self.image is not None or self.service_id is not None:
|
|
137
|
+
running = False
|
|
138
|
+
|
|
139
|
+
if self._container_id:
|
|
140
|
+
# make sure container is still running
|
|
141
|
+
|
|
142
|
+
for c in await context.room.containers.list():
|
|
143
|
+
if c.id == self._container_id or (
|
|
144
|
+
self.service_id is not None and c.service_id == self.service_id
|
|
145
|
+
):
|
|
146
|
+
running = True
|
|
147
|
+
|
|
148
|
+
if not running:
|
|
149
|
+
if self.service_id is not None:
|
|
150
|
+
env = {}
|
|
151
|
+
|
|
152
|
+
for k, v in kwargs.items():
|
|
153
|
+
env[k.upper()] = v
|
|
154
|
+
|
|
155
|
+
logger.info(
|
|
156
|
+
f"executing shell script in container with env {env}"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
self._container_id = await context.room.containers.run_service(
|
|
160
|
+
service_id=self.service_id,
|
|
161
|
+
env=env,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
else:
|
|
165
|
+
self._container_id = await context.room.containers.run(
|
|
166
|
+
command="sleep infinity",
|
|
167
|
+
image=self.image,
|
|
168
|
+
mounts=self.mounts,
|
|
169
|
+
writable_root_fs=True,
|
|
170
|
+
env=self.env,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
container_id = self._container_id
|
|
174
|
+
commands = self.commands
|
|
175
|
+
logger.info(
|
|
176
|
+
f"executing shell script in container {container_id} with timeout {timeout}: {commands}"
|
|
177
|
+
)
|
|
178
|
+
import shlex
|
|
179
|
+
|
|
180
|
+
for line in commands:
|
|
181
|
+
try:
|
|
182
|
+
# TODO: what if container start fails
|
|
183
|
+
|
|
184
|
+
exec = await context.room.containers.exec(
|
|
185
|
+
container_id=container_id,
|
|
186
|
+
command=shlex.join(["bash", "-c", line]),
|
|
187
|
+
tty=False,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
stdout = bytearray()
|
|
191
|
+
stderr = bytearray()
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
async with asyncio.timeout(timeout):
|
|
195
|
+
async for se in exec.stderr():
|
|
196
|
+
stderr.extend(se)
|
|
197
|
+
|
|
198
|
+
async for so in exec.stdout():
|
|
199
|
+
stdout.extend(so)
|
|
200
|
+
|
|
201
|
+
exit_code = await exec.result
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
"outcome": {
|
|
205
|
+
"type": "exit",
|
|
206
|
+
"exit_code": exit_code,
|
|
207
|
+
},
|
|
208
|
+
"stdout": stdout.decode(),
|
|
209
|
+
"stderr": stderr.decode(),
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
except asyncio.TimeoutError:
|
|
213
|
+
logger.info(f"The command timed out after {timeout}s")
|
|
214
|
+
await exec.kill()
|
|
215
|
+
|
|
216
|
+
results.append(
|
|
217
|
+
{
|
|
218
|
+
"outcome": {"type": "timeout"},
|
|
219
|
+
"stdout": limit(
|
|
220
|
+
stdout.decode(encoding, errors="replace")
|
|
221
|
+
),
|
|
222
|
+
"stderr": limit(
|
|
223
|
+
stderr.decode(encoding, errors="replace")
|
|
224
|
+
),
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
except Exception as ex:
|
|
230
|
+
results.append(
|
|
231
|
+
{
|
|
232
|
+
"outcome": {
|
|
233
|
+
"type": "exit",
|
|
234
|
+
"exit_code": 1,
|
|
235
|
+
},
|
|
236
|
+
"stdout": "",
|
|
237
|
+
"stderr": f"{ex}",
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
except Exception as ex:
|
|
243
|
+
results.append(
|
|
244
|
+
{
|
|
245
|
+
"outcome": {
|
|
246
|
+
"type": "exit",
|
|
247
|
+
"exit_code": 1,
|
|
248
|
+
},
|
|
249
|
+
"stdout": "",
|
|
250
|
+
"stderr": f"{ex}",
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
break
|
|
254
|
+
else:
|
|
255
|
+
for line in self.commands:
|
|
256
|
+
logger.info(f"executing command {line} with timeout: {timeout}s")
|
|
257
|
+
|
|
258
|
+
# Spawn the process
|
|
259
|
+
try:
|
|
260
|
+
import shlex
|
|
261
|
+
|
|
262
|
+
proc = await asyncio.create_subprocess_shell(
|
|
263
|
+
shlex.join(["bash", "-c", line]),
|
|
264
|
+
cwd=self.working_directory or os.getcwd(),
|
|
265
|
+
env=merged_env,
|
|
266
|
+
stdout=asyncio.subprocess.PIPE,
|
|
267
|
+
stderr=asyncio.subprocess.PIPE,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
stdout, stderr = await asyncio.wait_for(
|
|
271
|
+
proc.communicate(),
|
|
272
|
+
timeout=timeout,
|
|
273
|
+
)
|
|
274
|
+
except asyncio.TimeoutError:
|
|
275
|
+
logger.info(f"The command timed out after {timeout}s")
|
|
276
|
+
proc.kill() # send SIGKILL / TerminateProcess
|
|
277
|
+
|
|
278
|
+
stdout, stderr = await proc.communicate()
|
|
279
|
+
|
|
280
|
+
results.append(
|
|
281
|
+
{
|
|
282
|
+
"outcome": {"type": "timeout"},
|
|
283
|
+
"stdout": limit(stdout.decode(encoding, errors="replace")),
|
|
284
|
+
"stderr": limit(stderr.decode(encoding, errors="replace")),
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
except Exception as ex:
|
|
290
|
+
results.append(
|
|
291
|
+
{
|
|
292
|
+
"outcome": {
|
|
293
|
+
"type": "exit",
|
|
294
|
+
"exit_code": 1,
|
|
295
|
+
},
|
|
296
|
+
"stdout": "",
|
|
297
|
+
"stderr": f"{ex}",
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
results.append(
|
|
303
|
+
{
|
|
304
|
+
"outcome": {
|
|
305
|
+
"type": "exit",
|
|
306
|
+
"exit_code": proc.returncode,
|
|
307
|
+
},
|
|
308
|
+
"stdout": limit(stdout.decode(encoding, errors="replace")),
|
|
309
|
+
"stderr": limit(stderr.decode(encoding, errors="replace")),
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return {"results": results}
|