quantalogic 0.2.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.
- quantalogic/__init__.py +20 -0
- quantalogic/agent.py +638 -0
- quantalogic/agent_config.py +138 -0
- quantalogic/coding_agent.py +83 -0
- quantalogic/event_emitter.py +223 -0
- quantalogic/generative_model.py +226 -0
- quantalogic/interactive_text_editor.py +190 -0
- quantalogic/main.py +185 -0
- quantalogic/memory.py +217 -0
- quantalogic/model_names.py +19 -0
- quantalogic/print_event.py +66 -0
- quantalogic/prompts.py +99 -0
- quantalogic/server/__init__.py +3 -0
- quantalogic/server/agent_server.py +633 -0
- quantalogic/server/models.py +60 -0
- quantalogic/server/routes.py +117 -0
- quantalogic/server/state.py +199 -0
- quantalogic/server/static/js/event_visualizer.js +430 -0
- quantalogic/server/static/js/quantalogic.js +571 -0
- quantalogic/server/templates/index.html +134 -0
- quantalogic/tool_manager.py +68 -0
- quantalogic/tools/__init__.py +46 -0
- quantalogic/tools/agent_tool.py +88 -0
- quantalogic/tools/download_http_file_tool.py +64 -0
- quantalogic/tools/edit_whole_content_tool.py +70 -0
- quantalogic/tools/elixir_tool.py +240 -0
- quantalogic/tools/execute_bash_command_tool.py +116 -0
- quantalogic/tools/input_question_tool.py +57 -0
- quantalogic/tools/language_handlers/__init__.py +21 -0
- quantalogic/tools/language_handlers/c_handler.py +33 -0
- quantalogic/tools/language_handlers/cpp_handler.py +33 -0
- quantalogic/tools/language_handlers/go_handler.py +33 -0
- quantalogic/tools/language_handlers/java_handler.py +37 -0
- quantalogic/tools/language_handlers/javascript_handler.py +42 -0
- quantalogic/tools/language_handlers/python_handler.py +29 -0
- quantalogic/tools/language_handlers/rust_handler.py +33 -0
- quantalogic/tools/language_handlers/scala_handler.py +33 -0
- quantalogic/tools/language_handlers/typescript_handler.py +42 -0
- quantalogic/tools/list_directory_tool.py +123 -0
- quantalogic/tools/llm_tool.py +119 -0
- quantalogic/tools/markitdown_tool.py +105 -0
- quantalogic/tools/nodejs_tool.py +515 -0
- quantalogic/tools/python_tool.py +469 -0
- quantalogic/tools/read_file_block_tool.py +140 -0
- quantalogic/tools/read_file_tool.py +79 -0
- quantalogic/tools/replace_in_file_tool.py +300 -0
- quantalogic/tools/ripgrep_tool.py +353 -0
- quantalogic/tools/search_definition_names.py +419 -0
- quantalogic/tools/task_complete_tool.py +35 -0
- quantalogic/tools/tool.py +146 -0
- quantalogic/tools/unified_diff_tool.py +387 -0
- quantalogic/tools/write_file_tool.py +97 -0
- quantalogic/utils/__init__.py +17 -0
- quantalogic/utils/ask_user_validation.py +12 -0
- quantalogic/utils/download_http_file.py +77 -0
- quantalogic/utils/get_coding_environment.py +15 -0
- quantalogic/utils/get_environment.py +26 -0
- quantalogic/utils/get_quantalogic_rules_content.py +19 -0
- quantalogic/utils/git_ls.py +121 -0
- quantalogic/utils/read_file.py +54 -0
- quantalogic/utils/read_http_text_content.py +101 -0
- quantalogic/xml_parser.py +242 -0
- quantalogic/xml_tool_parser.py +99 -0
- quantalogic-0.2.0.dist-info/LICENSE +201 -0
- quantalogic-0.2.0.dist-info/METADATA +1034 -0
- quantalogic-0.2.0.dist-info/RECORD +68 -0
- quantalogic-0.2.0.dist-info/WHEEL +4 -0
- quantalogic-0.2.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en" class="h-full bg-gray-100">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
7
|
+
<meta http-equiv="Pragma" content="no-cache">
|
8
|
+
<meta http-equiv="Expires" content="0">
|
9
|
+
<title>QuantaLogic AI Assistant</title>
|
10
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
11
|
+
<script src="https://unpkg.com/marked/marked.min.js"></script>
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
13
|
+
<link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
|
14
|
+
<script>
|
15
|
+
tailwind.config = {
|
16
|
+
theme: {
|
17
|
+
extend: {
|
18
|
+
colors: {
|
19
|
+
primary: {
|
20
|
+
50: '#f0f9ff',
|
21
|
+
100: '#e0f2fe',
|
22
|
+
500: '#2563eb',
|
23
|
+
600: '#1d4ed8',
|
24
|
+
700: '#1e40af',
|
25
|
+
},
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
</script>
|
31
|
+
<script type="module" src="/static/js/quantalogic.js?v={{ range(1, 100000) | random }}"></script>
|
32
|
+
</head>
|
33
|
+
<body class="h-full flex flex-col">
|
34
|
+
<!-- Header -->
|
35
|
+
<header class="bg-white border-b border-gray-200 py-2">
|
36
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
37
|
+
<div class="flex justify-between items-center">
|
38
|
+
<div class="flex items-center space-x-3">
|
39
|
+
<h1 class="text-lg font-semibold text-gray-900">QuantaLogic</h1>
|
40
|
+
<span class="text-sm text-gray-500">AI Assistant</span>
|
41
|
+
</div>
|
42
|
+
<div class="flex items-center space-x-3">
|
43
|
+
<select id="modelSelect" class="block w-48 text-sm rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500">
|
44
|
+
<option value="openrouter/deepseek/deepseek-chat">DeepSeek Chat</option>
|
45
|
+
<option value="ollama/qwen2.5-coder:14b">Qwen Coder</option>
|
46
|
+
<option value="gpt-4o-mini">gpt-4o-mini</option>
|
47
|
+
</select>
|
48
|
+
<div id="connectionStatus" class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full"></div>
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</header>
|
53
|
+
|
54
|
+
<!-- Main Content -->
|
55
|
+
<main class="flex-1 flex min-h-0">
|
56
|
+
<!-- Left Panel - Task Input -->
|
57
|
+
<div class="w-1/2 p-4 flex flex-col min-h-0">
|
58
|
+
<div class="bg-white rounded-lg shadow-sm p-4 flex flex-col flex-1">
|
59
|
+
<div class="flex items-center justify-between mb-4">
|
60
|
+
<h2 class="text-lg font-semibold text-gray-900">Task Input</h2>
|
61
|
+
<div class="flex items-center space-x-4">
|
62
|
+
<label class="flex items-center space-x-2">
|
63
|
+
<span class="text-sm text-gray-600">Max Iterations:</span>
|
64
|
+
<input type="number" id="maxIterations"
|
65
|
+
class="w-20 rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
|
66
|
+
value="30" min="1" max="100">
|
67
|
+
</label>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
<textarea id="taskInput"
|
71
|
+
class="flex-1 p-4 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm resize-none"
|
72
|
+
placeholder="Describe your task here..."></textarea>
|
73
|
+
<div class="mt-4 flex justify-end">
|
74
|
+
<button id="submitTask"
|
75
|
+
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed">
|
76
|
+
<span>Submit Task</span>
|
77
|
+
<div class="loading-indicator hidden ml-2">
|
78
|
+
<svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
79
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
80
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
81
|
+
</svg>
|
82
|
+
</div>
|
83
|
+
</button>
|
84
|
+
</div>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
|
88
|
+
<!-- Right Panel - Events and Results -->
|
89
|
+
<div class="w-1/2 p-4 flex flex-col min-h-0">
|
90
|
+
<div class="bg-white rounded-lg shadow-sm p-4 flex flex-col flex-1 overflow-hidden">
|
91
|
+
<div class="flex justify-between items-center mb-4">
|
92
|
+
<h2 class="text-lg font-semibold text-gray-900">Events & Results</h2>
|
93
|
+
<div class="flex items-center space-x-4">
|
94
|
+
<label class="flex items-center space-x-2">
|
95
|
+
<input type="checkbox" id="autoScroll" checked
|
96
|
+
class="rounded text-primary-600 focus:ring-primary-500">
|
97
|
+
<span class="text-sm text-gray-600">Auto-scroll</span>
|
98
|
+
</label>
|
99
|
+
<button id="clearEvents"
|
100
|
+
class="inline-flex items-center px-3 py-1 text-sm text-gray-600 hover:text-gray-900 focus:outline-none">
|
101
|
+
Clear
|
102
|
+
</button>
|
103
|
+
</div>
|
104
|
+
</div>
|
105
|
+
<div class="flex-1 flex flex-col min-h-0 overflow-hidden">
|
106
|
+
<!-- Events Container -->
|
107
|
+
<div id="eventsContainer" class="flex-1 overflow-y-auto space-y-2 min-h-0 mb-4">
|
108
|
+
<!-- Events will be inserted here -->
|
109
|
+
</div>
|
110
|
+
|
111
|
+
<!-- Final Answer Section (Initially Hidden) -->
|
112
|
+
<div id="finalAnswer" class="hidden border-t border-gray-200 pt-4 overflow-y-auto">
|
113
|
+
<div class="bg-green-50 rounded-lg p-4">
|
114
|
+
<div class="flex items-center justify-between mb-2">
|
115
|
+
<h3 class="text-lg font-semibold text-green-800">Final Answer</h3>
|
116
|
+
<button id="copyAnswer" class="inline-flex items-center px-3 py-1 text-sm font-medium rounded-md text-green-700 bg-green-100 hover:bg-green-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
117
|
+
<svg class="h-4 w-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
118
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
119
|
+
</svg>
|
120
|
+
<span>Copy</span>
|
121
|
+
</button>
|
122
|
+
</div>
|
123
|
+
<div class="prose prose-sm max-w-none">
|
124
|
+
<div class="answer-content"></div>
|
125
|
+
</div>
|
126
|
+
</div>
|
127
|
+
</div>
|
128
|
+
</div>
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
</main>
|
132
|
+
|
133
|
+
</body>
|
134
|
+
</html>
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"""Tool dictionary for the agent."""
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
from quantalogic.tools.tool import Tool
|
7
|
+
|
8
|
+
|
9
|
+
class ToolManager(BaseModel):
|
10
|
+
"""Tool dictionary for the agent."""
|
11
|
+
|
12
|
+
tools: dict[str, Tool] = {}
|
13
|
+
|
14
|
+
def tool_names(self) -> list[str]:
|
15
|
+
"""Get the names of all tools in the tool dictionary."""
|
16
|
+
logger.debug("Getting tool names")
|
17
|
+
return list(self.tools.keys())
|
18
|
+
|
19
|
+
def add(self, tool: Tool):
|
20
|
+
"""Add a tool to the tool dictionary."""
|
21
|
+
logger.info(f"Adding tool: {tool.name} to tool dictionary")
|
22
|
+
self.tools[tool.name] = tool
|
23
|
+
|
24
|
+
def add_list(self, tools: list[Tool]):
|
25
|
+
"""Add a list of tools to the tool dictionary."""
|
26
|
+
logger.info(f"Adding {len(tools)} tools to tool dictionary")
|
27
|
+
for tool in tools:
|
28
|
+
self.add(tool)
|
29
|
+
|
30
|
+
def remove(self, tool_name: str) -> bool:
|
31
|
+
"""Remove a tool from the tool dictionary."""
|
32
|
+
logger.info(f"Removing tool: {tool_name} from tool dictionary")
|
33
|
+
del self.tools[tool_name]
|
34
|
+
return True
|
35
|
+
|
36
|
+
def get(self, tool_name: str) -> Tool:
|
37
|
+
"""Get a tool from the tool dictionary."""
|
38
|
+
logger.debug(f"Getting tool: {tool_name} from tool dictionary")
|
39
|
+
return self.tools[tool_name]
|
40
|
+
|
41
|
+
def list(self):
|
42
|
+
"""List all tools in the tool dictionary."""
|
43
|
+
logger.debug("Listing all tools")
|
44
|
+
return list(self.tools.keys())
|
45
|
+
|
46
|
+
def execute(self, tool_name: str, **kwargs) -> str:
|
47
|
+
"""Execute a tool from the tool dictionary."""
|
48
|
+
logger.info(f"Executing tool: {tool_name} with arguments: {kwargs}")
|
49
|
+
try:
|
50
|
+
result = self.tools[tool_name].execute(**kwargs)
|
51
|
+
logger.debug(f"Tool {tool_name} execution completed successfully")
|
52
|
+
return result
|
53
|
+
except Exception as e:
|
54
|
+
logger.error(f"Error executing tool {tool_name}: {str(e)}")
|
55
|
+
raise
|
56
|
+
|
57
|
+
def to_markdown(self):
|
58
|
+
"""Create a comprehensive Markdown representation of the tool dictionary."""
|
59
|
+
logger.debug("Creating Markdown representation of tool dictionary")
|
60
|
+
markdown = ""
|
61
|
+
index: int = 1
|
62
|
+
for tool_name, tool in self.tools.items():
|
63
|
+
# use the tool's to_markdown method
|
64
|
+
markdown += f"### {index}. {tool_name}\n"
|
65
|
+
markdown += tool.to_markdown()
|
66
|
+
markdown += "\n"
|
67
|
+
index += 1
|
68
|
+
return markdown
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""Tools for the QuantaLogic agent."""
|
2
|
+
|
3
|
+
from .agent_tool import AgentTool
|
4
|
+
from .download_http_file_tool import DownloadHttpFileTool
|
5
|
+
from .edit_whole_content_tool import EditWholeContentTool
|
6
|
+
from .elixir_tool import ElixirTool
|
7
|
+
from .execute_bash_command_tool import ExecuteBashCommandTool
|
8
|
+
from .input_question_tool import InputQuestionTool
|
9
|
+
from .list_directory_tool import ListDirectoryTool
|
10
|
+
from .llm_tool import LLMTool
|
11
|
+
from .markitdown_tool import MarkitdownTool
|
12
|
+
from .nodejs_tool import NodeJsTool
|
13
|
+
from .python_tool import PythonTool
|
14
|
+
from .read_file_block_tool import ReadFileBlockTool
|
15
|
+
from .read_file_tool import ReadFileTool
|
16
|
+
from .replace_in_file_tool import ReplaceInFileTool
|
17
|
+
from .ripgrep_tool import RipgrepTool
|
18
|
+
from .search_definition_names import SearchDefinitionNames
|
19
|
+
from .task_complete_tool import TaskCompleteTool
|
20
|
+
from .tool import Tool, ToolArgument
|
21
|
+
from .unified_diff_tool import UnifiedDiffTool
|
22
|
+
from .write_file_tool import WriteFileTool
|
23
|
+
|
24
|
+
__all__ = [
|
25
|
+
"Tool",
|
26
|
+
"ToolArgument",
|
27
|
+
"TaskCompleteTool",
|
28
|
+
"ReadFileTool",
|
29
|
+
"WriteFileTool",
|
30
|
+
"InputQuestionTool",
|
31
|
+
"ListDirectoryTool",
|
32
|
+
"LLMTool",
|
33
|
+
"ExecuteBashCommandTool",
|
34
|
+
"PythonTool",
|
35
|
+
"ElixirTool",
|
36
|
+
"NodeJsTool",
|
37
|
+
"UnifiedDiffTool",
|
38
|
+
"ReplaceInFileTool",
|
39
|
+
"AgentTool",
|
40
|
+
"ReadFileBlockTool",
|
41
|
+
"RipgrepTool",
|
42
|
+
"SearchDefinitionNames",
|
43
|
+
"MarkitdownTool",
|
44
|
+
"DownloadHttpFileTool",
|
45
|
+
"EditWholeContentTool",
|
46
|
+
]
|
@@ -0,0 +1,88 @@
|
|
1
|
+
"""Agent Tool for delegating tasks to another agent."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from typing import TYPE_CHECKING, Any
|
5
|
+
|
6
|
+
from pydantic import Field, model_validator
|
7
|
+
|
8
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
9
|
+
|
10
|
+
# Use conditional import to resolve circular dependency
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from quantalogic.agent import Agent
|
13
|
+
|
14
|
+
|
15
|
+
class AgentTool(Tool):
|
16
|
+
"""Tool to execute tasks using another agent."""
|
17
|
+
|
18
|
+
name: str = Field(default="agent_tool")
|
19
|
+
description: str = Field(
|
20
|
+
default=(
|
21
|
+
"Executes tasks using a specified agent. "
|
22
|
+
"This tool enables an agent to delegate tasks to another agent."
|
23
|
+
"A delegate agent doesn't have access to the memory and the conversation of the main agent."
|
24
|
+
"Context must be provided by the main agent."
|
25
|
+
"You must use variable interpolation syntax to use the context of the main agent."
|
26
|
+
)
|
27
|
+
)
|
28
|
+
agent_role: str = Field(..., description="The role of the agent (e.g., expert, assistant)")
|
29
|
+
agent: Any = Field(..., description="The agent to delegate tasks to")
|
30
|
+
|
31
|
+
# Type hint for static type checkers
|
32
|
+
if TYPE_CHECKING:
|
33
|
+
agent: "Agent"
|
34
|
+
|
35
|
+
# Allow extra fields
|
36
|
+
model_config = {"extra": "allow"}
|
37
|
+
|
38
|
+
# Tool Arguments
|
39
|
+
arguments: list[ToolArgument] = Field(
|
40
|
+
default_factory=lambda: [
|
41
|
+
ToolArgument(
|
42
|
+
name="task",
|
43
|
+
arg_type="string",
|
44
|
+
description="The task to delegate to the specified agent.",
|
45
|
+
required=True,
|
46
|
+
example="Summarize the latest news.",
|
47
|
+
),
|
48
|
+
]
|
49
|
+
)
|
50
|
+
|
51
|
+
@model_validator(mode="before")
|
52
|
+
def validate_agent(cls, values: dict[str, Any]) -> dict[str, Any]:
|
53
|
+
"""Validate the provided agent and its role."""
|
54
|
+
agent = values.get("agent")
|
55
|
+
# Lazy import to avoid circular dependency
|
56
|
+
from quantalogic.agent import Agent
|
57
|
+
|
58
|
+
if not isinstance(agent, Agent):
|
59
|
+
raise ValueError("The agent must be an instance of the Agent class.")
|
60
|
+
return values
|
61
|
+
|
62
|
+
def execute(self, task: str) -> str:
|
63
|
+
"""Execute the tool to delegate a task to the specified agent.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
task (str): The task to delegate to the agent.
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
str: The result of the delegated task.
|
70
|
+
"""
|
71
|
+
try:
|
72
|
+
logging.info(f"Delegating task to agent with role '{self.agent_role}': {task}")
|
73
|
+
# Lazy import to avoid circular dependency
|
74
|
+
from quantalogic.agent import Agent
|
75
|
+
|
76
|
+
# Ensure the agent is of the correct type
|
77
|
+
if not isinstance(self.agent, Agent):
|
78
|
+
raise ValueError("The agent must be an instance of the Agent class.")
|
79
|
+
|
80
|
+
# Delegate the task to the agent
|
81
|
+
result = self.agent.solve_task(task)
|
82
|
+
|
83
|
+
logging.info(f"Task delegation completed for agent with role '{self.agent_role}'")
|
84
|
+
return result
|
85
|
+
|
86
|
+
except Exception as e:
|
87
|
+
logging.error(f"Error delegating task to agent: {str(e)}")
|
88
|
+
raise
|
@@ -0,0 +1,64 @@
|
|
1
|
+
"""Tool for downloading a file from an HTTP URL and saving it to a local file."""
|
2
|
+
|
3
|
+
from urllib.parse import urlparse
|
4
|
+
|
5
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
6
|
+
from quantalogic.utils.download_http_file import download_http_file
|
7
|
+
|
8
|
+
|
9
|
+
class DownloadHttpFileTool(Tool):
|
10
|
+
"""Tool for downloading a file from an HTTP URL and saving it to a local file."""
|
11
|
+
|
12
|
+
name: str = "download_http_file_tool"
|
13
|
+
description: str = "Downloads a file from an HTTP URL and saves it to a local file."
|
14
|
+
arguments: list = [
|
15
|
+
ToolArgument(
|
16
|
+
name="url",
|
17
|
+
arg_type="string",
|
18
|
+
description="The URL of the file to download.",
|
19
|
+
required=True,
|
20
|
+
example="https://example.com/data.txt",
|
21
|
+
),
|
22
|
+
ToolArgument(
|
23
|
+
name="output_path",
|
24
|
+
arg_type="string",
|
25
|
+
description="The local path where the downloaded file will be saved.",
|
26
|
+
required=True,
|
27
|
+
example="/path/to/save/data.txt",
|
28
|
+
),
|
29
|
+
]
|
30
|
+
|
31
|
+
def _is_url(self, path: str) -> bool:
|
32
|
+
"""Check if the given path is a valid URL."""
|
33
|
+
try:
|
34
|
+
result = urlparse(path)
|
35
|
+
return all([result.scheme, result.netloc])
|
36
|
+
except ValueError:
|
37
|
+
return False
|
38
|
+
|
39
|
+
def execute(self, url: str, output_path: str) -> str:
|
40
|
+
"""Downloads a file from an HTTP URL and saves it to a local file.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
url (str): The URL of the file to download.
|
44
|
+
output_path (str): The local path where the downloaded file will be saved.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
str: A message indicating the result of the download operation.
|
48
|
+
"""
|
49
|
+
if not self._is_url(url):
|
50
|
+
return f"Error: {url} is not a valid URL."
|
51
|
+
|
52
|
+
try:
|
53
|
+
result = download_http_file(url, output_path)
|
54
|
+
if result:
|
55
|
+
return f"File downloaded successfully and saved to {output_path}."
|
56
|
+
else:
|
57
|
+
return f"Error downloading file from {url}: Unable to download."
|
58
|
+
except Exception as e:
|
59
|
+
return f"Error downloading file from {url}: {str(e)}"
|
60
|
+
|
61
|
+
|
62
|
+
if __name__ == "__main__":
|
63
|
+
tool = DownloadHttpFileTool()
|
64
|
+
print(tool.to_markdown())
|
@@ -0,0 +1,70 @@
|
|
1
|
+
"""Tool for editing the whole content of a file."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
|
5
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
6
|
+
|
7
|
+
|
8
|
+
class EditWholeContentTool(Tool):
|
9
|
+
"""Tool for replace the whole content of a file."""
|
10
|
+
|
11
|
+
name: str = "edit_whole_content_tool"
|
12
|
+
description: str = "Edits the whole content of an existing file."
|
13
|
+
need_validation: bool = True
|
14
|
+
arguments: list = [
|
15
|
+
ToolArgument(
|
16
|
+
name="file_path",
|
17
|
+
arg_type="string",
|
18
|
+
description="The path to the file to edit. Using an absolute path is recommended.",
|
19
|
+
required=True,
|
20
|
+
example="/path/to/file.txt",
|
21
|
+
),
|
22
|
+
ToolArgument(
|
23
|
+
name="content",
|
24
|
+
arg_type="string",
|
25
|
+
description="""
|
26
|
+
The content to write to the file. Use CDATA to escape special characters.
|
27
|
+
Don't add newlines at the beginning or end of the content.
|
28
|
+
""",
|
29
|
+
required=True,
|
30
|
+
example="Hello, world!",
|
31
|
+
),
|
32
|
+
]
|
33
|
+
|
34
|
+
def execute(self, file_path: str, content: str) -> str:
|
35
|
+
"""Writes a file with the given content.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
file_path (str): The path to the file to write.
|
39
|
+
content (str): The content to write to the file.
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
str: The content of the file.
|
43
|
+
|
44
|
+
Raises:
|
45
|
+
FileExistsError: If the file already exists and append_mode is False and overwrite is False.
|
46
|
+
"""
|
47
|
+
## Handle tilde expansion
|
48
|
+
if file_path.startswith("~"):
|
49
|
+
# Expand the tilde to the user's home directory
|
50
|
+
file_path = os.path.expanduser(file_path)
|
51
|
+
|
52
|
+
# Convert relative paths to absolute paths using current working directory
|
53
|
+
if not os.path.isabs(file_path):
|
54
|
+
file_path = os.path.abspath(os.path.join(os.getcwd(), file_path))
|
55
|
+
|
56
|
+
# Ensure parent directory exists
|
57
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
58
|
+
|
59
|
+
if not os.path.exists(file_path):
|
60
|
+
raise FileExistsError(f"File {file_path} does not exist.")
|
61
|
+
|
62
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
63
|
+
f.write(content)
|
64
|
+
file_size = os.path.getsize(file_path)
|
65
|
+
return f"File {file_path} written successfully. Size: {file_size} bytes."
|
66
|
+
|
67
|
+
|
68
|
+
if __name__ == "__main__":
|
69
|
+
tool = EditWholeContentTool()
|
70
|
+
print(tool.to_markdown())
|