voxagent 0.2.3__tar.gz → 0.2.4__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.
- {voxagent-0.2.3 → voxagent-0.2.4}/PKG-INFO +1 -1
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/_version.py +1 -1
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/agent/core.py +90 -20
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/chatgpt.py +16 -7
- {voxagent-0.2.3 → voxagent-0.2.4}/.gitignore +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/README.md +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/examples/README.md +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/pyproject.toml +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/agent/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/agent/abort.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/code/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/code/agent.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/code/sandbox.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/code/tool_proxy.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/code/virtual_fs.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/mcp/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/mcp/manager.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/mcp/tool.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/anthropic.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/augment.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/auth.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/base.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/claudecode.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/cli_base.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/codex.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/failover.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/google.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/groq.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/ollama.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/openai.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/providers/registry.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/py.typed +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/security/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/security/events.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/security/filter.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/security/registry.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/session/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/session/compaction.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/session/lock.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/session/model.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/session/storage.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/streaming/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/streaming/emitter.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/streaming/events.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/subagent/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/subagent/context.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/subagent/definition.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/tools/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/tools/context.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/tools/decorator.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/tools/definition.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/tools/executor.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/tools/policy.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/tools/registry.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/types/__init__.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/types/messages.py +0 -0
- {voxagent-0.2.3 → voxagent-0.2.4}/src/voxagent/types/run.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voxagent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.2.
|
|
1
|
+
__version__ = "0.2.4"
|
|
2
2
|
__version_info__ = tuple(int(x) for x in __version__.split("."))
|
|
@@ -125,6 +125,68 @@ class Agent(Generic[DepsT, OutputT]):
|
|
|
125
125
|
# Toolsets (MCP servers, etc.) - store for later
|
|
126
126
|
self._toolsets = toolsets or []
|
|
127
127
|
|
|
128
|
+
# MCP connection caching (persistent across run() calls)
|
|
129
|
+
self._mcp_manager: MCPServerManager | None = None
|
|
130
|
+
self._mcp_tools: list[ToolDefinition] = []
|
|
131
|
+
self._mcp_connected: bool = False
|
|
132
|
+
|
|
133
|
+
# -------------------------------------------------------------------------
|
|
134
|
+
# MCP Connection Management
|
|
135
|
+
# -------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
async def connect_mcp(self) -> list[ToolDefinition]:
|
|
138
|
+
"""Connect to MCP servers and cache the connection.
|
|
139
|
+
|
|
140
|
+
This method connects to all MCP servers in toolsets and caches the
|
|
141
|
+
connection for reuse across multiple run() calls. Call this during
|
|
142
|
+
initialization/warmup to avoid connection overhead on first message.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List of ToolDefinition objects from connected MCP servers.
|
|
146
|
+
"""
|
|
147
|
+
if self._mcp_connected:
|
|
148
|
+
return self._mcp_tools
|
|
149
|
+
|
|
150
|
+
if self._toolsets:
|
|
151
|
+
self._mcp_manager = MCPServerManager()
|
|
152
|
+
await self._mcp_manager.add_servers(self._toolsets)
|
|
153
|
+
self._mcp_tools = await self._mcp_manager.connect_all()
|
|
154
|
+
self._mcp_connected = True
|
|
155
|
+
|
|
156
|
+
return self._mcp_tools
|
|
157
|
+
|
|
158
|
+
async def disconnect_mcp(self) -> None:
|
|
159
|
+
"""Disconnect from MCP servers.
|
|
160
|
+
|
|
161
|
+
Call this when the agent is no longer needed to clean up MCP
|
|
162
|
+
server connections. This is called automatically when using
|
|
163
|
+
the agent as an async context manager.
|
|
164
|
+
"""
|
|
165
|
+
if self._mcp_manager and self._mcp_connected:
|
|
166
|
+
await self._mcp_manager.disconnect_all()
|
|
167
|
+
self._mcp_manager = None
|
|
168
|
+
self._mcp_connected = False
|
|
169
|
+
self._mcp_tools = []
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def mcp_connected(self) -> bool:
|
|
173
|
+
"""Check if MCP servers are currently connected."""
|
|
174
|
+
return self._mcp_connected
|
|
175
|
+
|
|
176
|
+
async def __aenter__(self) -> "Agent[DepsT, OutputT]":
|
|
177
|
+
"""Enter async context manager - connect MCP servers."""
|
|
178
|
+
await self.connect_mcp()
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
async def __aexit__(
|
|
182
|
+
self,
|
|
183
|
+
exc_type: type[BaseException] | None,
|
|
184
|
+
exc_val: BaseException | None,
|
|
185
|
+
exc_tb: Any,
|
|
186
|
+
) -> None:
|
|
187
|
+
"""Exit async context manager - disconnect MCP servers."""
|
|
188
|
+
await self.disconnect_mcp()
|
|
189
|
+
|
|
128
190
|
@staticmethod
|
|
129
191
|
def _parse_model_string(model_string: str) -> ModelConfig:
|
|
130
192
|
"""Parse 'provider:model' string into ModelConfig.
|
|
@@ -495,19 +557,22 @@ class Agent(Generic[DepsT, OutputT]):
|
|
|
495
557
|
timeout_handler: TimeoutHandler | None = None
|
|
496
558
|
timed_out = False
|
|
497
559
|
error_message: str | None = None
|
|
498
|
-
|
|
560
|
+
# Track if we connected MCP in this run (for cleanup)
|
|
561
|
+
mcp_connected_in_this_run = False
|
|
499
562
|
|
|
500
563
|
if timeout_ms:
|
|
501
564
|
timeout_handler = TimeoutHandler(timeout_ms)
|
|
502
565
|
await timeout_handler.start(abort_controller)
|
|
503
566
|
|
|
504
567
|
try:
|
|
505
|
-
#
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
await
|
|
510
|
-
|
|
568
|
+
# Use cached MCP connection if available, otherwise connect
|
|
569
|
+
if self._mcp_connected:
|
|
570
|
+
mcp_tools = self._mcp_tools
|
|
571
|
+
elif self._toolsets:
|
|
572
|
+
mcp_tools = await self.connect_mcp()
|
|
573
|
+
mcp_connected_in_this_run = True
|
|
574
|
+
else:
|
|
575
|
+
mcp_tools = []
|
|
511
576
|
|
|
512
577
|
# Get all tools (native + MCP)
|
|
513
578
|
all_tools = self._get_all_tools(mcp_tools)
|
|
@@ -652,9 +717,10 @@ class Agent(Generic[DepsT, OutputT]):
|
|
|
652
717
|
)
|
|
653
718
|
|
|
654
719
|
finally:
|
|
655
|
-
#
|
|
656
|
-
if
|
|
657
|
-
|
|
720
|
+
# Only disconnect MCP servers if we connected them in this run
|
|
721
|
+
# (not if using cached connection from connect_mcp())
|
|
722
|
+
if mcp_connected_in_this_run and not self._mcp_connected:
|
|
723
|
+
await self.disconnect_mcp()
|
|
658
724
|
if timeout_handler:
|
|
659
725
|
timeout_handler.cancel()
|
|
660
726
|
abort_controller.cleanup()
|
|
@@ -685,19 +751,22 @@ class Agent(Generic[DepsT, OutputT]):
|
|
|
685
751
|
abort_controller = AbortController()
|
|
686
752
|
timeout_handler: TimeoutHandler | None = None
|
|
687
753
|
timed_out = False
|
|
688
|
-
|
|
754
|
+
# Track if we connected MCP in this run (for cleanup)
|
|
755
|
+
mcp_connected_in_this_run = False
|
|
689
756
|
|
|
690
757
|
if timeout_ms:
|
|
691
758
|
timeout_handler = TimeoutHandler(timeout_ms)
|
|
692
759
|
await timeout_handler.start(abort_controller)
|
|
693
760
|
|
|
694
761
|
try:
|
|
695
|
-
#
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
await
|
|
700
|
-
|
|
762
|
+
# Use cached MCP connection if available, otherwise connect
|
|
763
|
+
if self._mcp_connected:
|
|
764
|
+
mcp_tools = self._mcp_tools
|
|
765
|
+
elif self._toolsets:
|
|
766
|
+
mcp_tools = await self.connect_mcp()
|
|
767
|
+
mcp_connected_in_this_run = True
|
|
768
|
+
else:
|
|
769
|
+
mcp_tools = []
|
|
701
770
|
|
|
702
771
|
# Get all tools (native + MCP)
|
|
703
772
|
all_tools = self._get_all_tools(mcp_tools)
|
|
@@ -838,9 +907,10 @@ class Agent(Generic[DepsT, OutputT]):
|
|
|
838
907
|
)
|
|
839
908
|
|
|
840
909
|
finally:
|
|
841
|
-
#
|
|
842
|
-
if
|
|
843
|
-
|
|
910
|
+
# Only disconnect MCP servers if we connected them in this run
|
|
911
|
+
# (not if using cached connection from connect_mcp())
|
|
912
|
+
if mcp_connected_in_this_run and not self._mcp_connected:
|
|
913
|
+
await self.disconnect_mcp()
|
|
844
914
|
if timeout_handler:
|
|
845
915
|
timeout_handler.cancel()
|
|
846
916
|
abort_controller.cleanup()
|
|
@@ -206,7 +206,7 @@ class ChatGPTProvider(BaseProvider):
|
|
|
206
206
|
input_msgs.append({
|
|
207
207
|
"type": "message",
|
|
208
208
|
"role": "assistant",
|
|
209
|
-
"content": [{"type": "
|
|
209
|
+
"content": [{"type": "output_text", "text": msg.content}],
|
|
210
210
|
})
|
|
211
211
|
# Add function_call items for each tool call
|
|
212
212
|
for tc in msg.tool_calls:
|
|
@@ -242,25 +242,28 @@ class ChatGPTProvider(BaseProvider):
|
|
|
242
242
|
"output": block.get("content", ""),
|
|
243
243
|
})
|
|
244
244
|
elif hasattr(block, "text"):
|
|
245
|
-
# TextBlock
|
|
245
|
+
# TextBlock - use output_text for assistant, input_text for user
|
|
246
|
+
content_type = "output_text" if msg.role == "assistant" else "input_text"
|
|
246
247
|
input_msgs.append({
|
|
247
248
|
"type": "message",
|
|
248
249
|
"role": "user" if msg.role == "user" else "assistant",
|
|
249
|
-
"content": [{"type":
|
|
250
|
+
"content": [{"type": content_type, "text": block.text}],
|
|
250
251
|
})
|
|
251
252
|
elif isinstance(block, dict) and "text" in block:
|
|
253
|
+
content_type = "output_text" if msg.role == "assistant" else "input_text"
|
|
252
254
|
input_msgs.append({
|
|
253
255
|
"type": "message",
|
|
254
256
|
"role": "user" if msg.role == "user" else "assistant",
|
|
255
|
-
"content": [{"type":
|
|
257
|
+
"content": [{"type": content_type, "text": block["text"]}],
|
|
256
258
|
})
|
|
257
259
|
elif isinstance(msg.content, str):
|
|
258
|
-
# Simple string content
|
|
260
|
+
# Simple string content - use output_text for assistant, input_text for user
|
|
259
261
|
role = "user" if msg.role == "user" else "assistant"
|
|
262
|
+
content_type = "output_text" if msg.role == "assistant" else "input_text"
|
|
260
263
|
input_msgs.append({
|
|
261
264
|
"type": "message",
|
|
262
265
|
"role": role,
|
|
263
|
-
"content": [{"type":
|
|
266
|
+
"content": [{"type": content_type, "text": msg.content}],
|
|
264
267
|
})
|
|
265
268
|
|
|
266
269
|
return input_msgs
|
|
@@ -335,7 +338,13 @@ class ChatGPTProvider(BaseProvider):
|
|
|
335
338
|
if response.status_code == 401:
|
|
336
339
|
yield ErrorChunk(error="Authentication failed - token may be expired")
|
|
337
340
|
return
|
|
338
|
-
response.
|
|
341
|
+
if response.status_code >= 400:
|
|
342
|
+
# Read error body before raising
|
|
343
|
+
error_body = await response.aread()
|
|
344
|
+
error_text = error_body.decode("utf-8", errors="replace")
|
|
345
|
+
logger.error("ChatGPT API error %d: %s", response.status_code, error_text)
|
|
346
|
+
yield ErrorChunk(error=f"HTTP {response.status_code}: {error_text[:500]}")
|
|
347
|
+
return
|
|
339
348
|
|
|
340
349
|
async for line in response.aiter_lines():
|
|
341
350
|
if abort_signal and abort_signal.aborted:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|