hyperpocket 0.3.7__py3-none-any.whl → 0.4.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- hyperpocket/auth/activeloop/token_handler.py +4 -0
- hyperpocket/auth/adobe/token_handler.py +4 -0
- hyperpocket/auth/affinity/token_handler.py +4 -0
- hyperpocket/auth/agentql/token_handler.py +4 -0
- hyperpocket/auth/ahrefs/token_handler.py +4 -0
- hyperpocket/auth/airtable/token_handler.py +4 -0
- hyperpocket/auth/alchemy/token_handler.py +4 -0
- hyperpocket/auth/altoviz/token_handler.py +4 -0
- hyperpocket/auth/bamboohr/token_handler.py +4 -0
- hyperpocket/auth/bitwarden/token_handler.py +4 -0
- hyperpocket/auth/brevo/README.md +1 -1
- hyperpocket/auth/brevo/token_handler.py +4 -0
- hyperpocket/auth/brex/token_handler.py +4 -0
- hyperpocket/auth/cal/token_handler.py +4 -0
- hyperpocket/auth/canvas/token_handler.py +4 -0
- hyperpocket/auth/clickup/token_handler.py +4 -0
- hyperpocket/auth/cloudflare/token_handler.py +4 -0
- hyperpocket/auth/dailybot/token_handler.py +4 -0
- hyperpocket/auth/datadog/token_handler.py +4 -0
- hyperpocket/auth/discordbot/README.md +0 -1
- hyperpocket/auth/discordbot/token_handler.py +4 -0
- hyperpocket/auth/elevenlabs/token_handler.py +4 -0
- hyperpocket/auth/exa/token_handler.py +4 -0
- hyperpocket/auth/facebook/oauth2_handler.py +4 -0
- hyperpocket/auth/finage/token_handler.py +4 -0
- hyperpocket/auth/happyrobot/token_handler.py +4 -0
- hyperpocket/auth/heygen/token_handler.py +4 -0
- hyperpocket/auth/klaviyo/token_handler.py +4 -0
- hyperpocket/auth/lever/token_handler.py +4 -0
- hyperpocket/auth/lever_sandbox/token_handler.py +4 -0
- hyperpocket/auth/listennotes/token_handler.py +4 -0
- hyperpocket/auth/mem0/token_handler.py +4 -0
- hyperpocket/auth/microsoft_clarity/token_handler.py +4 -0
- hyperpocket/auth/neon/token_handler.py +4 -0
- hyperpocket/auth/ngrok/token_handler.py +4 -0
- hyperpocket/auth/oncehub/token_handler.py +4 -0
- hyperpocket/auth/pagerduty/token_handler.py +4 -0
- hyperpocket/auth/pandadoc/token_handler.py +4 -0
- hyperpocket/auth/pipedrive/token_handler.py +4 -0
- hyperpocket/auth/posthog/token_handler.py +4 -0
- hyperpocket/auth/provider.py +1 -0
- hyperpocket/auth/ravenseotools/token_handler.py +4 -0
- hyperpocket/auth/semantic_scholar/token_handler.py +4 -0
- hyperpocket/auth/sendgrid/token_handler.py +4 -0
- hyperpocket/auth/stripe/token_handler.py +4 -0
- hyperpocket/auth/supabase/token_handler.py +4 -0
- hyperpocket/auth/tavily/token_handler.py +4 -0
- hyperpocket/auth/timekit/token_handler.py +4 -0
- hyperpocket/auth/trello/token_handler.py +4 -0
- hyperpocket/auth/wandb/token_handler.py +4 -0
- hyperpocket/auth/weaviate/context.py +12 -0
- hyperpocket/auth/weaviate/token_context.py +11 -0
- hyperpocket/auth/weaviate/token_handler.py +68 -0
- hyperpocket/auth/weaviate/token_schema.py +7 -0
- hyperpocket/auth/workiom/token_handler.py +4 -0
- hyperpocket/auth/zinc/token_handler.py +4 -0
- hyperpocket/cli/eject.py +2 -7
- hyperpocket/cli/pull.py +2 -7
- hyperpocket/config/settings.py +2 -1
- hyperpocket/pocket_core.py +41 -68
- hyperpocket/pocket_main.py +37 -16
- hyperpocket/repository/__init__.py +3 -4
- hyperpocket/repository/repository.py +6 -41
- hyperpocket/repository/tool_reference.py +28 -0
- hyperpocket/server/auth/weaviate.py +27 -0
- hyperpocket/server/server.py +127 -61
- hyperpocket/session/in_memory.py +13 -3
- hyperpocket/tool/__init__.py +0 -3
- hyperpocket/tool/dock/__init__.py +3 -0
- hyperpocket/tool/dock/dock.py +34 -0
- hyperpocket/tool/function/__init__.py +1 -1
- hyperpocket/tool/function/tool.py +62 -32
- hyperpocket/tool/tool.py +1 -9
- hyperpocket/tool_like.py +2 -1
- hyperpocket/util/generate_slug.py +4 -0
- hyperpocket/util/json_schema_to_model.py +5 -1
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.1.dist-info}/METADATA +4 -1
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.1.dist-info}/RECORD +81 -87
- hyperpocket/cli/sync.py +0 -17
- hyperpocket/repository/lock.py +0 -240
- hyperpocket/repository/lockfile.py +0 -62
- hyperpocket/server/tool/__init__.py +0 -10
- hyperpocket/server/tool/dto/script.py +0 -33
- hyperpocket/server/tool/wasm.py +0 -46
- hyperpocket/tool/wasm/README.md +0 -166
- hyperpocket/tool/wasm/__init__.py +0 -3
- hyperpocket/tool/wasm/browser.py +0 -63
- hyperpocket/tool/wasm/invoker.py +0 -41
- hyperpocket/tool/wasm/script.py +0 -134
- hyperpocket/tool/wasm/templates/__init__.py +0 -35
- hyperpocket/tool/wasm/templates/node.py +0 -87
- hyperpocket/tool/wasm/templates/python.py +0 -93
- hyperpocket/tool/wasm/tool.py +0 -163
- /hyperpocket/{server/tool/dto → auth/weaviate}/__init__.py +0 -0
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.1.dist-info}/WHEEL +0 -0
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.1.dist-info}/entry_points.txt +0 -0
hyperpocket/repository/lock.py
DELETED
@@ -1,240 +0,0 @@
|
|
1
|
-
import abc
|
2
|
-
import pathlib
|
3
|
-
import shutil
|
4
|
-
from typing import Optional, Tuple, ClassVar
|
5
|
-
|
6
|
-
import git
|
7
|
-
from pydantic import BaseModel, Field
|
8
|
-
from pydantic.fields import ModelPrivateAttr
|
9
|
-
|
10
|
-
from hyperpocket.config import pocket_logger, settings
|
11
|
-
|
12
|
-
|
13
|
-
class Lock(BaseModel, abc.ABC):
|
14
|
-
tool_source: str = None
|
15
|
-
|
16
|
-
@abc.abstractmethod
|
17
|
-
def __str__(self):
|
18
|
-
raise NotImplementedError
|
19
|
-
|
20
|
-
@abc.abstractmethod
|
21
|
-
def key(self) -> tuple[str, ...]:
|
22
|
-
raise NotImplementedError
|
23
|
-
|
24
|
-
@abc.abstractmethod
|
25
|
-
def sync(self, **kwargs):
|
26
|
-
raise NotImplementedError
|
27
|
-
|
28
|
-
@abc.abstractmethod
|
29
|
-
def toolpkg_path(self) -> pathlib.Path:
|
30
|
-
raise NotImplementedError
|
31
|
-
|
32
|
-
def eject_to_path(self, dest_path: pathlib.Path, src_sub_path: str = None):
|
33
|
-
## local locks are already tracked by git
|
34
|
-
raise NotImplementedError
|
35
|
-
|
36
|
-
|
37
|
-
class LocalLock(Lock):
|
38
|
-
tool_source: str = Field(default="local")
|
39
|
-
tool_path: str
|
40
|
-
|
41
|
-
def __init__(self, tool_path: str):
|
42
|
-
super().__init__(
|
43
|
-
tool_source="local", tool_path=str(pathlib.Path(tool_path).resolve())
|
44
|
-
)
|
45
|
-
|
46
|
-
def __str__(self):
|
47
|
-
return f"local\t{self.tool_path}"
|
48
|
-
|
49
|
-
def key(self):
|
50
|
-
return self.tool_source, self.tool_path.rstrip("/")
|
51
|
-
|
52
|
-
def sync(self, **kwargs):
|
53
|
-
pocket_logger.info(f"Syncing path: {self.tool_path} ...")
|
54
|
-
pkg_path = self.toolpkg_path()
|
55
|
-
if pkg_path.exists():
|
56
|
-
shutil.rmtree(pkg_path)
|
57
|
-
shutil.copytree(self.tool_path, pkg_path)
|
58
|
-
|
59
|
-
def toolpkg_path(self) -> pathlib.Path:
|
60
|
-
pocket_pkgs = settings.toolpkg_path
|
61
|
-
return pocket_pkgs / "local" / self.tool_path[1:]
|
62
|
-
|
63
|
-
|
64
|
-
class GitLock(Lock):
|
65
|
-
_remote_cache: ClassVar[dict[str, dict[str, str]]]
|
66
|
-
tool_source: str = "git"
|
67
|
-
repository_url: str
|
68
|
-
git_ref: str
|
69
|
-
ref_sha: Optional[str] = None
|
70
|
-
|
71
|
-
def __str__(self):
|
72
|
-
return f"git\t{self.repository_url}\t{self.git_ref}\t{self.ref_sha}"
|
73
|
-
|
74
|
-
def key(self):
|
75
|
-
return self.tool_source, self.repository_url.rstrip("/"), self.git_ref
|
76
|
-
|
77
|
-
def toolpkg_path(self) -> pathlib.Path:
|
78
|
-
if not self.ref_sha:
|
79
|
-
raise ValueError("ref_sha is not set")
|
80
|
-
cleansed_url = self.repository_url
|
81
|
-
if self.repository_url.startswith("http://"):
|
82
|
-
cleansed_url = self.repository_url[7:]
|
83
|
-
elif self.repository_url.startswith("https://"):
|
84
|
-
cleansed_url = self.repository_url[8:]
|
85
|
-
elif self.repository_url.startswith("git@"):
|
86
|
-
cleansed_url = self.repository_url[4:]
|
87
|
-
return settings.toolpkg_path / cleansed_url / self.ref_sha
|
88
|
-
|
89
|
-
def sync(self, force_update: bool = False, **kwargs):
|
90
|
-
"""
|
91
|
-
Synchronize the local git repository with the target remote branch.
|
92
|
-
|
93
|
-
1. Check if the SHA of the target ref in the remote repository matches the current local SHA.
|
94
|
-
2. If they do not match, fetch the target ref from the remote repository and do a hard reset
|
95
|
-
to align the local repository with the remote version.
|
96
|
-
"""
|
97
|
-
try:
|
98
|
-
pocket_logger.info(
|
99
|
-
f"Syncing git: {self.repository_url} @ ref: {self.git_ref} ..."
|
100
|
-
)
|
101
|
-
|
102
|
-
# get new sha from refs
|
103
|
-
new_sha = self._get_new_sha_if_exists_in_remote()
|
104
|
-
if new_sha is None:
|
105
|
-
raise ValueError(
|
106
|
-
f"Could not find ref {self.git_ref} in {self.repository_url}"
|
107
|
-
)
|
108
|
-
|
109
|
-
# check self.ref_sha should be updated
|
110
|
-
if self.ref_sha != new_sha:
|
111
|
-
if force_update or self.ref_sha is None:
|
112
|
-
self.ref_sha = new_sha
|
113
|
-
|
114
|
-
# make pkg_version_path dir if not exists
|
115
|
-
pkg_version_path = self.toolpkg_path()
|
116
|
-
if not pkg_version_path.exists():
|
117
|
-
pkg_version_path.mkdir(parents=True)
|
118
|
-
|
119
|
-
# init git repo in local and set origin url
|
120
|
-
repo = git.Repo.init(pkg_version_path)
|
121
|
-
try:
|
122
|
-
remote = repo.remote("origin")
|
123
|
-
remote.set_url(self.repository_url)
|
124
|
-
except ValueError:
|
125
|
-
remote = repo.create_remote("origin", self.repository_url)
|
126
|
-
|
127
|
-
# check current local commit include new_sha
|
128
|
-
# if not included, fetch and do hard reset
|
129
|
-
exist_sha = None
|
130
|
-
try:
|
131
|
-
exist_sha = repo.head.commit.hexsha
|
132
|
-
except ValueError:
|
133
|
-
pass
|
134
|
-
if exist_sha is None or exist_sha != self.ref_sha:
|
135
|
-
remote.fetch(depth=1, refspec=self.ref_sha)
|
136
|
-
repo.git.checkout(new_sha)
|
137
|
-
repo.git.reset("--hard", new_sha)
|
138
|
-
repo.git.clean("-fd")
|
139
|
-
except Exception as e:
|
140
|
-
pocket_logger.error(
|
141
|
-
f"failed to sync git: {self.repository_url} @ ref: {self.git_ref}. reason : {e}"
|
142
|
-
)
|
143
|
-
raise e
|
144
|
-
|
145
|
-
def _get_new_sha_if_exists_in_remote(self):
|
146
|
-
"""
|
147
|
-
get new sha in refs
|
148
|
-
First, check remote sha is matched to saved ref_sha
|
149
|
-
Second, check remote ref name is matched to saved ref name
|
150
|
-
Third, check local ref name is matched to saved ref name
|
151
|
-
And last, check tag ref name is matched to saved ref name
|
152
|
-
"""
|
153
|
-
refs = git.cmd.Git().ls_remote(self.repository_url)
|
154
|
-
|
155
|
-
new_sha = None
|
156
|
-
for r in refs.split("\n"):
|
157
|
-
sha, ref = r.split("\t")
|
158
|
-
if sha == self.ref_sha:
|
159
|
-
new_sha = sha
|
160
|
-
break
|
161
|
-
elif ref == self.git_ref:
|
162
|
-
new_sha = sha
|
163
|
-
break
|
164
|
-
elif ref == f"refs/heads/{self.git_ref}":
|
165
|
-
new_sha = sha
|
166
|
-
break
|
167
|
-
elif ref == f"refs/tags/{self.git_ref}":
|
168
|
-
new_sha = sha
|
169
|
-
break
|
170
|
-
return new_sha
|
171
|
-
|
172
|
-
@classmethod
|
173
|
-
def get_git_branches(cls, repo_url):
|
174
|
-
if not hasattr(cls, "_remote_cache"):
|
175
|
-
cls._remote_cache = {}
|
176
|
-
if cls._remote_cache.get(repo_url) is None:
|
177
|
-
ls_lists = git.cmd.Git().ls_remote(repo_url)
|
178
|
-
|
179
|
-
branches = {}
|
180
|
-
for line in ls_lists.split("\n"):
|
181
|
-
sha, ref = line.split("\t")
|
182
|
-
if ref.startswith("refs/heads/"):
|
183
|
-
branch_name = ref.replace("refs/heads/", "")
|
184
|
-
branches[branch_name] = sha
|
185
|
-
cls._remote_cache[repo_url] = branches
|
186
|
-
return cls._remote_cache[repo_url]
|
187
|
-
|
188
|
-
@classmethod
|
189
|
-
def parse_repo_url(cls, repo_url: str) -> Tuple[str, str, str]:
|
190
|
-
"""
|
191
|
-
Parses a GitHub repository URL with optional branch and path information.
|
192
|
-
|
193
|
-
Returns:
|
194
|
-
Tuple[str, str, str]: base_repo, branch_name, directory_path
|
195
|
-
"""
|
196
|
-
if not repo_url.startswith("https://github.com/"):
|
197
|
-
raise AttributeError("Only GitHub URLs are supported")
|
198
|
-
|
199
|
-
# Remove the base URL and split the path
|
200
|
-
repo_path = repo_url.removeprefix("https://github.com/")
|
201
|
-
repo_path_list = repo_path.split("/")
|
202
|
-
|
203
|
-
# Check if the URL contains 'tree' (indicating branch and sub-path information)
|
204
|
-
if "tree" not in repo_path_list:
|
205
|
-
# If no 'tree', return the full repository URL
|
206
|
-
return repo_url, "HEAD", ""
|
207
|
-
|
208
|
-
# Parse base repo URL and remaining path
|
209
|
-
tree_index = repo_path_list.index("tree")
|
210
|
-
base_repo = f"https://github.com/{'/'.join(repo_path_list[:tree_index])}"
|
211
|
-
sub_path = repo_path_list[tree_index + 1 :]
|
212
|
-
|
213
|
-
# Fetch branch information
|
214
|
-
branches = cls.get_git_branches(base_repo)
|
215
|
-
|
216
|
-
# Find branch and sub-directory path
|
217
|
-
for idx in range(1, len(sub_path) + 1):
|
218
|
-
branch_name = "/".join(sub_path[:idx])
|
219
|
-
if branch_name in branches:
|
220
|
-
directory_path = (
|
221
|
-
"/".join(sub_path[idx:]) if idx < len(sub_path) else None
|
222
|
-
)
|
223
|
-
return base_repo, branch_name, directory_path
|
224
|
-
|
225
|
-
# If no valid branch is found, raise an error
|
226
|
-
raise ValueError("Branch not found in repository")
|
227
|
-
|
228
|
-
def eject_to_path(self, dest_path: pathlib.Path, src_sub_path: str = None):
|
229
|
-
# clone the git repository to the target path
|
230
|
-
pocket_logger.info(
|
231
|
-
f"Ejecting git: {self.repository_url} @ ref: {self.git_ref} source in path: {src_sub_path} to {dest_path} ..."
|
232
|
-
)
|
233
|
-
if dest_path.exists():
|
234
|
-
shutil.rmtree(dest_path)
|
235
|
-
|
236
|
-
if src_sub_path:
|
237
|
-
src_path = self.toolpkg_path() / src_sub_path
|
238
|
-
else:
|
239
|
-
src_path = self.toolpkg_path()
|
240
|
-
shutil.copytree(src_path, dest_path)
|
@@ -1,62 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
from concurrent.futures.thread import ThreadPoolExecutor
|
3
|
-
|
4
|
-
from hyperpocket.repository.lock import GitLock, LocalLock, Lock
|
5
|
-
|
6
|
-
|
7
|
-
class Lockfile:
|
8
|
-
path: pathlib.Path = None
|
9
|
-
locks: dict[tuple, Lock] = None
|
10
|
-
referenced_locks: set[tuple] = None
|
11
|
-
|
12
|
-
def __init__(self, path: pathlib.Path):
|
13
|
-
self.path = path
|
14
|
-
self.locks = {}
|
15
|
-
self.referenced_locks = set()
|
16
|
-
if self.path.exists():
|
17
|
-
with open(self.path, "r") as f:
|
18
|
-
for line in f:
|
19
|
-
split = line.strip().split("\t")
|
20
|
-
source = split[0]
|
21
|
-
if source == "local":
|
22
|
-
lock = LocalLock(tool_path=split[1])
|
23
|
-
elif source == "git":
|
24
|
-
lock = GitLock(
|
25
|
-
repository_url=split[1],
|
26
|
-
git_ref=split[2],
|
27
|
-
ref_sha=split[3],
|
28
|
-
)
|
29
|
-
else:
|
30
|
-
raise ValueError(f"Unknown tool source: {source}")
|
31
|
-
self.locks[lock.key()] = lock
|
32
|
-
else:
|
33
|
-
self.path.touch()
|
34
|
-
|
35
|
-
def add_lock(self, lock: Lock):
|
36
|
-
if lock.key() not in self.locks:
|
37
|
-
self.locks[lock.key()] = lock
|
38
|
-
self.referenced_locks.add(lock.key())
|
39
|
-
|
40
|
-
def remove_lock(self, key: tuple[str, ...]):
|
41
|
-
self.locks.pop(key)
|
42
|
-
if key in self.referenced_locks:
|
43
|
-
self.referenced_locks.remove(key)
|
44
|
-
|
45
|
-
def get_lock(self, key: tuple[str, ...]):
|
46
|
-
return self.locks[key]
|
47
|
-
|
48
|
-
def sync(self, force_update: bool, referenced_only: bool = False):
|
49
|
-
if referenced_only:
|
50
|
-
locks = [self.get_lock(key) for key in self.referenced_locks]
|
51
|
-
else:
|
52
|
-
locks = list(self.locks.values())
|
53
|
-
with ThreadPoolExecutor(
|
54
|
-
max_workers=min(len(locks) + 1, 100), thread_name_prefix="repository_loader"
|
55
|
-
) as executor:
|
56
|
-
executor.map(lambda lock: lock.sync(force_update=force_update), locks)
|
57
|
-
self.write()
|
58
|
-
|
59
|
-
def write(self):
|
60
|
-
with open(self.path, "w") as f:
|
61
|
-
for lock in self.locks.values():
|
62
|
-
f.write(str(lock) + "\n")
|
@@ -1,33 +0,0 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
|
3
|
-
from pydantic import BaseModel, Field
|
4
|
-
|
5
|
-
from hyperpocket.tool.wasm.script import ScriptFileNode
|
6
|
-
|
7
|
-
|
8
|
-
class Script(BaseModel):
|
9
|
-
id: str = Field(alias="id")
|
10
|
-
tool_id: str = Field(alias="tool_id")
|
11
|
-
|
12
|
-
|
13
|
-
class ScriptResult(BaseModel):
|
14
|
-
stdout: Optional[str] = Field(alias="stdout", default=None)
|
15
|
-
stderr: Optional[str] = Field(alias="stderr", default=None)
|
16
|
-
error: Optional[str] = Field(alias="error", default=None)
|
17
|
-
|
18
|
-
|
19
|
-
class ScriptFileTree(BaseModel):
|
20
|
-
tree: dict[str, ScriptFileNode] = Field(alias="tree")
|
21
|
-
|
22
|
-
|
23
|
-
class ScriptEntrypoint(BaseModel):
|
24
|
-
package_name: Optional[str] = Field(alias="package_name")
|
25
|
-
entrypoint: str = Field(alias="entrypoint")
|
26
|
-
|
27
|
-
|
28
|
-
class ScriptEncodedFile(BaseModel):
|
29
|
-
encoded_file: str = Field(alias="encoded_file")
|
30
|
-
|
31
|
-
|
32
|
-
class ScriptFileRequest(BaseModel):
|
33
|
-
path: str = Field(alias="path")
|
hyperpocket/server/tool/wasm.py
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
from fastapi import APIRouter
|
2
|
-
from fastapi.responses import FileResponse, HTMLResponse
|
3
|
-
|
4
|
-
from hyperpocket.futures import FutureStore
|
5
|
-
from hyperpocket.server.tool.dto import script as scriptdto
|
6
|
-
from hyperpocket.tool.wasm.script import ScriptStore
|
7
|
-
|
8
|
-
wasm_tool_router = APIRouter(prefix="/wasm")
|
9
|
-
|
10
|
-
|
11
|
-
@wasm_tool_router.get("/scripts/{script_id}/browse", response_class=HTMLResponse)
|
12
|
-
async def browse_script_page(script_id: str):
|
13
|
-
html = ScriptStore.get_script(script_id).rendered_html
|
14
|
-
return HTMLResponse(content=html)
|
15
|
-
|
16
|
-
|
17
|
-
@wasm_tool_router.post("/scripts/{script_id}/done")
|
18
|
-
async def done_script_page(
|
19
|
-
script_id: str, req: scriptdto.ScriptResult
|
20
|
-
) -> scriptdto.ScriptResult:
|
21
|
-
FutureStore.resolve_future(
|
22
|
-
script_id, {"stdout": req.stdout, "stderr": req.stderr, "error": req.error}
|
23
|
-
)
|
24
|
-
return req
|
25
|
-
|
26
|
-
|
27
|
-
@wasm_tool_router.get("/scripts/{script_id}/file_tree")
|
28
|
-
async def get_file_tree(script_id: str) -> scriptdto.ScriptFileTree:
|
29
|
-
script = ScriptStore.get_script(script_id)
|
30
|
-
return scriptdto.ScriptFileTree(tree=script.load_file_tree())
|
31
|
-
|
32
|
-
|
33
|
-
@wasm_tool_router.get("/scripts/{script_id}/entrypoint")
|
34
|
-
async def get_entrypoint(script_id: str) -> scriptdto.ScriptEntrypoint:
|
35
|
-
script = ScriptStore.get_script(script_id)
|
36
|
-
package_name = script.package_name
|
37
|
-
entrypoint = f"/tools/wasm/scripts/{script_id}/file/{script.entrypoint}"
|
38
|
-
return scriptdto.ScriptEntrypoint(package_name=package_name, entrypoint=entrypoint)
|
39
|
-
|
40
|
-
|
41
|
-
@wasm_tool_router.get(
|
42
|
-
"/scripts/{script_id}/file/{file_name}", response_class=FileResponse
|
43
|
-
)
|
44
|
-
async def get_dist_file(script_id: str, file_name: str):
|
45
|
-
script = ScriptStore.get_script(script_id)
|
46
|
-
return FileResponse(script.dist_file_path(file_name))
|
hyperpocket/tool/wasm/README.md
DELETED
@@ -1,166 +0,0 @@
|
|
1
|
-
# Wasm(WebAssembly)
|
2
|
-
|
3
|
-
---
|
4
|
-
|
5
|
-
## How To Use
|
6
|
-
|
7
|
-
### plain url
|
8
|
-
|
9
|
-
load the tool by providing a GitHub URL
|
10
|
-
|
11
|
-
```python
|
12
|
-
from hyperpocket import Pocket
|
13
|
-
|
14
|
-
pocket = Pocket(tools=[
|
15
|
-
'https://github.com/your-organization/your-repository/tree/main',
|
16
|
-
])
|
17
|
-
```
|
18
|
-
|
19
|
-
- in this case, read tool code from `main` branch by default
|
20
|
-
|
21
|
-
### from_git
|
22
|
-
|
23
|
-
- using `from_git`, you can specify branch and tool code path
|
24
|
-
|
25
|
-
```python
|
26
|
-
from hyperpocket import Pocket
|
27
|
-
from hyperpocket.tool import from_git
|
28
|
-
|
29
|
-
pocket = Pocket(tools=[
|
30
|
-
from_git("https://github.com/your-organization/your-repository", "branch-name", "your/tool/code/path"),
|
31
|
-
])
|
32
|
-
```
|
33
|
-
|
34
|
-
### from_local
|
35
|
-
|
36
|
-
- using `from_local`, you can load a tool by specifying a local path
|
37
|
-
|
38
|
-
```python
|
39
|
-
from hyperpocket import Pocket
|
40
|
-
from hyperpocket.tool import from_local
|
41
|
-
|
42
|
-
pocket = Pocket(tools=[
|
43
|
-
from_local("your/local/tool/code/path")
|
44
|
-
])
|
45
|
-
```
|
46
|
-
|
47
|
-
## WasmToolRequest
|
48
|
-
|
49
|
-
The class to initiate WasmTool
|
50
|
-
|
51
|
-
WasmToolRequest offer those build method:
|
52
|
-
|
53
|
-
- `from_local`
|
54
|
-
- `from_git`
|
55
|
-
|
56
|
-
Each `WasmToolRequest` has it's own `Lock` object
|
57
|
-
|
58
|
-
Those `Lock` object is managed by `Lockfile` in the `Pocket` class
|
59
|
-
|
60
|
-
```mermaid
|
61
|
-
classDiagram
|
62
|
-
class WasmToolRequest {
|
63
|
-
+Lock lock
|
64
|
-
+ from_git()
|
65
|
-
+ from_local()
|
66
|
-
}
|
67
|
-
|
68
|
-
class Lock{
|
69
|
-
+key()
|
70
|
-
+sync()
|
71
|
-
}
|
72
|
-
|
73
|
-
class GitLock{
|
74
|
-
+key()
|
75
|
-
+sync()
|
76
|
-
}
|
77
|
-
|
78
|
-
class LocalLock{
|
79
|
-
+key()
|
80
|
-
+sync()
|
81
|
-
}
|
82
|
-
|
83
|
-
class Lockfile {
|
84
|
-
+dict[tuple,Lock] locks
|
85
|
-
|
86
|
-
+ add_lock()
|
87
|
-
+ get_lock()
|
88
|
-
+ sync()
|
89
|
-
+ write()
|
90
|
-
}
|
91
|
-
|
92
|
-
ToolRequest <|.. WasmToolRequest : Implementation
|
93
|
-
Lock o-- WasmToolRequest : 1..1
|
94
|
-
Lock <|.. GitLock : Implementation
|
95
|
-
Lock <|.. LocalLock : Implementation
|
96
|
-
Lock o-- Lockfile : n..1
|
97
|
-
Lockfile o-- Pocket : 1..1
|
98
|
-
```
|
99
|
-
|
100
|
-
### Inject tool variables
|
101
|
-
|
102
|
-
If the user specifies [tool_vars] in the `config.toml` of the tool's repository, which is allowed to be injected dynamically when the user develops an agent, it can be injected through the following steps.
|
103
|
-
|
104
|
-
```toml
|
105
|
-
# config.toml of a tool
|
106
|
-
|
107
|
-
[tool_vars]
|
108
|
-
config1 = "config1"
|
109
|
-
config2 = "config2"
|
110
|
-
```
|
111
|
-
|
112
|
-
1. Injecting tool_vars when importing tool in code.
|
113
|
-
|
114
|
-
```python
|
115
|
-
from_git('https://github.com/your-organization/your-repository/tree/main',tool_vars = {
|
116
|
-
"config1": "modified_config1"
|
117
|
-
})
|
118
|
-
```
|
119
|
-
|
120
|
-
2. Injecting tool_vars by settings.toml
|
121
|
-
Hyperpocket checks the `settings.toml` from the agent code directory.
|
122
|
-
|
123
|
-
## WasmTool
|
124
|
-
|
125
|
-
```python
|
126
|
-
class WasmTool(Tool):
|
127
|
-
_invoker: WasmInvoker = None
|
128
|
-
pkg_lock: Lock = None
|
129
|
-
rel_path: str
|
130
|
-
runtime: ScriptRuntime = None
|
131
|
-
json_schema: Optional[dict] = None
|
132
|
-
readme: Optional[str] = None
|
133
|
-
```
|
134
|
-
|
135
|
-
- `_invoker`: A class for executing WASM.
|
136
|
-
- `pkg_lock`: The lock class for the tool.
|
137
|
-
- Used for determining the package path where the current WASM code is stored.
|
138
|
-
- `rel_path`: The relative path to the location of the WASM code within the package.
|
139
|
-
- `runtime`: The runtime language of the WASM code.
|
140
|
-
- `json_schema`: The JSON schema for the WASM tool.
|
141
|
-
- Information read from schema.json.
|
142
|
-
- `readme`: The README information.
|
143
|
-
|
144
|
-
## WasmInvoker
|
145
|
-
|
146
|
-
A class for running actual WASM
|
147
|
-
|
148
|
-
1. Combines runtime information, authentication details, and body content to render HTML.
|
149
|
-
2. Stores the rendered HTML in memory.
|
150
|
-
3. Launches a browser to execute the WASM.
|
151
|
-
4. The browser sends a request to the server to retrieve the rendered HTML.
|
152
|
-
5. Executes code specific to each runtime within the HTML and returns the result.
|
153
|
-
|
154
|
-
```mermaid
|
155
|
-
sequenceDiagram
|
156
|
-
participant WasmTool as WasmTool (Includes Server)
|
157
|
-
participant Browser as Browser (Executes WASM Runtime)
|
158
|
-
|
159
|
-
|
160
|
-
WasmTool->>WasmTool: Render HTML and Store in Memory
|
161
|
-
WasmTool->>Browser: Launch Browser
|
162
|
-
Browser->>WasmTool: Request Rendered HTML
|
163
|
-
WasmTool->>Browser: Provide Rendered HTML
|
164
|
-
Browser->>Browser: Execute Injected WASM Runtime Script
|
165
|
-
Browser->>WasmTool: Return Execution Result
|
166
|
-
```
|
hyperpocket/tool/wasm/browser.py
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
|
3
|
-
from playwright.async_api import (
|
4
|
-
BrowserContext,
|
5
|
-
Page,
|
6
|
-
Playwright,
|
7
|
-
Route,
|
8
|
-
async_playwright,
|
9
|
-
)
|
10
|
-
|
11
|
-
|
12
|
-
class InvokerBrowser(object):
|
13
|
-
_instance: "InvokerBrowser" = None
|
14
|
-
_lock = asyncio.Lock()
|
15
|
-
playwright: Playwright
|
16
|
-
browser_context: BrowserContext
|
17
|
-
|
18
|
-
def __init__(self):
|
19
|
-
raise RuntimeError("Use InvokerBrowser.get_instance() instead")
|
20
|
-
|
21
|
-
async def _async_init(self):
|
22
|
-
self.playwright = await async_playwright().start()
|
23
|
-
self.browser_context = await self.playwright.chromium.launch_persistent_context(
|
24
|
-
headless=True,
|
25
|
-
args=[
|
26
|
-
"--disable-web-security=True",
|
27
|
-
],
|
28
|
-
user_data_dir="/tmp/chrome_dev_user",
|
29
|
-
)
|
30
|
-
|
31
|
-
@classmethod
|
32
|
-
async def get_instance(cls):
|
33
|
-
if not cls._instance:
|
34
|
-
async with cls._lock:
|
35
|
-
if cls._instance is None:
|
36
|
-
instance = cls.__new__(cls)
|
37
|
-
await instance._async_init()
|
38
|
-
cls._instance = instance
|
39
|
-
return cls._instance
|
40
|
-
|
41
|
-
async def new_page(self) -> Page:
|
42
|
-
page = await self.browser_context.new_page()
|
43
|
-
|
44
|
-
async def _hijack_route(route: Route):
|
45
|
-
response = await route.fetch()
|
46
|
-
body = await response.body()
|
47
|
-
await route.fulfill(
|
48
|
-
response=response,
|
49
|
-
body=body,
|
50
|
-
headers={
|
51
|
-
**response.headers,
|
52
|
-
"Cross-Origin-Opener-Policy": "same-origin",
|
53
|
-
"Cross-Origin-Embedder-Policy": "require-corp",
|
54
|
-
"Cross-Origin-Resource-Policy": "cross-origin",
|
55
|
-
},
|
56
|
-
)
|
57
|
-
|
58
|
-
await page.route("**/*", _hijack_route)
|
59
|
-
return page
|
60
|
-
|
61
|
-
async def teardown(self):
|
62
|
-
await self.browser_context.close()
|
63
|
-
await self.playwright.stop()
|
hyperpocket/tool/wasm/invoker.py
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import json
|
3
|
-
import uuid
|
4
|
-
from typing import Any
|
5
|
-
from urllib.parse import urljoin
|
6
|
-
|
7
|
-
from hyperpocket.config import config
|
8
|
-
from hyperpocket.futures import FutureStore
|
9
|
-
from hyperpocket.tool.wasm.browser import InvokerBrowser
|
10
|
-
from hyperpocket.tool.wasm.script import Script, ScriptRuntime, ScriptStore
|
11
|
-
from hyperpocket.tool.wasm.templates import render
|
12
|
-
|
13
|
-
|
14
|
-
class WasmInvoker(object):
|
15
|
-
def invoke(
|
16
|
-
self, tool_path: str, runtime: ScriptRuntime, body: Any, envs: dict, **kwargs
|
17
|
-
) -> str:
|
18
|
-
loop = asyncio.get_running_loop()
|
19
|
-
return loop.run_until_complete(
|
20
|
-
self.ainvoke(tool_path, runtime, body, envs, **kwargs)
|
21
|
-
)
|
22
|
-
|
23
|
-
async def ainvoke(
|
24
|
-
self, tool_path: str, runtime: ScriptRuntime, body: Any, envs: dict, **kwargs
|
25
|
-
) -> str:
|
26
|
-
uid = str(uuid.uuid4())
|
27
|
-
html = render(runtime.value, uid, envs, json.dumps(body))
|
28
|
-
script = Script(
|
29
|
-
id=uid, tool_path=tool_path, rendered_html=html, runtime=runtime
|
30
|
-
)
|
31
|
-
ScriptStore.add_script(script=script)
|
32
|
-
future_data = FutureStore.create_future(uid=uid)
|
33
|
-
browser = await InvokerBrowser.get_instance()
|
34
|
-
page = await browser.new_page()
|
35
|
-
url = urljoin(
|
36
|
-
config().internal_base_url + "/", f"tools/wasm/scripts/{uid}/browse"
|
37
|
-
)
|
38
|
-
await page.goto(url)
|
39
|
-
stdout = await future_data.future
|
40
|
-
await page.close()
|
41
|
-
return stdout
|