voxagent 0.2.4__py3-none-any.whl → 0.2.5__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.
voxagent/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.2.4"
1
+ __version__ = "0.2.5"
2
2
  __version_info__ = tuple(int(x) for x in __version__.split("."))
voxagent/code/__init__.py CHANGED
@@ -30,6 +30,7 @@ from voxagent.code.tool_proxy import (
30
30
  ToolProxyServer,
31
31
  create_tool_proxy_pair,
32
32
  )
33
+ from voxagent.code.query import QueryResult
33
34
 
34
35
  __all__ = [
35
36
  # Sandbox
@@ -52,4 +53,6 @@ __all__ = [
52
53
  "ToolProxyClient",
53
54
  "ToolProxyServer",
54
55
  "create_tool_proxy_pair",
56
+ # Query
57
+ "QueryResult",
55
58
  ]
voxagent/code/agent.py CHANGED
@@ -7,9 +7,12 @@ that calls tools via the virtual filesystem.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import asyncio
11
+ import multiprocessing
10
12
  from typing import TYPE_CHECKING, Any
11
13
 
12
14
  from voxagent.code.sandbox import SubprocessSandbox, SandboxResult
15
+ from voxagent.code.tool_proxy import ToolProxyServer
13
16
  from voxagent.code.virtual_fs import VirtualFilesystem, ToolRegistry
14
17
  from voxagent.tools.definition import ToolDefinition
15
18
 
@@ -32,6 +35,21 @@ You have access to a single tool: `execute_code`. Use it to write Python code th
32
35
  - `call_tool(category, tool_name, **kwargs)` - Call a tool with arguments
33
36
  - `print(*args)` - Output results (captured and returned to you)
34
37
 
38
+ ### Query Functions (for efficient data processing)
39
+ - `query(data)` - Wrap tool result in QueryResult for chaining
40
+ - `tree(path?)` - Show full tool structure at a glance
41
+ - `search(keyword)` - Find tools by name
42
+
43
+ ### QueryResult Methods (chainable)
44
+ - `.filter(**patterns)` - Filter by regex patterns on fields
45
+ - `.map(fields)` - Extract specific fields
46
+ - `.reduce(by?, count?)` - Aggregate/group results
47
+ - `.first()` - Get first result
48
+ - `.last()` - Get last result
49
+ - `.each(fn)` - Apply function to each result
50
+ - `.sort(by, reverse?)` - Sort results by field
51
+ - `.unique(by)` - Remove duplicates by field
52
+
35
53
  ### Workflow
36
54
  1. **Explore**: `print(ls("tools/"))` to see categories
37
55
  2. **Learn**: `print(read("tools/<category>/<tool>.py"))` to see tool signatures
@@ -51,6 +69,19 @@ result = call_tool("devices", "registry.py", device_type="light")
51
69
  print("Devices:", result)
52
70
  ```
53
71
 
72
+ ### Efficient Device Control Example
73
+ ```python
74
+ # Instead of 12+ calls, use 2:
75
+ devices = call_tool("devices", "list_devices")
76
+ device = query(devices).filter(name="balcony").first()
77
+ call_tool("control", "turn_on_device", **device)
78
+
79
+ # Find all living room lights and turn them off
80
+ devices = call_tool("devices", "list_devices")
81
+ living_lights = query(devices).filter(room="living", type="light")
82
+ living_lights.each(lambda d: call_tool("control", "turn_off_device", device_id=d["device_id"]))
83
+ ```
84
+
54
85
  ### Rules
55
86
  1. Always use `print()` to show results
56
87
  2. Explore before assuming - use `ls()` and `read()` first
@@ -83,13 +114,13 @@ class CodeModeConfig:
83
114
 
84
115
  class CodeModeExecutor:
85
116
  """Executes code for an agent in code mode.
86
-
117
+
87
118
  This class:
88
119
  1. Manages the sandbox and virtual filesystem
89
120
  2. Provides the execute_code tool implementation
90
- 3. Routes tool calls from sandbox to real implementations
121
+ 3. Routes tool calls from sandbox to real implementations via queue-based proxy
91
122
  """
92
-
123
+
93
124
  def __init__(
94
125
  self,
95
126
  config: CodeModeConfig,
@@ -97,19 +128,28 @@ class CodeModeExecutor:
97
128
  ):
98
129
  self.config = config
99
130
  self.tool_registry = tool_registry
131
+
132
+ # Create tool proxy queues (queues are picklable, client/server objects are not)
133
+ self._tool_request_queue: multiprocessing.Queue[Any] = multiprocessing.Queue()
134
+ self._tool_response_queue: multiprocessing.Queue[Any] = multiprocessing.Queue()
135
+
136
+ # Create proxy server for main process
137
+ self._proxy_server = ToolProxyServer(
138
+ self._tool_request_queue,
139
+ self._tool_response_queue,
140
+ )
141
+
142
+ # Create sandbox (queues passed during execute)
100
143
  self.sandbox = SubprocessSandbox(
101
144
  timeout_seconds=config.timeout_seconds,
102
145
  memory_limit_mb=config.memory_limit_mb,
103
146
  )
104
147
 
105
- # Tool proxy for routing calls
148
+ # Tool implementations registry (kept for backward compatibility)
106
149
  self._tool_implementations: dict[str, Any] = {}
107
150
 
108
- # Create virtual filesystem with call_tool support
109
- self.virtual_fs = VirtualFilesystem(
110
- registry=tool_registry,
111
- tool_caller=self.call_tool,
112
- )
151
+ # Create virtual filesystem (no longer needs tool_caller)
152
+ self.virtual_fs = VirtualFilesystem(registry=tool_registry)
113
153
 
114
154
  def register_tool_implementation(
115
155
  self,
@@ -120,6 +160,8 @@ class CodeModeExecutor:
120
160
  """Register a real tool implementation for the proxy."""
121
161
  key = f"{category}.{tool_name}"
122
162
  self._tool_implementations[key] = implementation
163
+ # Also register with proxy server for queue-based communication
164
+ self._proxy_server.register_implementation(category, tool_name, implementation)
123
165
 
124
166
  async def execute_code(self, code: str) -> str:
125
167
  """Execute Python code in the sandbox.
@@ -132,11 +174,29 @@ class CodeModeExecutor:
132
174
  Returns:
133
175
  Captured output or error message
134
176
  """
135
- # Build globals with virtual filesystem functions (ls, read, call_tool)
177
+ # Build globals with virtual filesystem functions (ls, read)
136
178
  globals_dict = self.virtual_fs.get_sandbox_globals()
137
179
 
138
- # Execute in sandbox
139
- result = await self.sandbox.execute(code, globals_dict)
180
+ # Start proxy server task to handle tool calls from subprocess
181
+ server_task = asyncio.create_task(
182
+ self._proxy_server.run_until_complete(timeout=self.config.timeout_seconds + 5)
183
+ )
184
+
185
+ try:
186
+ # Execute in sandbox with queues for tool proxy
187
+ result = await self.sandbox.execute(
188
+ code,
189
+ globals_dict,
190
+ tool_request_queue=self._tool_request_queue,
191
+ tool_response_queue=self._tool_response_queue,
192
+ )
193
+ finally:
194
+ # Stop server
195
+ self._proxy_server.stop()
196
+ try:
197
+ await asyncio.wait_for(server_task, timeout=1.0)
198
+ except asyncio.TimeoutError:
199
+ pass
140
200
 
141
201
  # Format output
142
202
  if result.success:
voxagent/code/query.py ADDED
@@ -0,0 +1,179 @@
1
+ """Query utilities for Code Mode sandbox.
2
+
3
+ Provides functional-style data pipelines for efficient tool result processing.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from typing import Any, Callable, Iterator
10
+
11
+
12
+ class QueryResult:
13
+ """Chainable wrapper for query results.
14
+
15
+ Enables functional-style data pipelines:
16
+ filter("tools/devices/list_devices", name="balcony").map(["device_id", "name"]).first()
17
+ """
18
+
19
+ def __init__(self, data: list[dict[str, Any]]) -> None:
20
+ """Initialize with list of dicts."""
21
+ self._data = data if isinstance(data, list) else [data] if data else []
22
+
23
+ def __iter__(self) -> Iterator[dict[str, Any]]:
24
+ """Allow iteration over results."""
25
+ return iter(self._data)
26
+
27
+ def __len__(self) -> int:
28
+ """Return number of results."""
29
+ return len(self._data)
30
+
31
+ def __repr__(self) -> str:
32
+ """String representation."""
33
+ return f"QueryResult({len(self._data)} items)"
34
+
35
+ @property
36
+ def data(self) -> list[dict[str, Any]]:
37
+ """Get raw data."""
38
+ return self._data
39
+
40
+ def filter(self, **patterns: str) -> "QueryResult":
41
+ """Filter results by regex patterns on field values.
42
+
43
+ Args:
44
+ **patterns: Field name -> regex pattern pairs
45
+
46
+ Returns:
47
+ New QueryResult with matching items
48
+
49
+ Example:
50
+ result.filter(name="balcony", room="living.*")
51
+ """
52
+ if not patterns:
53
+ return self
54
+
55
+ filtered = []
56
+ for item in self._data:
57
+ match = True
58
+ for field, pattern in patterns.items():
59
+ value = str(item.get(field, ""))
60
+ if not re.search(pattern, value, re.IGNORECASE):
61
+ match = False
62
+ break
63
+ if match:
64
+ filtered.append(item)
65
+
66
+ return QueryResult(filtered)
67
+
68
+ def map(self, fields: list[str] | str) -> "QueryResult":
69
+ """Extract specific fields from each result.
70
+
71
+ Args:
72
+ fields: Field name or list of field names to extract
73
+
74
+ Returns:
75
+ New QueryResult with only specified fields
76
+
77
+ Example:
78
+ result.map(["device_id", "name"])
79
+ result.map("device_id") # Returns list of values
80
+ """
81
+ if isinstance(fields, str):
82
+ # Single field - return list of values wrapped in dicts
83
+ return QueryResult([{fields: item.get(fields)} for item in self._data])
84
+
85
+ mapped = []
86
+ for item in self._data:
87
+ mapped.append({f: item.get(f) for f in fields if f in item})
88
+ return QueryResult(mapped)
89
+
90
+ def reduce(self, by: str | None = None, count: bool = False) -> dict[str, Any]:
91
+ """Aggregate results.
92
+
93
+ Args:
94
+ by: Field to group by (optional)
95
+ count: If True, return counts per group
96
+
97
+ Returns:
98
+ Aggregated dict
99
+
100
+ Example:
101
+ result.reduce(by="room", count=True) # {"living": 3, "bedroom": 2}
102
+ """
103
+ if by is None:
104
+ if count:
105
+ return {"count": len(self._data)}
106
+ return {"items": self._data}
107
+
108
+ groups: dict[str, list[dict[str, Any]]] = {}
109
+ for item in self._data:
110
+ key = str(item.get(by, "unknown"))
111
+ if key not in groups:
112
+ groups[key] = []
113
+ groups[key].append(item)
114
+
115
+ if count:
116
+ return {k: len(v) for k, v in groups.items()}
117
+ return groups
118
+
119
+ def first(self) -> dict[str, Any] | None:
120
+ """Get first result or None.
121
+
122
+ Returns:
123
+ First item or None if empty
124
+ """
125
+ return self._data[0] if self._data else None
126
+
127
+ def last(self) -> dict[str, Any] | None:
128
+ """Get last result or None."""
129
+ return self._data[-1] if self._data else None
130
+
131
+ def each(self, fn: Callable[[dict[str, Any]], Any]) -> list[Any]:
132
+ """Apply function to each result.
133
+
134
+ Args:
135
+ fn: Function to apply to each item
136
+
137
+ Returns:
138
+ List of function results
139
+
140
+ Example:
141
+ result.each(lambda d: call_tool("control", "turn_on", **d))
142
+ """
143
+ return [fn(item) for item in self._data]
144
+
145
+ def sort(self, by: str, reverse: bool = False) -> "QueryResult":
146
+ """Sort results by field.
147
+
148
+ Args:
149
+ by: Field name to sort by
150
+ reverse: If True, sort descending
151
+
152
+ Returns:
153
+ New QueryResult with sorted items
154
+ """
155
+ sorted_data = sorted(
156
+ self._data,
157
+ key=lambda x: str(x.get(by, "")),
158
+ reverse=reverse
159
+ )
160
+ return QueryResult(sorted_data)
161
+
162
+ def unique(self, by: str) -> "QueryResult":
163
+ """Remove duplicates based on field value.
164
+
165
+ Args:
166
+ by: Field to check for uniqueness
167
+
168
+ Returns:
169
+ New QueryResult with unique items
170
+ """
171
+ seen: set[str] = set()
172
+ unique_items = []
173
+ for item in self._data:
174
+ key = str(item.get(by, ""))
175
+ if key not in seen:
176
+ seen.add(key)
177
+ unique_items.append(item)
178
+ return QueryResult(unique_items)
179
+
voxagent/code/sandbox.py CHANGED
@@ -91,6 +91,8 @@ def _execute_in_subprocess(
91
91
  globals_dict: dict[str, Any],
92
92
  result_queue: multiprocessing.Queue, # type: ignore[type-arg]
93
93
  memory_limit_mb: int,
94
+ tool_request_queue: "multiprocessing.Queue[Any] | None" = None,
95
+ tool_response_queue: "multiprocessing.Queue[Any] | None" = None,
94
96
  ) -> None:
95
97
  """Subprocess entry point for sandboxed execution."""
96
98
  # Import here to avoid loading in main process
@@ -257,6 +259,114 @@ def _execute_in_subprocess(
257
259
  "_write_": lambda x: x,
258
260
  **globals_dict,
259
261
  }
262
+
263
+ # Create call_tool function using proxy if queues are provided
264
+ if tool_request_queue is not None and tool_response_queue is not None:
265
+ from voxagent.code.tool_proxy import ToolProxyClient
266
+ proxy_client = ToolProxyClient(tool_request_queue, tool_response_queue)
267
+
268
+ def call_tool(category: str, tool_name: str, **kwargs: Any) -> Any:
269
+ proxy = proxy_client.create_tool_proxy(category, tool_name)
270
+ return proxy(**kwargs)
271
+
272
+ exec_globals["call_tool"] = call_tool
273
+
274
+ # Add query functions for efficient data processing
275
+ from voxagent.code.query import QueryResult
276
+ import re as _re
277
+
278
+ def query(data: list | dict) -> QueryResult:
279
+ """Wrap data in QueryResult for chaining."""
280
+ if isinstance(data, dict) and "devices" in data:
281
+ # Handle list_devices response format
282
+ return QueryResult(data["devices"])
283
+ if isinstance(data, dict) and "items" in data:
284
+ return QueryResult(data["items"])
285
+ if isinstance(data, list):
286
+ return QueryResult(data)
287
+ return QueryResult([data] if data else [])
288
+
289
+ def tree(path: str = "tools") -> str:
290
+ """Show full tool structure with signatures.
291
+
292
+ Args:
293
+ path: Starting path (default: "tools")
294
+
295
+ Returns:
296
+ Tree-formatted string showing all tools
297
+ """
298
+ lines = []
299
+ path = path.rstrip("/")
300
+
301
+ # Get ls function from globals_dict
302
+ ls_func = globals_dict.get("ls")
303
+ if ls_func is None:
304
+ return "(ls function not available)"
305
+
306
+ # Get root entries
307
+ entries = ls_func(path)
308
+ if isinstance(entries, str):
309
+ # Handle error message
310
+ return entries
311
+ for entry in entries:
312
+ if entry == "__index__.md":
313
+ continue
314
+ if entry.endswith("/"):
315
+ # It's a category
316
+ cat_name = entry.rstrip("/")
317
+ lines.append(f"📁 {cat_name}/")
318
+ # Get tools in category
319
+ cat_entries = ls_func(f"{path}/{cat_name}")
320
+ if isinstance(cat_entries, list):
321
+ for tool in cat_entries:
322
+ if tool != "__index__.md":
323
+ lines.append(f" 📄 {tool}")
324
+ else:
325
+ lines.append(f"📄 {entry}")
326
+
327
+ return "\n".join(lines) if lines else "(empty)"
328
+
329
+ def search(query_str: str) -> list:
330
+ """Search for tools by keyword.
331
+
332
+ Args:
333
+ query_str: Search term (matches tool names)
334
+
335
+ Returns:
336
+ List of matching tool paths
337
+ """
338
+ results = []
339
+ pattern = _re.compile(query_str, _re.IGNORECASE)
340
+
341
+ # Get ls function from globals_dict
342
+ ls_func = globals_dict.get("ls")
343
+ if ls_func is None:
344
+ return []
345
+
346
+ # Search all categories
347
+ categories = ls_func("tools")
348
+ if isinstance(categories, str):
349
+ return []
350
+ for cat in categories:
351
+ if cat == "__index__.md" or not cat.endswith("/"):
352
+ continue
353
+ cat_name = cat.rstrip("/")
354
+ tools = ls_func(f"tools/{cat_name}")
355
+ if isinstance(tools, str):
356
+ continue
357
+ for tool in tools:
358
+ if tool == "__index__.md":
359
+ continue
360
+ if pattern.search(tool) or pattern.search(cat_name):
361
+ results.append(f"tools/{cat_name}/{tool}")
362
+
363
+ return results
364
+
365
+ exec_globals["query"] = query
366
+ exec_globals["tree"] = tree
367
+ exec_globals["search"] = search
368
+ exec_globals["QueryResult"] = QueryResult
369
+
260
370
  exec(byte_code, exec_globals)
261
371
  # Get the _print object that was created during execution and call it
262
372
  _print_obj = exec_globals.get("_print")
@@ -298,18 +408,48 @@ class SubprocessSandbox(CodeSandbox):
298
408
  self.tool_proxy_client = tool_proxy_client
299
409
 
300
410
  async def execute(
301
- self, code: str, globals_dict: dict[str, Any] | None = None
411
+ self,
412
+ code: str,
413
+ globals_dict: dict[str, Any] | None = None,
414
+ tool_request_queue: "multiprocessing.Queue[Any] | None" = None,
415
+ tool_response_queue: "multiprocessing.Queue[Any] | None" = None,
302
416
  ) -> SandboxResult:
303
- """Execute code in subprocess with RestrictedPython."""
417
+ """Execute code in subprocess with RestrictedPython.
418
+
419
+ Args:
420
+ code: Python source code to execute
421
+ globals_dict: Optional globals to inject (e.g., ls, read functions)
422
+ tool_request_queue: Queue for tool call requests from subprocess
423
+ tool_response_queue: Queue for tool call responses to subprocess
424
+
425
+ Returns:
426
+ SandboxResult with output or error
427
+ """
428
+ import asyncio
429
+
304
430
  start_time = time.monotonic()
305
431
 
306
432
  result_queue: multiprocessing.Queue[SandboxResult] = multiprocessing.Queue()
307
433
  process = multiprocessing.Process(
308
434
  target=_execute_in_subprocess,
309
- args=(code, globals_dict or {}, result_queue, self.memory_limit_mb),
435
+ args=(
436
+ code,
437
+ globals_dict or {},
438
+ result_queue,
439
+ self.memory_limit_mb,
440
+ tool_request_queue,
441
+ tool_response_queue,
442
+ ),
310
443
  )
311
444
  process.start()
312
- process.join(timeout=self.timeout_seconds)
445
+
446
+ # Use non-blocking wait to allow event loop to run proxy server
447
+ # Poll the process status instead of blocking on join()
448
+ poll_interval = 0.01 # 10ms
449
+ elapsed = 0.0
450
+ while process.is_alive() and elapsed < self.timeout_seconds:
451
+ await asyncio.sleep(poll_interval)
452
+ elapsed = time.monotonic() - start_time
313
453
 
314
454
  execution_time_ms = (time.monotonic() - start_time) * 1000
315
455
 
@@ -95,14 +95,12 @@ class ToolRegistry:
95
95
  return []
96
96
 
97
97
 
98
- # Type alias for tool caller function
99
- ToolCaller = Any # Callable[[str, str, ...], Any]
100
-
101
-
102
98
  class VirtualFilesystem:
103
99
  """Virtual filesystem for tool discovery.
104
100
 
105
101
  Provides ls() and read() functions that can be injected into the sandbox.
102
+ The call_tool function is now injected directly by the sandbox using
103
+ the tool proxy pattern for reliable cross-process communication.
106
104
 
107
105
  Directory structure:
108
106
  tools/
@@ -116,13 +114,8 @@ class VirtualFilesystem:
116
114
  └── temperature.py
117
115
  """
118
116
 
119
- def __init__(
120
- self,
121
- registry: ToolRegistry,
122
- tool_caller: ToolCaller | None = None,
123
- ) -> None:
117
+ def __init__(self, registry: ToolRegistry) -> None:
124
118
  self._registry = registry
125
- self._tool_caller = tool_caller
126
119
 
127
120
  def ls(self, path: str) -> list[str]:
128
121
  """List directory contents.
@@ -223,15 +216,12 @@ class VirtualFilesystem:
223
216
  """Get globals dict to inject into sandbox.
224
217
 
225
218
  Returns:
226
- Dict with ls, read, and call_tool functions bound to this filesystem
219
+ Dict with ls and read functions bound to this filesystem.
220
+ Note: call_tool is now injected directly by the sandbox using
221
+ the tool proxy pattern for reliable cross-process communication.
227
222
  """
228
- globals_dict: dict[str, Any] = {
223
+ return {
229
224
  "ls": self.ls,
230
225
  "read": self.read,
231
226
  }
232
227
 
233
- if self._tool_caller is not None:
234
- globals_dict["call_tool"] = self._tool_caller
235
-
236
- return globals_dict
237
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voxagent
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: A lightweight, model-agnostic LLM provider abstraction with streaming and tool support
5
5
  Project-URL: Homepage, https://github.com/lensator/voxagent
6
6
  Project-URL: Documentation, https://github.com/lensator/voxagent#readme
@@ -1,14 +1,15 @@
1
1
  voxagent/__init__.py,sha256=YMYC95iwWXK26hicGYmd2erNOInrYtohwUVOuNmpTCs,3927
2
- voxagent/_version.py,sha256=uG0jH_KsTBSDgsjnJdMe2gyLmoUNsNl07RdN-sUtP8Y,87
2
+ voxagent/_version.py,sha256=BQIRef51oDZ39-xgLzB-7zzZTwre-GSkFQ2C7tG_vg4,87
3
3
  voxagent/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
4
  voxagent/agent/__init__.py,sha256=eASoU7Zhvw8BtJ-iUqVN06S4fMLkHwDgUZbHeH2AUOM,755
5
5
  voxagent/agent/abort.py,sha256=2Wnnxq8Dcn7wQkKPHrba2o0OeOdzF4NNsl-usgE4CJw,5191
6
6
  voxagent/agent/core.py,sha256=ddjDoWcDqiacRtNzef5wORR2f5uHqL1Y1gOmzoeXRFY,33339
7
- voxagent/code/__init__.py,sha256=MzbrYReislAB-lCZEZn_lBEPGjYZyPK-c9RFCq1nSm0,1379
8
- voxagent/code/agent.py,sha256=fVaOlJNvnHJedPCCW5Rmm4_-YRvgcYpaLicXA-8HWxU,9634
9
- voxagent/code/sandbox.py,sha256=LP2cwXchDk6mtiYGRb_RmkGNoyPv5OEKQC2h4M89dC0,11669
7
+ voxagent/code/__init__.py,sha256=WEmr8IcpVwM8eykSa4XDwPAwl5QmtzlygOLuzVA8JzE,1454
8
+ voxagent/code/agent.py,sha256=NbdhT1Zx99N76YzUqL1A49wmYViFuOp4l0zIoftGpIM,12128
9
+ voxagent/code/query.py,sha256=qqRxT9_4gJ28lYXY6Ygdzio8eAvWasuswVmVy0cNMjc,5462
10
+ voxagent/code/sandbox.py,sha256=sJvKL-qrfzXZDQTwDMPUZrtGCb_hznIiomTNb2wG7UM,16859
10
11
  voxagent/code/tool_proxy.py,sha256=wZvRqXoz2SfYTHLpe8tIkpJL6b8fmlU96DSGeYw-HfM,8091
11
- voxagent/code/virtual_fs.py,sha256=wgkH7voirgDEeD2xg0yaOxU08qWwF-iqXEWy94n0I5U,7435
12
+ voxagent/code/virtual_fs.py,sha256=L4Z_Kmoalwj4lJDhRxQOTOblC7Zrx8lv7slvHeHRILM,7362
12
13
  voxagent/mcp/__init__.py,sha256=_3Rsn7nIuivdWLv0MzpyjRGsPuCgr4LrXCge6FCb3nE,470
13
14
  voxagent/mcp/manager.py,sha256=sECOhw-f6HB6NV-mBqcgJzsEt28acdQI_O2cT-41Rxw,6606
14
15
  voxagent/mcp/tool.py,sha256=YQQqXNcanDDr5tkL1Z5OjNsDI5dWMEzm_SlJ4pOcUpk,5147
@@ -52,6 +53,6 @@ voxagent/tools/registry.py,sha256=MNJzgcmKT0AoMWIky9TJY4WVhzn5dkmjIHsUiZ3mv3U,25
52
53
  voxagent/types/__init__.py,sha256=3VunuprKKEpOR9Cg-UITHJXds_xQ-tfqQb4S7wD3nP4,933
53
54
  voxagent/types/messages.py,sha256=c6hNi9w6C8gbFoFm5fFge35vwJGywaoR_OiPQprfyVs,3494
54
55
  voxagent/types/run.py,sha256=4vYq0pCqH7_7SWbMb1SplWj4TLiE3DELDYMi0HefFmo,5071
55
- voxagent-0.2.4.dist-info/METADATA,sha256=MwN72uka42N7nHRbI6F6d3YvqFklZmcAEDDyg093e04,5685
56
- voxagent-0.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
57
- voxagent-0.2.4.dist-info/RECORD,,
56
+ voxagent-0.2.5.dist-info/METADATA,sha256=V29VGrxDI7moTtP7ps104Io9vF3jrZxcBn4xFJQly5c,5685
57
+ voxagent-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
58
+ voxagent-0.2.5.dist-info/RECORD,,