superqode 0.1.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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced Tool Framework - Production-Ready Tool System.
|
|
3
|
+
|
|
4
|
+
Builds upon the base tool system with:
|
|
5
|
+
- Tool result caching
|
|
6
|
+
- Retry logic with backoff
|
|
7
|
+
- Tool metrics and timing
|
|
8
|
+
- Tool validation
|
|
9
|
+
- Async-first design
|
|
10
|
+
|
|
11
|
+
Provides a robust foundation for tool execution.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import hashlib
|
|
18
|
+
import time
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from datetime import datetime, timedelta
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from functools import wraps
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar, Union
|
|
26
|
+
import json
|
|
27
|
+
|
|
28
|
+
from .base import Tool, ToolContext, ToolResult
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
T = TypeVar("T")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ToolCategory(Enum):
|
|
35
|
+
"""Categories of tools."""
|
|
36
|
+
|
|
37
|
+
FILE = "file" # File operations
|
|
38
|
+
SHELL = "shell" # Shell commands
|
|
39
|
+
SEARCH = "search" # Search operations
|
|
40
|
+
EDIT = "edit" # Code editing
|
|
41
|
+
NETWORK = "network" # Network requests
|
|
42
|
+
ANALYSIS = "analysis" # Code analysis
|
|
43
|
+
TEST = "test" # Testing tools
|
|
44
|
+
OTHER = "other"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ToolMetrics:
|
|
49
|
+
"""Metrics for tool execution."""
|
|
50
|
+
|
|
51
|
+
call_count: int = 0
|
|
52
|
+
success_count: int = 0
|
|
53
|
+
error_count: int = 0
|
|
54
|
+
total_duration_ms: float = 0.0
|
|
55
|
+
last_called: Optional[datetime] = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def avg_duration_ms(self) -> float:
|
|
59
|
+
"""Average execution duration."""
|
|
60
|
+
if self.call_count == 0:
|
|
61
|
+
return 0.0
|
|
62
|
+
return self.total_duration_ms / self.call_count
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def success_rate(self) -> float:
|
|
66
|
+
"""Success rate as percentage."""
|
|
67
|
+
if self.call_count == 0:
|
|
68
|
+
return 100.0
|
|
69
|
+
return (self.success_count / self.call_count) * 100
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class CacheEntry:
|
|
74
|
+
"""A cached tool result."""
|
|
75
|
+
|
|
76
|
+
result: ToolResult
|
|
77
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
78
|
+
hits: int = 0
|
|
79
|
+
|
|
80
|
+
def is_expired(self, ttl_seconds: float) -> bool:
|
|
81
|
+
"""Check if cache entry is expired."""
|
|
82
|
+
age = (datetime.now() - self.created_at).total_seconds()
|
|
83
|
+
return age > ttl_seconds
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ToolCache:
|
|
87
|
+
"""
|
|
88
|
+
Cache for tool results.
|
|
89
|
+
|
|
90
|
+
Caches results based on tool name and arguments hash.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
max_size: int = 1000,
|
|
96
|
+
default_ttl: float = 300.0, # 5 minutes
|
|
97
|
+
):
|
|
98
|
+
self.max_size = max_size
|
|
99
|
+
self.default_ttl = default_ttl
|
|
100
|
+
self._cache: Dict[str, CacheEntry] = {}
|
|
101
|
+
self._tool_ttls: Dict[str, float] = {}
|
|
102
|
+
|
|
103
|
+
def _make_key(self, tool_name: str, args: Dict[str, Any]) -> str:
|
|
104
|
+
"""Create cache key from tool name and arguments."""
|
|
105
|
+
args_json = json.dumps(args, sort_keys=True, default=str)
|
|
106
|
+
args_hash = hashlib.md5(args_json.encode()).hexdigest()
|
|
107
|
+
return f"{tool_name}:{args_hash}"
|
|
108
|
+
|
|
109
|
+
def set_tool_ttl(self, tool_name: str, ttl: float) -> None:
|
|
110
|
+
"""Set TTL for a specific tool."""
|
|
111
|
+
self._tool_ttls[tool_name] = ttl
|
|
112
|
+
|
|
113
|
+
def get(self, tool_name: str, args: Dict[str, Any]) -> Optional[ToolResult]:
|
|
114
|
+
"""Get cached result."""
|
|
115
|
+
key = self._make_key(tool_name, args)
|
|
116
|
+
entry = self._cache.get(key)
|
|
117
|
+
|
|
118
|
+
if entry is None:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
ttl = self._tool_ttls.get(tool_name, self.default_ttl)
|
|
122
|
+
if entry.is_expired(ttl):
|
|
123
|
+
del self._cache[key]
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
entry.hits += 1
|
|
127
|
+
return entry.result
|
|
128
|
+
|
|
129
|
+
def set(self, tool_name: str, args: Dict[str, Any], result: ToolResult) -> None:
|
|
130
|
+
"""Cache a result."""
|
|
131
|
+
# Enforce size limit
|
|
132
|
+
if len(self._cache) >= self.max_size:
|
|
133
|
+
# Remove oldest entries
|
|
134
|
+
sorted_keys = sorted(
|
|
135
|
+
self._cache.keys(),
|
|
136
|
+
key=lambda k: self._cache[k].created_at,
|
|
137
|
+
)
|
|
138
|
+
for key in sorted_keys[: self.max_size // 4]:
|
|
139
|
+
del self._cache[key]
|
|
140
|
+
|
|
141
|
+
key = self._make_key(tool_name, args)
|
|
142
|
+
self._cache[key] = CacheEntry(result=result)
|
|
143
|
+
|
|
144
|
+
def invalidate(self, tool_name: str) -> int:
|
|
145
|
+
"""Invalidate all cache entries for a tool."""
|
|
146
|
+
prefix = f"{tool_name}:"
|
|
147
|
+
keys_to_remove = [k for k in self._cache if k.startswith(prefix)]
|
|
148
|
+
for key in keys_to_remove:
|
|
149
|
+
del self._cache[key]
|
|
150
|
+
return len(keys_to_remove)
|
|
151
|
+
|
|
152
|
+
def clear(self) -> None:
|
|
153
|
+
"""Clear all cache entries."""
|
|
154
|
+
self._cache.clear()
|
|
155
|
+
|
|
156
|
+
def stats(self) -> Dict[str, Any]:
|
|
157
|
+
"""Get cache statistics."""
|
|
158
|
+
total_hits = sum(e.hits for e in self._cache.values())
|
|
159
|
+
return {
|
|
160
|
+
"size": len(self._cache),
|
|
161
|
+
"max_size": self.max_size,
|
|
162
|
+
"total_hits": total_hits,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class EnhancedTool(Tool):
|
|
167
|
+
"""
|
|
168
|
+
Enhanced base class for tools.
|
|
169
|
+
|
|
170
|
+
Adds caching, metrics, and retry support to the base Tool class.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
# Override in subclasses
|
|
174
|
+
category: ToolCategory = ToolCategory.OTHER
|
|
175
|
+
cacheable: bool = False
|
|
176
|
+
cache_ttl: float = 300.0 # 5 minutes
|
|
177
|
+
max_retries: int = 0
|
|
178
|
+
retry_delay: float = 1.0
|
|
179
|
+
|
|
180
|
+
def __init__(self):
|
|
181
|
+
self._metrics = ToolMetrics()
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def metrics(self) -> ToolMetrics:
|
|
185
|
+
"""Get tool metrics."""
|
|
186
|
+
return self._metrics
|
|
187
|
+
|
|
188
|
+
async def execute_with_cache(
|
|
189
|
+
self,
|
|
190
|
+
args: Dict[str, Any],
|
|
191
|
+
ctx: ToolContext,
|
|
192
|
+
cache: Optional[ToolCache] = None,
|
|
193
|
+
) -> ToolResult:
|
|
194
|
+
"""Execute tool with caching support."""
|
|
195
|
+
# Check cache
|
|
196
|
+
if self.cacheable and cache:
|
|
197
|
+
cached = cache.get(self.name, args)
|
|
198
|
+
if cached:
|
|
199
|
+
return cached
|
|
200
|
+
|
|
201
|
+
# Execute
|
|
202
|
+
result = await self.execute(args, ctx)
|
|
203
|
+
|
|
204
|
+
# Cache result if successful
|
|
205
|
+
if self.cacheable and cache and result.success:
|
|
206
|
+
cache.set(self.name, args, result)
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
async def execute_with_retry(
|
|
211
|
+
self,
|
|
212
|
+
args: Dict[str, Any],
|
|
213
|
+
ctx: ToolContext,
|
|
214
|
+
) -> ToolResult:
|
|
215
|
+
"""Execute tool with retry logic."""
|
|
216
|
+
last_error = None
|
|
217
|
+
|
|
218
|
+
for attempt in range(self.max_retries + 1):
|
|
219
|
+
try:
|
|
220
|
+
result = await self.execute(args, ctx)
|
|
221
|
+
if result.success:
|
|
222
|
+
return result
|
|
223
|
+
last_error = result.error
|
|
224
|
+
except Exception as e:
|
|
225
|
+
last_error = str(e)
|
|
226
|
+
|
|
227
|
+
if attempt < self.max_retries:
|
|
228
|
+
delay = self.retry_delay * (2**attempt) # Exponential backoff
|
|
229
|
+
await asyncio.sleep(delay)
|
|
230
|
+
|
|
231
|
+
return ToolResult(
|
|
232
|
+
success=False,
|
|
233
|
+
output="",
|
|
234
|
+
error=f"Failed after {self.max_retries + 1} attempts: {last_error}",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
async def execute_with_metrics(
|
|
238
|
+
self,
|
|
239
|
+
args: Dict[str, Any],
|
|
240
|
+
ctx: ToolContext,
|
|
241
|
+
) -> ToolResult:
|
|
242
|
+
"""Execute tool and track metrics."""
|
|
243
|
+
start_time = time.monotonic()
|
|
244
|
+
|
|
245
|
+
self._metrics.call_count += 1
|
|
246
|
+
self._metrics.last_called = datetime.now()
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
result = await self.execute(args, ctx)
|
|
250
|
+
|
|
251
|
+
if result.success:
|
|
252
|
+
self._metrics.success_count += 1
|
|
253
|
+
else:
|
|
254
|
+
self._metrics.error_count += 1
|
|
255
|
+
|
|
256
|
+
return result
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
self._metrics.error_count += 1
|
|
260
|
+
raise
|
|
261
|
+
|
|
262
|
+
finally:
|
|
263
|
+
duration_ms = (time.monotonic() - start_time) * 1000
|
|
264
|
+
self._metrics.total_duration_ms += duration_ms
|
|
265
|
+
|
|
266
|
+
def validate_args(self, args: Dict[str, Any]) -> List[str]:
|
|
267
|
+
"""Validate tool arguments.
|
|
268
|
+
|
|
269
|
+
Returns list of validation errors (empty if valid).
|
|
270
|
+
Override in subclasses for custom validation.
|
|
271
|
+
"""
|
|
272
|
+
errors = []
|
|
273
|
+
|
|
274
|
+
# Check required parameters
|
|
275
|
+
params = self.parameters
|
|
276
|
+
required = params.get("required", [])
|
|
277
|
+
properties = params.get("properties", {})
|
|
278
|
+
|
|
279
|
+
for param_name in required:
|
|
280
|
+
if param_name not in args:
|
|
281
|
+
errors.append(f"Missing required parameter: {param_name}")
|
|
282
|
+
|
|
283
|
+
# Type checking
|
|
284
|
+
for param_name, value in args.items():
|
|
285
|
+
if param_name in properties:
|
|
286
|
+
param_schema = properties[param_name]
|
|
287
|
+
expected_type = param_schema.get("type")
|
|
288
|
+
|
|
289
|
+
if expected_type == "string" and not isinstance(value, str):
|
|
290
|
+
errors.append(f"Parameter '{param_name}' must be a string")
|
|
291
|
+
elif expected_type == "integer" and not isinstance(value, int):
|
|
292
|
+
errors.append(f"Parameter '{param_name}' must be an integer")
|
|
293
|
+
elif expected_type == "boolean" and not isinstance(value, bool):
|
|
294
|
+
errors.append(f"Parameter '{param_name}' must be a boolean")
|
|
295
|
+
elif expected_type == "array" and not isinstance(value, list):
|
|
296
|
+
errors.append(f"Parameter '{param_name}' must be an array")
|
|
297
|
+
|
|
298
|
+
return errors
|
|
299
|
+
|
|
300
|
+
def to_openai_format_extended(self) -> Dict[str, Any]:
|
|
301
|
+
"""Extended OpenAI format with metadata."""
|
|
302
|
+
base = self.to_openai_format()
|
|
303
|
+
base["metadata"] = {
|
|
304
|
+
"category": self.category.value,
|
|
305
|
+
"cacheable": self.cacheable,
|
|
306
|
+
"max_retries": self.max_retries,
|
|
307
|
+
}
|
|
308
|
+
return base
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class EnhancedToolRegistry:
|
|
312
|
+
"""
|
|
313
|
+
Enhanced tool registry with additional features.
|
|
314
|
+
|
|
315
|
+
Provides caching, metrics tracking, and tool management.
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
def __init__(
|
|
319
|
+
self,
|
|
320
|
+
enable_cache: bool = True,
|
|
321
|
+
cache_config: Optional[Dict[str, Any]] = None,
|
|
322
|
+
):
|
|
323
|
+
self._tools: Dict[str, Tool] = {}
|
|
324
|
+
self._cache = ToolCache(**(cache_config or {})) if enable_cache else None
|
|
325
|
+
self._execution_order: List[str] = [] # Track execution order
|
|
326
|
+
|
|
327
|
+
def register(self, tool: Tool) -> None:
|
|
328
|
+
"""Register a tool."""
|
|
329
|
+
self._tools[tool.name] = tool
|
|
330
|
+
|
|
331
|
+
def unregister(self, name: str) -> bool:
|
|
332
|
+
"""Unregister a tool."""
|
|
333
|
+
if name in self._tools:
|
|
334
|
+
del self._tools[name]
|
|
335
|
+
return True
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
def get(self, name: str) -> Optional[Tool]:
|
|
339
|
+
"""Get a tool by name."""
|
|
340
|
+
return self._tools.get(name)
|
|
341
|
+
|
|
342
|
+
def list(self) -> List[Tool]:
|
|
343
|
+
"""List all tools."""
|
|
344
|
+
return list(self._tools.values())
|
|
345
|
+
|
|
346
|
+
def list_by_category(self, category: ToolCategory) -> List[Tool]:
|
|
347
|
+
"""List tools by category."""
|
|
348
|
+
return [
|
|
349
|
+
t
|
|
350
|
+
for t in self._tools.values()
|
|
351
|
+
if isinstance(t, EnhancedTool) and t.category == category
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
async def execute(
|
|
355
|
+
self,
|
|
356
|
+
name: str,
|
|
357
|
+
args: Dict[str, Any],
|
|
358
|
+
ctx: ToolContext,
|
|
359
|
+
use_cache: bool = True,
|
|
360
|
+
use_retry: bool = True,
|
|
361
|
+
track_metrics: bool = True,
|
|
362
|
+
) -> ToolResult:
|
|
363
|
+
"""Execute a tool with all enhancements."""
|
|
364
|
+
tool = self.get(name)
|
|
365
|
+
|
|
366
|
+
if not tool:
|
|
367
|
+
return ToolResult(
|
|
368
|
+
success=False,
|
|
369
|
+
output="",
|
|
370
|
+
error=f"Tool not found: {name}",
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Track execution order
|
|
374
|
+
self._execution_order.append(name)
|
|
375
|
+
|
|
376
|
+
# Validate arguments
|
|
377
|
+
if isinstance(tool, EnhancedTool):
|
|
378
|
+
errors = tool.validate_args(args)
|
|
379
|
+
if errors:
|
|
380
|
+
return ToolResult(
|
|
381
|
+
success=False,
|
|
382
|
+
output="",
|
|
383
|
+
error=f"Validation errors: {'; '.join(errors)}",
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Execute with enhancements
|
|
387
|
+
if isinstance(tool, EnhancedTool):
|
|
388
|
+
if track_metrics:
|
|
389
|
+
if use_cache and self._cache:
|
|
390
|
+
result = await tool.execute_with_cache(args, ctx, self._cache)
|
|
391
|
+
elif use_retry and tool.max_retries > 0:
|
|
392
|
+
result = await tool.execute_with_retry(args, ctx)
|
|
393
|
+
else:
|
|
394
|
+
result = await tool.execute_with_metrics(args, ctx)
|
|
395
|
+
else:
|
|
396
|
+
result = await tool.execute(args, ctx)
|
|
397
|
+
else:
|
|
398
|
+
result = await tool.execute(args, ctx)
|
|
399
|
+
|
|
400
|
+
return result
|
|
401
|
+
|
|
402
|
+
def get_metrics(self) -> Dict[str, ToolMetrics]:
|
|
403
|
+
"""Get metrics for all enhanced tools."""
|
|
404
|
+
return {
|
|
405
|
+
name: tool.metrics
|
|
406
|
+
for name, tool in self._tools.items()
|
|
407
|
+
if isinstance(tool, EnhancedTool)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
def get_cache_stats(self) -> Optional[Dict[str, Any]]:
|
|
411
|
+
"""Get cache statistics."""
|
|
412
|
+
return self._cache.stats() if self._cache else None
|
|
413
|
+
|
|
414
|
+
def invalidate_cache(self, tool_name: Optional[str] = None) -> int:
|
|
415
|
+
"""Invalidate cache entries."""
|
|
416
|
+
if not self._cache:
|
|
417
|
+
return 0
|
|
418
|
+
|
|
419
|
+
if tool_name:
|
|
420
|
+
return self._cache.invalidate(tool_name)
|
|
421
|
+
else:
|
|
422
|
+
count = len(self._cache._cache)
|
|
423
|
+
self._cache.clear()
|
|
424
|
+
return count
|
|
425
|
+
|
|
426
|
+
def get_execution_history(self, limit: int = 100) -> List[str]:
|
|
427
|
+
"""Get recent tool execution history."""
|
|
428
|
+
return self._execution_order[-limit:]
|
|
429
|
+
|
|
430
|
+
@classmethod
|
|
431
|
+
def default(cls) -> "EnhancedToolRegistry":
|
|
432
|
+
"""Create registry with default tools."""
|
|
433
|
+
from .file_tools import ReadFileTool, WriteFileTool, ListDirectoryTool
|
|
434
|
+
from .edit_tools import EditFileTool, InsertTextTool
|
|
435
|
+
from .shell_tools import BashTool
|
|
436
|
+
from .search_tools import GrepTool, GlobTool
|
|
437
|
+
|
|
438
|
+
registry = cls()
|
|
439
|
+
|
|
440
|
+
# File operations
|
|
441
|
+
registry.register(ReadFileTool())
|
|
442
|
+
registry.register(WriteFileTool())
|
|
443
|
+
registry.register(ListDirectoryTool())
|
|
444
|
+
|
|
445
|
+
# Editing
|
|
446
|
+
registry.register(EditFileTool())
|
|
447
|
+
registry.register(InsertTextTool())
|
|
448
|
+
|
|
449
|
+
# Shell
|
|
450
|
+
registry.register(BashTool())
|
|
451
|
+
|
|
452
|
+
# Search
|
|
453
|
+
registry.register(GrepTool())
|
|
454
|
+
registry.register(GlobTool())
|
|
455
|
+
|
|
456
|
+
return registry
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def cacheable(ttl: float = 300.0):
|
|
460
|
+
"""Decorator to make a tool method cacheable."""
|
|
461
|
+
|
|
462
|
+
def decorator(func):
|
|
463
|
+
@wraps(func)
|
|
464
|
+
async def wrapper(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
|
|
465
|
+
# Check if we have a cache in context
|
|
466
|
+
cache = getattr(ctx, "_tool_cache", None)
|
|
467
|
+
|
|
468
|
+
if cache:
|
|
469
|
+
cached = cache.get(self.name, args)
|
|
470
|
+
if cached:
|
|
471
|
+
return cached
|
|
472
|
+
|
|
473
|
+
result = await func(self, args, ctx)
|
|
474
|
+
|
|
475
|
+
if cache and result.success:
|
|
476
|
+
cache.set(self.name, args, result)
|
|
477
|
+
|
|
478
|
+
return result
|
|
479
|
+
|
|
480
|
+
wrapper._cacheable = True
|
|
481
|
+
wrapper._cache_ttl = ttl
|
|
482
|
+
return wrapper
|
|
483
|
+
|
|
484
|
+
return decorator
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def retry(max_retries: int = 3, delay: float = 1.0):
|
|
488
|
+
"""Decorator to add retry logic to a tool method."""
|
|
489
|
+
|
|
490
|
+
def decorator(func):
|
|
491
|
+
@wraps(func)
|
|
492
|
+
async def wrapper(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
|
|
493
|
+
last_error = None
|
|
494
|
+
|
|
495
|
+
for attempt in range(max_retries + 1):
|
|
496
|
+
try:
|
|
497
|
+
result = await func(self, args, ctx)
|
|
498
|
+
if result.success:
|
|
499
|
+
return result
|
|
500
|
+
last_error = result.error
|
|
501
|
+
except Exception as e:
|
|
502
|
+
last_error = str(e)
|
|
503
|
+
|
|
504
|
+
if attempt < max_retries:
|
|
505
|
+
await asyncio.sleep(delay * (2**attempt))
|
|
506
|
+
|
|
507
|
+
return ToolResult(
|
|
508
|
+
success=False,
|
|
509
|
+
output="",
|
|
510
|
+
error=f"Failed after {max_retries + 1} attempts: {last_error}",
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
return wrapper
|
|
514
|
+
|
|
515
|
+
return decorator
|