autosh 0.0.0__py3-none-any.whl → 0.0.1__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.
- autosh/config.py +78 -0
- autosh/main.py +164 -0
- autosh/md.py +394 -0
- autosh/plugins/__init__.py +87 -0
- autosh/plugins/calc.py +22 -0
- autosh/plugins/cli.py +222 -0
- autosh/plugins/clock.py +20 -0
- autosh/plugins/code.py +68 -0
- autosh/plugins/search.py +90 -0
- autosh/plugins/web.py +73 -0
- autosh/session.py +193 -0
- autosh-0.0.1.dist-info/METADATA +77 -0
- autosh-0.0.1.dist-info/RECORD +17 -0
- {autosh-0.0.0.dist-info → autosh-0.0.1.dist-info}/WHEEL +1 -1
- autosh-0.0.1.dist-info/entry_points.txt +3 -0
- {autosh-0.0.0.dist-info → autosh-0.0.1.dist-info/licenses}/LICENSE +1 -1
- autosh-0.0.0.dist-info/METADATA +0 -16
- autosh-0.0.0.dist-info/RECORD +0 -5
- {reserved → autosh}/__init__.py +0 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
import rich
|
2
|
+
from rich.prompt import Confirm
|
3
|
+
from rich.panel import Panel
|
4
|
+
from rich.console import RenderableType
|
5
|
+
from autosh.config import CLI_OPTIONS, CONFIG
|
6
|
+
|
7
|
+
|
8
|
+
def banner(tag: str, text: str | None = None, dim: str | None = None):
|
9
|
+
if CLI_OPTIONS.quiet:
|
10
|
+
return
|
11
|
+
s = f"[bold magenta]{tag}[/bold magenta]"
|
12
|
+
if text:
|
13
|
+
s += f" [italic magenta]{text}[/italic magenta]"
|
14
|
+
if dim:
|
15
|
+
s += f" [italic dim]{dim}[/italic dim]"
|
16
|
+
s += "\n"
|
17
|
+
rich.print(s)
|
18
|
+
|
19
|
+
|
20
|
+
def confirm(message: str):
|
21
|
+
if CLI_OPTIONS.yes:
|
22
|
+
return True
|
23
|
+
result = Confirm.ask(
|
24
|
+
f"[magenta]{message}[/magenta]", default=True, case_sensitive=False
|
25
|
+
)
|
26
|
+
if not CLI_OPTIONS.quiet:
|
27
|
+
rich.print()
|
28
|
+
return result
|
29
|
+
|
30
|
+
|
31
|
+
def cmd_preview_panel(title: str, content: RenderableType, short: str | None = None):
|
32
|
+
if CLI_OPTIONS.quiet and not CLI_OPTIONS.yes:
|
33
|
+
if short:
|
34
|
+
rich.print(f"[magenta]{short}[/magenta]\n")
|
35
|
+
return
|
36
|
+
panel = Panel.fit(content, title=f"[magenta]{title}[/magenta]", title_align="left")
|
37
|
+
rich.print(panel)
|
38
|
+
rich.print()
|
39
|
+
|
40
|
+
|
41
|
+
def cmd_result_panel(
|
42
|
+
title: str,
|
43
|
+
out: str | None = None,
|
44
|
+
err: str | None = None,
|
45
|
+
):
|
46
|
+
if CLI_OPTIONS.quiet:
|
47
|
+
return
|
48
|
+
if isinstance(out, str):
|
49
|
+
out = out.strip()
|
50
|
+
if isinstance(err, str):
|
51
|
+
err = err.strip()
|
52
|
+
if not out and not err:
|
53
|
+
rich.print(title)
|
54
|
+
else:
|
55
|
+
text = out if out else ""
|
56
|
+
text += (("\n---\n" if out else "") + err) if err else ""
|
57
|
+
panel = Panel.fit(text, title=title, title_align="left", style="dim")
|
58
|
+
rich.print(panel)
|
59
|
+
if not CLI_OPTIONS.quiet:
|
60
|
+
rich.print()
|
61
|
+
|
62
|
+
|
63
|
+
from . import calc
|
64
|
+
from . import clock
|
65
|
+
from . import code
|
66
|
+
from . import search
|
67
|
+
from . import web
|
68
|
+
from . import cli
|
69
|
+
|
70
|
+
|
71
|
+
def create_plugins():
|
72
|
+
"""Get all plugins in the autosh.plugins module."""
|
73
|
+
cfgs = CONFIG.plugins
|
74
|
+
plugins = []
|
75
|
+
if cfgs.calc is not None:
|
76
|
+
plugins.append(calc.CalculatorPlugin(cfgs.calc.model_dump()))
|
77
|
+
if cfgs.cli is not None:
|
78
|
+
plugins.append(cli.CLIPlugin(cfgs.cli.model_dump()))
|
79
|
+
if cfgs.clock is not None:
|
80
|
+
plugins.append(clock.ClockPlugin(cfgs.clock.model_dump()))
|
81
|
+
if cfgs.code is not None:
|
82
|
+
plugins.append(code.CodePlugin(cfgs.code.model_dump()))
|
83
|
+
if cfgs.search is not None:
|
84
|
+
plugins.append(search.SearchPlugin(cfgs.search.model_dump()))
|
85
|
+
if cfgs.web is not None:
|
86
|
+
plugins.append(web.WebPlugin(cfgs.web.model_dump()))
|
87
|
+
return plugins
|
autosh/plugins/calc.py
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
from agentia.plugins import tool, Plugin
|
2
|
+
from typing import Annotated
|
3
|
+
from . import banner
|
4
|
+
|
5
|
+
|
6
|
+
class CalculatorPlugin(Plugin):
|
7
|
+
NAME = "calc"
|
8
|
+
|
9
|
+
@tool
|
10
|
+
def evaluate(
|
11
|
+
self,
|
12
|
+
expression: Annotated[
|
13
|
+
str, "The math expression to evaluate. Must be an valid python expression."
|
14
|
+
],
|
15
|
+
):
|
16
|
+
"""
|
17
|
+
Execute a math expression and return the result. The expression must be an valid python expression that can be execuated by `eval()`.
|
18
|
+
"""
|
19
|
+
banner("CALC", expression)
|
20
|
+
|
21
|
+
result = eval(expression)
|
22
|
+
return result
|
autosh/plugins/cli.py
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
from typing import Annotated
|
4
|
+
from agentia.plugins import Plugin, tool
|
5
|
+
import rich
|
6
|
+
import subprocess
|
7
|
+
from enum import StrEnum
|
8
|
+
|
9
|
+
from autosh.config import CLI_OPTIONS
|
10
|
+
|
11
|
+
from . import banner, confirm, cmd_result_panel, cmd_preview_panel
|
12
|
+
|
13
|
+
|
14
|
+
class Color(StrEnum):
|
15
|
+
black = "black"
|
16
|
+
red = "red"
|
17
|
+
green = "green"
|
18
|
+
yellow = "yellow"
|
19
|
+
blue = "blue"
|
20
|
+
magenta = "magenta"
|
21
|
+
cyan = "cyan"
|
22
|
+
white = "white"
|
23
|
+
bright_black = "bright_black"
|
24
|
+
bright_red = "bright_red"
|
25
|
+
bright_green = "bright_green"
|
26
|
+
bright_yellow = "bright_yellow"
|
27
|
+
bright_blue = "bright_blue"
|
28
|
+
bright_magenta = "bright_magenta"
|
29
|
+
bright_cyan = "bright_cyan"
|
30
|
+
bright_white = "bright_white"
|
31
|
+
dim = "dim"
|
32
|
+
|
33
|
+
|
34
|
+
class CLIPlugin(Plugin):
|
35
|
+
EXIT_CODE = 0
|
36
|
+
|
37
|
+
@tool
|
38
|
+
def print(
|
39
|
+
self,
|
40
|
+
text: Annotated[
|
41
|
+
str,
|
42
|
+
"The text to print. Can be markdown or using python-rich's markup syntax.",
|
43
|
+
],
|
44
|
+
color: Annotated[Color | None, "The color of the text"] = None,
|
45
|
+
bold: Annotated[bool, "Whether to print the text in bold"] = False,
|
46
|
+
italic: Annotated[bool, "Whether to print the text in italic"] = False,
|
47
|
+
stderr: Annotated[bool, "Whether to print the text to stderr"] = False,
|
48
|
+
end: Annotated[str, "The text to print at the end"] = "\n",
|
49
|
+
):
|
50
|
+
"""
|
51
|
+
Print an important message to the terminal. NOTE: Important message ONLY! Don't use it when you want to say something to the user.
|
52
|
+
"""
|
53
|
+
if color:
|
54
|
+
text = f"[{color}]{text}[/{color}]"
|
55
|
+
if bold:
|
56
|
+
text = f"[bold]{text}[/bold]"
|
57
|
+
if italic:
|
58
|
+
text = f"[italic]{text}[/italic]"
|
59
|
+
rich.print(text, file=sys.stderr if stderr else sys.stdout, end=end)
|
60
|
+
return "DONE. You can continue and no need to repeat the text"
|
61
|
+
|
62
|
+
@tool
|
63
|
+
def chdir(self, path: Annotated[str, "The path to the new working directory"]):
|
64
|
+
"""
|
65
|
+
Changes the current working directory of the terminal to another directory.
|
66
|
+
"""
|
67
|
+
banner("CWD", path)
|
68
|
+
if not os.path.exists(path):
|
69
|
+
raise FileNotFoundError(f"Path `{path}` does not exist.")
|
70
|
+
os.chdir(path)
|
71
|
+
|
72
|
+
@tool
|
73
|
+
def get_argv(self):
|
74
|
+
"""
|
75
|
+
Get the command line arguments.
|
76
|
+
"""
|
77
|
+
banner("GET ARGV")
|
78
|
+
if not CLI_OPTIONS.script:
|
79
|
+
return CLI_OPTIONS.args
|
80
|
+
return {
|
81
|
+
"script": str(CLI_OPTIONS.script),
|
82
|
+
"args": CLI_OPTIONS.args,
|
83
|
+
}
|
84
|
+
|
85
|
+
@tool
|
86
|
+
def read(
|
87
|
+
self,
|
88
|
+
path: Annotated[str, "The path to the file to read"],
|
89
|
+
):
|
90
|
+
"""
|
91
|
+
Read a file and print its content.
|
92
|
+
"""
|
93
|
+
banner("READ", path)
|
94
|
+
if not os.path.exists(path):
|
95
|
+
raise FileNotFoundError(f"File `{path}` does not exist.")
|
96
|
+
if not os.path.isfile(path):
|
97
|
+
raise FileNotFoundError(f"Path `{path}` is not a file.")
|
98
|
+
with open(path, "r") as f:
|
99
|
+
content = f.read()
|
100
|
+
return content
|
101
|
+
|
102
|
+
@tool
|
103
|
+
def write(
|
104
|
+
self,
|
105
|
+
path: Annotated[str, "The path to the file to write"],
|
106
|
+
content: Annotated[str, "The content to write to the file"],
|
107
|
+
create: Annotated[
|
108
|
+
bool, "Whether to create the file if it does not exist"
|
109
|
+
] = True,
|
110
|
+
append: Annotated[bool, "Whether to append to the file if it exists"] = False,
|
111
|
+
):
|
112
|
+
"""
|
113
|
+
Write or append text content to a file.
|
114
|
+
"""
|
115
|
+
banner("WRITE" if not append else "APPEND", path, f"({len(content)} bytes)")
|
116
|
+
|
117
|
+
if not create and not os.path.exists(path):
|
118
|
+
raise FileNotFoundError(f"File `{path}` does not exist.")
|
119
|
+
if not create and not os.path.isfile(path):
|
120
|
+
raise FileNotFoundError(f"Path `{path}` is not a file.")
|
121
|
+
if path == str(CLI_OPTIONS.script):
|
122
|
+
raise FileExistsError(
|
123
|
+
f"No, you cannot overwrite the script file `{path}`. You're likely writing to it by mistake."
|
124
|
+
)
|
125
|
+
if not confirm("Write file?"):
|
126
|
+
return {"error": "The user declined the write operation."}
|
127
|
+
flag = "a" if append else "w"
|
128
|
+
if create:
|
129
|
+
flag += "+"
|
130
|
+
with open(path, flag) as f:
|
131
|
+
f.write(content)
|
132
|
+
return "DONE. You can continue and no need to repeat the text"
|
133
|
+
|
134
|
+
@tool
|
135
|
+
def stdin_readline(
|
136
|
+
self,
|
137
|
+
prompt: Annotated[
|
138
|
+
str | None, "The optional prompt to display before reading from stdin"
|
139
|
+
] = None,
|
140
|
+
):
|
141
|
+
"""
|
142
|
+
Read a line from stdin.
|
143
|
+
"""
|
144
|
+
if not sys.stdin.isatty():
|
145
|
+
raise RuntimeError("stdin is not a terminal.")
|
146
|
+
return input(prompt)
|
147
|
+
|
148
|
+
@tool
|
149
|
+
def stdin_readall(self):
|
150
|
+
"""
|
151
|
+
Read all from stdin until EOF.
|
152
|
+
"""
|
153
|
+
if sys.stdin.isatty():
|
154
|
+
raise RuntimeError("No piped input. stdin is a terminal.")
|
155
|
+
if not CLI_OPTIONS.quiet:
|
156
|
+
rich.print("[bold magenta]READ ALL STDIN[/bold magenta]\n")
|
157
|
+
if CLI_OPTIONS.stdin_is_script:
|
158
|
+
raise RuntimeError("No piped input from stdin")
|
159
|
+
return sys.stdin.read()
|
160
|
+
|
161
|
+
@tool
|
162
|
+
def exec(
|
163
|
+
self,
|
164
|
+
command: Annotated[
|
165
|
+
str,
|
166
|
+
"The one-liner bash command to execute. This will be directly sent to `bash -c ...` so be careful with the quotes escaping!",
|
167
|
+
],
|
168
|
+
explanation: Annotated[
|
169
|
+
str,
|
170
|
+
"Explain what this command does, and what are you going to use it for.",
|
171
|
+
],
|
172
|
+
):
|
173
|
+
"""
|
174
|
+
Run a one-liner bash command
|
175
|
+
"""
|
176
|
+
|
177
|
+
def run():
|
178
|
+
cmd = ["bash", "-c", command]
|
179
|
+
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
180
|
+
|
181
|
+
# Print the command and explanation
|
182
|
+
cmd_preview_panel(
|
183
|
+
title="Run Command",
|
184
|
+
content=f"[magenta][bold]➜[/bold] [italic]{command}[/italic][/magenta]\n\n[dim]{explanation}[/dim]",
|
185
|
+
short=f"[magenta][bold]➜[/bold] [italic]{command}[/italic][/magenta]",
|
186
|
+
)
|
187
|
+
|
188
|
+
# Ask for confirmation
|
189
|
+
if not confirm("Execute this command?"):
|
190
|
+
return {"error": "The user declined to execute the command."}
|
191
|
+
|
192
|
+
# Execute the command
|
193
|
+
proc_result = run()
|
194
|
+
|
195
|
+
# Print the result
|
196
|
+
if not CLI_OPTIONS.quiet:
|
197
|
+
out = proc_result.stdout.decode("utf-8")
|
198
|
+
err = proc_result.stderr.decode("utf-8")
|
199
|
+
if not out and not err:
|
200
|
+
title = "[green][bold]✔[/bold] Command Finished[/green]"
|
201
|
+
else:
|
202
|
+
if proc_result.returncode != 0:
|
203
|
+
title = f"[red][bold]✘[/bold] Command Failed [{proc_result.returncode}][/red]"
|
204
|
+
else:
|
205
|
+
title = "[green][bold]✔[/bold] Command Finished[/green]"
|
206
|
+
cmd_result_panel(title, out, err)
|
207
|
+
|
208
|
+
result = {
|
209
|
+
"stdout": proc_result.stdout.decode("utf-8"),
|
210
|
+
"stderr": proc_result.stderr.decode("utf-8"),
|
211
|
+
"returncode": proc_result.returncode,
|
212
|
+
"success": proc_result.returncode == 0,
|
213
|
+
}
|
214
|
+
return result
|
215
|
+
|
216
|
+
@tool
|
217
|
+
def exit(self, exitcode: Annotated[int, "The exit code of this shell session"] = 0):
|
218
|
+
"""
|
219
|
+
Exit the current shell session with an optional exit code.
|
220
|
+
"""
|
221
|
+
banner("EXIT", str(exitcode))
|
222
|
+
sys.exit(exitcode)
|
autosh/plugins/clock.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
import datetime
|
2
|
+
from agentia.plugins import tool, Plugin
|
3
|
+
import tzlocal
|
4
|
+
|
5
|
+
from autosh.plugins import banner
|
6
|
+
|
7
|
+
|
8
|
+
class ClockPlugin(Plugin):
|
9
|
+
@tool
|
10
|
+
def get_current_time(self):
|
11
|
+
"""Get the current UTC time in ISO format"""
|
12
|
+
banner("GET TIME")
|
13
|
+
utc = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
14
|
+
local = datetime.datetime.now().isoformat()
|
15
|
+
timezone = tzlocal.get_localzone_name()
|
16
|
+
return {
|
17
|
+
"utc": utc,
|
18
|
+
"local": local,
|
19
|
+
"timezone": timezone,
|
20
|
+
}
|
autosh/plugins/code.py
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
from agentia.plugins import tool, Plugin
|
2
|
+
from typing import Annotated
|
3
|
+
import traceback
|
4
|
+
from rich.syntax import Syntax
|
5
|
+
from rich.console import group
|
6
|
+
from contextlib import redirect_stdout, redirect_stderr
|
7
|
+
import io
|
8
|
+
from . import confirm, cmd_result_panel, cmd_preview_panel
|
9
|
+
|
10
|
+
|
11
|
+
class CodePlugin(Plugin):
|
12
|
+
@tool
|
13
|
+
def execute(
|
14
|
+
self,
|
15
|
+
python_code: Annotated[str, "The python code to run."],
|
16
|
+
explanation: Annotated[
|
17
|
+
str, "Explain what this code does, and what are you going to use it for."
|
18
|
+
],
|
19
|
+
):
|
20
|
+
"""
|
21
|
+
Execute python code and return the result.
|
22
|
+
The python code must be a valid python source file that accepts no inputs.
|
23
|
+
Print results to stdout or stderr.
|
24
|
+
"""
|
25
|
+
|
26
|
+
@group()
|
27
|
+
def code_with_explanation():
|
28
|
+
yield Syntax(python_code.strip(), "python")
|
29
|
+
yield "\n[dim]───[/dim]\n"
|
30
|
+
yield f"[dim]{explanation}[/dim]"
|
31
|
+
|
32
|
+
cmd_preview_panel(
|
33
|
+
title="Run Python",
|
34
|
+
content=code_with_explanation(),
|
35
|
+
short=f"[bold]RUN[/bold] [italic]Python Code[/italic]",
|
36
|
+
)
|
37
|
+
|
38
|
+
if not confirm("Execute this code?"):
|
39
|
+
return {"error": "The user declined to execute the command."}
|
40
|
+
|
41
|
+
out = io.StringIO()
|
42
|
+
err = io.StringIO()
|
43
|
+
with redirect_stdout(out):
|
44
|
+
with redirect_stderr(err):
|
45
|
+
try:
|
46
|
+
exec(python_code, globals())
|
47
|
+
o = out.getvalue()
|
48
|
+
e = err.getvalue()
|
49
|
+
title = "[green][bold]✔[/bold] Finished[/green]"
|
50
|
+
result = {
|
51
|
+
"stdout": o,
|
52
|
+
"stderr": e,
|
53
|
+
"success": True,
|
54
|
+
}
|
55
|
+
except Exception as ex:
|
56
|
+
o = out.getvalue()
|
57
|
+
e = err.getvalue()
|
58
|
+
title = "[red][bold]✘[/bold] Failed [/red]"
|
59
|
+
result = {
|
60
|
+
"stdout": o,
|
61
|
+
"stderr": e,
|
62
|
+
"success": False,
|
63
|
+
"error": str(ex),
|
64
|
+
"traceback": repr(traceback.format_exc()),
|
65
|
+
}
|
66
|
+
|
67
|
+
cmd_result_panel(title, o, e)
|
68
|
+
return result
|
autosh/plugins/search.py
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
from agentia.plugins import tool, Plugin
|
2
|
+
from typing import Annotated, override
|
3
|
+
import os
|
4
|
+
from tavily import TavilyClient
|
5
|
+
|
6
|
+
from autosh.plugins import banner
|
7
|
+
|
8
|
+
|
9
|
+
class SearchPlugin(Plugin):
|
10
|
+
@override
|
11
|
+
async def init(self):
|
12
|
+
if "tavily_api_key" in self.config:
|
13
|
+
key = self.config["tavily_api_key"]
|
14
|
+
elif "TAVILY_API_KEY" in os.environ:
|
15
|
+
key = os.environ["TAVILY_API_KEY"]
|
16
|
+
else:
|
17
|
+
raise ValueError("Please set the TAVILY_API_KEY environment variable.")
|
18
|
+
self.__tavily = TavilyClient(api_key=key)
|
19
|
+
|
20
|
+
@tool
|
21
|
+
async def web_search(
|
22
|
+
self,
|
23
|
+
query: Annotated[
|
24
|
+
str, "The search query. Please be as specific and verbose as possible."
|
25
|
+
],
|
26
|
+
):
|
27
|
+
"""
|
28
|
+
Perform web search on the given query.
|
29
|
+
Returning the top related search results in json format.
|
30
|
+
When necessary, you need to combine this tool with the get_webpage_content tools (if available), to browse the web in depth by jumping through links.
|
31
|
+
"""
|
32
|
+
banner("WEB SEARCH", dim=query)
|
33
|
+
|
34
|
+
tavily_results = self.__tavily.search(
|
35
|
+
query=query,
|
36
|
+
search_depth="advanced",
|
37
|
+
# max_results=10,
|
38
|
+
include_answer=True,
|
39
|
+
include_images=True,
|
40
|
+
include_image_descriptions=True,
|
41
|
+
)
|
42
|
+
return tavily_results
|
43
|
+
|
44
|
+
@tool
|
45
|
+
async def news_search(
|
46
|
+
self,
|
47
|
+
query: Annotated[
|
48
|
+
str, "The search query. Please be as specific and verbose as possible."
|
49
|
+
],
|
50
|
+
):
|
51
|
+
"""
|
52
|
+
Perform news search on the given query.
|
53
|
+
Returning the top related results in json format.
|
54
|
+
"""
|
55
|
+
banner("NEWS SEARCH", dim=query)
|
56
|
+
|
57
|
+
tavily_results = self.__tavily.search(
|
58
|
+
query=query,
|
59
|
+
search_depth="advanced",
|
60
|
+
topic="news",
|
61
|
+
# max_results=10,
|
62
|
+
include_answer=True,
|
63
|
+
include_images=True,
|
64
|
+
include_image_descriptions=True,
|
65
|
+
)
|
66
|
+
return tavily_results
|
67
|
+
|
68
|
+
@tool
|
69
|
+
async def finance_search(
|
70
|
+
self,
|
71
|
+
query: Annotated[
|
72
|
+
str, "The search query. Please be as specific and verbose as possible."
|
73
|
+
],
|
74
|
+
):
|
75
|
+
"""
|
76
|
+
Search for finance-related news and information on the given query.
|
77
|
+
Returning the top related results in json format.
|
78
|
+
"""
|
79
|
+
banner("FINANCE SEARCH", dim=query)
|
80
|
+
|
81
|
+
tavily_results = self.__tavily.search(
|
82
|
+
query=query,
|
83
|
+
search_depth="advanced",
|
84
|
+
topic="finance",
|
85
|
+
# max_results=10,
|
86
|
+
include_answer=True,
|
87
|
+
include_images=True,
|
88
|
+
include_image_descriptions=True,
|
89
|
+
)
|
90
|
+
return tavily_results
|
autosh/plugins/web.py
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
from io import BytesIO
|
2
|
+
import os
|
3
|
+
from agentia.plugins import tool, Plugin
|
4
|
+
from typing import Annotated, override
|
5
|
+
import requests
|
6
|
+
from markdownify import markdownify
|
7
|
+
import uuid
|
8
|
+
from tavily import TavilyClient
|
9
|
+
|
10
|
+
from autosh.plugins import banner
|
11
|
+
|
12
|
+
|
13
|
+
class WebPlugin(Plugin):
|
14
|
+
|
15
|
+
@override
|
16
|
+
async def init(self):
|
17
|
+
if "tavily_api_key" in self.config:
|
18
|
+
key = self.config["tavily_api_key"]
|
19
|
+
elif "TAVILY_API_KEY" in os.environ:
|
20
|
+
key = os.environ["TAVILY_API_KEY"]
|
21
|
+
else:
|
22
|
+
raise ValueError("Please set the TAVILY_API_KEY environment variable.")
|
23
|
+
self.__tavily = TavilyClient(api_key=key)
|
24
|
+
|
25
|
+
def __embed_file(self, content: bytes, file_ext: str):
|
26
|
+
assert self.agent.knowledge_base is not None
|
27
|
+
with BytesIO(content) as f:
|
28
|
+
ext = file_ext if file_ext.startswith(".") else "." + file_ext
|
29
|
+
f.name = str(uuid.uuid4()) + ext
|
30
|
+
# self.agent.knowledge_base.add_document(f)
|
31
|
+
# file_name = f.name
|
32
|
+
raise NotImplementedError
|
33
|
+
return {
|
34
|
+
"file_name": file_name,
|
35
|
+
"hint": f"This is a .{file_ext} file and it is embeded in the knowledge base. Use _file_search to query the content.",
|
36
|
+
}
|
37
|
+
|
38
|
+
def __get(self, url: str):
|
39
|
+
res = requests.get(url)
|
40
|
+
res.raise_for_status()
|
41
|
+
content_type = res.headers.get("content-type")
|
42
|
+
# if content_type == "application/pdf":
|
43
|
+
# # Add this file to the knowledge base
|
44
|
+
# if self.agent.knowledge_base is not None:
|
45
|
+
# return self.__embed_file(res.content, "pdf")
|
46
|
+
# return {"content": "This is a PDF file. You don't know how to view it."}
|
47
|
+
md = markdownify(res.text)
|
48
|
+
return {"content": md}
|
49
|
+
|
50
|
+
@tool
|
51
|
+
def get_webpage_content(
|
52
|
+
self,
|
53
|
+
url: Annotated[str, "The URL of the web page to get the content of"],
|
54
|
+
):
|
55
|
+
"""
|
56
|
+
Access a web page by a URL, and fetch the content of this web page (in markdown format).
|
57
|
+
You can always use this tool to directly access web content or access external sites.
|
58
|
+
Use it at any time when you think you may need to access the internet.
|
59
|
+
"""
|
60
|
+
banner("BROWSE", dim=url)
|
61
|
+
|
62
|
+
result = self.__tavily.extract(
|
63
|
+
urls=url,
|
64
|
+
# extract_depth="advanced",
|
65
|
+
include_images=True,
|
66
|
+
)
|
67
|
+
failed_results = result.get("failed_results", [])
|
68
|
+
if len(failed_results) > 0:
|
69
|
+
try:
|
70
|
+
return self.__get(url)
|
71
|
+
except Exception as e:
|
72
|
+
pass
|
73
|
+
return result
|