hyperpocket 0.3.6__py3-none-any.whl → 0.4.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- hyperpocket/auth/provider.py +2 -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/zinc/__init__.py +0 -0
- hyperpocket/auth/zinc/context.py +12 -0
- hyperpocket/auth/zinc/token_context.py +11 -0
- hyperpocket/auth/zinc/token_handler.py +64 -0
- hyperpocket/auth/zinc/token_schema.py +7 -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/auth/zinc.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.6.dist-info → hyperpocket-0.4.0.dist-info}/METADATA +4 -1
- {hyperpocket-0.3.6.dist-info → hyperpocket-0.4.0.dist-info}/RECORD +36 -36
- 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.6.dist-info → hyperpocket-0.4.0.dist-info}/WHEEL +0 -0
- {hyperpocket-0.3.6.dist-info → hyperpocket-0.4.0.dist-info}/entry_points.txt +0 -0
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
|
hyperpocket/tool/wasm/script.py
DELETED
@@ -1,134 +0,0 @@
|
|
1
|
-
import base64
|
2
|
-
import enum
|
3
|
-
import pathlib
|
4
|
-
from typing import Optional
|
5
|
-
|
6
|
-
import toml
|
7
|
-
from pydantic import BaseModel
|
8
|
-
|
9
|
-
from hyperpocket.config import pocket_logger
|
10
|
-
|
11
|
-
|
12
|
-
class ScriptRuntime(enum.Enum):
|
13
|
-
Node = "node"
|
14
|
-
Python = "python"
|
15
|
-
Wasm = "wasm"
|
16
|
-
|
17
|
-
|
18
|
-
_RuntimePackageFiles = {
|
19
|
-
ScriptRuntime.Node: ["dist/index.js"],
|
20
|
-
ScriptRuntime.Python: ["main.py", "requirements.txt"],
|
21
|
-
ScriptRuntime.Wasm: ["dist/index.wasm"],
|
22
|
-
}
|
23
|
-
|
24
|
-
|
25
|
-
class ScriptFileNodeContent(BaseModel):
|
26
|
-
contents: str
|
27
|
-
|
28
|
-
|
29
|
-
class ScriptFileNode(BaseModel):
|
30
|
-
directory: Optional[dict[str, "ScriptFileNode"]] = None
|
31
|
-
file: Optional[ScriptFileNodeContent] = None
|
32
|
-
|
33
|
-
@classmethod
|
34
|
-
def create_file_tree(cls, path: str, contents: str) -> dict[str, "ScriptFileNode"]:
|
35
|
-
path_split = path.split("/")
|
36
|
-
if len(path_split) == 1:
|
37
|
-
return {
|
38
|
-
path_split[0]: ScriptFileNode(
|
39
|
-
file=ScriptFileNodeContent(contents=contents)
|
40
|
-
)
|
41
|
-
}
|
42
|
-
node = cls.create_file_tree("/".join(path_split[1:]), contents)
|
43
|
-
return {path_split[0]: ScriptFileNode(directory=node)}
|
44
|
-
|
45
|
-
@staticmethod
|
46
|
-
def merge(
|
47
|
-
a: dict[str, "ScriptFileNode"], b: [str, "ScriptFileNode"]
|
48
|
-
) -> dict[str, "ScriptFileNode"]:
|
49
|
-
for k, v in b.items():
|
50
|
-
if k in a:
|
51
|
-
if a[k].directory and v.directory:
|
52
|
-
a[k].directory = ScriptFileNode.merge(a[k].directory, v.directory)
|
53
|
-
elif a[k].file and v.file:
|
54
|
-
a[k].file = v.file
|
55
|
-
else:
|
56
|
-
a[k] = v
|
57
|
-
return a
|
58
|
-
|
59
|
-
|
60
|
-
class Script(BaseModel):
|
61
|
-
id: str
|
62
|
-
tool_path: str
|
63
|
-
rendered_html: str
|
64
|
-
runtime: ScriptRuntime
|
65
|
-
|
66
|
-
def load_file_tree(self) -> dict[str, ScriptFileNode]:
|
67
|
-
relpaths = _RuntimePackageFiles[self.runtime]
|
68
|
-
file_tree = dict()
|
69
|
-
for p in relpaths:
|
70
|
-
filepath = pathlib.Path(self.tool_path) / p
|
71
|
-
with filepath.open("r") as f:
|
72
|
-
contents = f.read().encode("utf-8")
|
73
|
-
encoded_bytes = base64.b64encode(contents)
|
74
|
-
encoded_str = encoded_bytes.decode()
|
75
|
-
file_tree = ScriptFileNode.merge(
|
76
|
-
file_tree, ScriptFileNode.create_file_tree(p, encoded_str)
|
77
|
-
)
|
78
|
-
return file_tree
|
79
|
-
|
80
|
-
@property
|
81
|
-
def package_name(self) -> Optional[str]:
|
82
|
-
if self.runtime != ScriptRuntime.Python:
|
83
|
-
return
|
84
|
-
pyproject = toml.load(pathlib.Path(self.tool_path) / "pyproject.toml")
|
85
|
-
if "project" in pyproject:
|
86
|
-
name = pyproject["project"]["name"]
|
87
|
-
if "tool" in pyproject and "poetry" in pyproject["tool"]:
|
88
|
-
name = pyproject["tool"]["poetry"]["name"]
|
89
|
-
if not name:
|
90
|
-
raise ValueError("Could not find package name")
|
91
|
-
return name.replace("-", "_")
|
92
|
-
|
93
|
-
@property
|
94
|
-
def entrypoint(self) -> str:
|
95
|
-
pocket_logger.info(self.tool_path)
|
96
|
-
if self.runtime == ScriptRuntime.Node:
|
97
|
-
return "dist/index.js"
|
98
|
-
elif self.runtime == ScriptRuntime.Wasm:
|
99
|
-
return "dist/main.wasm"
|
100
|
-
pyproject = toml.load(pathlib.Path(self.tool_path) / "pyproject.toml")
|
101
|
-
version = None
|
102
|
-
if "project" in pyproject:
|
103
|
-
version = pyproject["project"]["version"]
|
104
|
-
elif "tool" in pyproject and "poetry" in pyproject["tool"]:
|
105
|
-
version = pyproject["tool"]["poetry"]["version"]
|
106
|
-
else:
|
107
|
-
raise ValueError("Could not find package version")
|
108
|
-
wheel_name = f"{self.package_name}-{version}-py3-none-any.whl"
|
109
|
-
wheel_path = pathlib.Path(self.tool_path) / "dist" / wheel_name
|
110
|
-
if not wheel_path.exists():
|
111
|
-
raise ValueError(f"Wheel file {wheel_path} does not exist")
|
112
|
-
return wheel_name
|
113
|
-
|
114
|
-
def dist_file_path(self, file_name: str) -> str:
|
115
|
-
return str(pathlib.Path(self.tool_path) / "dist" / file_name)
|
116
|
-
|
117
|
-
|
118
|
-
class _ScriptStore(object):
|
119
|
-
scripts: dict[str, Script] = {}
|
120
|
-
|
121
|
-
def __init__(self):
|
122
|
-
self.rendered_html = {}
|
123
|
-
|
124
|
-
def add_script(self, script: Script):
|
125
|
-
if script.id in self.scripts:
|
126
|
-
raise ValueError("Script id already exists")
|
127
|
-
self.scripts[script.id] = script
|
128
|
-
|
129
|
-
def get_script(self, script_id: str) -> Script:
|
130
|
-
# ValueError exception is intentional
|
131
|
-
return self.scripts[script_id]
|
132
|
-
|
133
|
-
|
134
|
-
ScriptStore = _ScriptStore()
|
@@ -1,35 +0,0 @@
|
|
1
|
-
import base64
|
2
|
-
import json
|
3
|
-
|
4
|
-
from jinja2 import DictLoader, Environment
|
5
|
-
|
6
|
-
from hyperpocket.tool.wasm.templates.node import node_template
|
7
|
-
from hyperpocket.tool.wasm.templates.python import python_template
|
8
|
-
|
9
|
-
TemplateEnvironments = Environment(
|
10
|
-
loader=DictLoader(
|
11
|
-
{
|
12
|
-
"python.html": python_template,
|
13
|
-
"node.html": node_template,
|
14
|
-
}
|
15
|
-
),
|
16
|
-
autoescape=False,
|
17
|
-
)
|
18
|
-
|
19
|
-
|
20
|
-
def render(
|
21
|
-
language: str, script_id: str, env: dict[str, str], body: str, **kwargs
|
22
|
-
) -> str:
|
23
|
-
env_json = json.dumps(env)
|
24
|
-
template = TemplateEnvironments.get_template(f"{language.lower()}.html")
|
25
|
-
body_bytes = body.encode("utf-8")
|
26
|
-
body_b64_bytes = base64.b64encode(body_bytes)
|
27
|
-
body_b64 = body_b64_bytes.decode("ascii")
|
28
|
-
return template.render(
|
29
|
-
**{
|
30
|
-
"SCRIPT_ID": script_id,
|
31
|
-
"ENV_JSON": env_json,
|
32
|
-
"BODY_JSON_B64": body_b64,
|
33
|
-
}
|
34
|
-
| kwargs
|
35
|
-
)
|
@@ -1,87 +0,0 @@
|
|
1
|
-
node_template = """
|
2
|
-
<!DOCTYPE html>
|
3
|
-
<html lang="en">
|
4
|
-
<head>
|
5
|
-
<meta charset="UTF-8">
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
|
-
<title>PyScript Offline</title>
|
8
|
-
</head>
|
9
|
-
<body>
|
10
|
-
<script type="module">
|
11
|
-
function loadConfig() {
|
12
|
-
globalThis.toolConfigs = {
|
13
|
-
envs: `{{ ENV_JSON }}`,
|
14
|
-
body: `{{ BODY_JSON_B64 }}`,
|
15
|
-
scriptID: `{{ SCRIPT_ID }}`
|
16
|
-
}
|
17
|
-
}
|
18
|
-
import { WebContainer } from 'https://esm.run/@webcontainer/api@1.5.1';
|
19
|
-
function decodeContent(content) {
|
20
|
-
return Uint8Array.from(atob(content), c => c.charCodeAt(0));
|
21
|
-
}
|
22
|
-
function decodeFileTree(filetree) {
|
23
|
-
const decoded = {};
|
24
|
-
for (const [key, value] of Object.entries(filetree)) {
|
25
|
-
if (value.file) {
|
26
|
-
decoded[key] = {
|
27
|
-
file: {
|
28
|
-
contents: decodeContent(value.file.contents)
|
29
|
-
}
|
30
|
-
}
|
31
|
-
} else if (value.directory) {
|
32
|
-
decoded[key] = {
|
33
|
-
directory: decodeFileTree(value.directory)
|
34
|
-
}
|
35
|
-
} else if (value.symlink) {
|
36
|
-
decoded[key] = {
|
37
|
-
symlink: value.symlink
|
38
|
-
}
|
39
|
-
}
|
40
|
-
}
|
41
|
-
return decoded;
|
42
|
-
}
|
43
|
-
async function main() {
|
44
|
-
loadConfig();
|
45
|
-
const b64FilesResp = await fetch(`/tools/wasm/scripts/${globalThis.toolConfigs.scriptID}/file_tree`);
|
46
|
-
const b64Files = await b64FilesResp.json();
|
47
|
-
const files = decodeFileTree(b64Files.tree);
|
48
|
-
const webcontainer = await WebContainer.boot();
|
49
|
-
|
50
|
-
await webcontainer.mount(files)
|
51
|
-
const envs = JSON.parse(globalThis.toolConfigs.envs)
|
52
|
-
envs['DEPLOYED'] = 'true'
|
53
|
-
const runProcess = await webcontainer.spawn('node', ['dist/index.js'], {
|
54
|
-
output: true,
|
55
|
-
env: envs,
|
56
|
-
});
|
57
|
-
const stdin = runProcess.input.getWriter();
|
58
|
-
const decodedBytes = atob(globalThis.toolConfigs.body);
|
59
|
-
await (async () => {
|
60
|
-
await stdin.ready
|
61
|
-
await stdin.write(decodedBytes);
|
62
|
-
})()
|
63
|
-
let stdout = '';
|
64
|
-
runProcess.output.pipeTo(
|
65
|
-
new WritableStream({
|
66
|
-
write(chunk) {
|
67
|
-
stdout += chunk;
|
68
|
-
}
|
69
|
-
})
|
70
|
-
)
|
71
|
-
await runProcess.exit;
|
72
|
-
if (stdout.startsWith(decodedBytes)) {
|
73
|
-
stdout = stdout.slice(decodedBytes);
|
74
|
-
}
|
75
|
-
await fetch(`/tools/wasm/scripts/${globalThis.toolConfigs.scriptID}/done`, {
|
76
|
-
method: 'POST',
|
77
|
-
headers: {
|
78
|
-
'Content-Type': 'application/json'
|
79
|
-
},
|
80
|
-
body: JSON.stringify({ stdout })
|
81
|
-
});
|
82
|
-
}
|
83
|
-
main();
|
84
|
-
</script>
|
85
|
-
</body>
|
86
|
-
</html>
|
87
|
-
"""
|
@@ -1,93 +0,0 @@
|
|
1
|
-
python_template = """
|
2
|
-
<!DOCTYPE html>
|
3
|
-
<html lang="en">
|
4
|
-
<head>
|
5
|
-
<meta charset="UTF-8">
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
|
-
<title>PyScript Offline</title>
|
8
|
-
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js"></script>
|
9
|
-
</head>
|
10
|
-
<body>
|
11
|
-
<script type="module">
|
12
|
-
function loadConfig() {
|
13
|
-
globalThis.toolConfigs = {
|
14
|
-
envs: `{{ ENV_JSON }}`,
|
15
|
-
body: `{{ BODY_JSON_B64 }}`,
|
16
|
-
scriptID: `{{ SCRIPT_ID }}`
|
17
|
-
}
|
18
|
-
}
|
19
|
-
async function _main() {
|
20
|
-
// load the script configs
|
21
|
-
loadConfig();
|
22
|
-
|
23
|
-
// get entrypoint wheel
|
24
|
-
const entrypointResp = await fetch(`/tools/wasm/scripts/${globalThis.toolConfigs.scriptID}/entrypoint`);
|
25
|
-
const { package_name: packageName, entrypoint } = await entrypointResp.json();
|
26
|
-
|
27
|
-
// initialize pyodide
|
28
|
-
const pyodide = await loadPyodide({
|
29
|
-
env: JSON.parse(globalThis.toolConfigs.envs),
|
30
|
-
});
|
31
|
-
await pyodide.loadPackage("micropip");
|
32
|
-
await pyodide.loadPackage("ssl");
|
33
|
-
const micropip = pyodide.pyimport("micropip");
|
34
|
-
await micropip.install(entrypoint);
|
35
|
-
await micropip.install("pyodide-http")
|
36
|
-
|
37
|
-
let emitted = false;
|
38
|
-
const decodedBytes = atob(globalThis.toolConfigs.body);
|
39
|
-
pyodide.setStdin({
|
40
|
-
stdin: () => {
|
41
|
-
if (emitted) {
|
42
|
-
return null;
|
43
|
-
}
|
44
|
-
emitted = true;
|
45
|
-
return decodedBytes;
|
46
|
-
},
|
47
|
-
autoEOF: true,
|
48
|
-
})
|
49
|
-
let stdout = "";
|
50
|
-
let stderr = "";
|
51
|
-
pyodide.setStdout({
|
52
|
-
batched: (x) => { stdout += x; },
|
53
|
-
})
|
54
|
-
pyodide.setStderr({
|
55
|
-
batched: (x) => { stderr += x; },
|
56
|
-
})
|
57
|
-
await pyodide.runPythonAsync(`
|
58
|
-
import pyodide_http
|
59
|
-
pyodide_http.patch_all()
|
60
|
-
|
61
|
-
import ${packageName}
|
62
|
-
${packageName}.main()
|
63
|
-
`);
|
64
|
-
console.log(stdout)
|
65
|
-
await fetch(`/tools/wasm/scripts/${globalThis.toolConfigs.scriptID}/done`, {
|
66
|
-
method: 'POST',
|
67
|
-
headers: {
|
68
|
-
'Content-Type': 'application/json'
|
69
|
-
},
|
70
|
-
body: JSON.stringify({ stdout, stderr })
|
71
|
-
});
|
72
|
-
}
|
73
|
-
|
74
|
-
async function main() {
|
75
|
-
try {
|
76
|
-
await _main();
|
77
|
-
} catch (e) {
|
78
|
-
console.error(e);
|
79
|
-
await fetch(`/tools/wasm/scripts/${globalThis.toolConfigs.scriptID}/done`, {
|
80
|
-
method: 'POST',
|
81
|
-
headers: {
|
82
|
-
'Content-Type': 'application/json'
|
83
|
-
},
|
84
|
-
body: JSON.stringify({ error: e.message })
|
85
|
-
});
|
86
|
-
}
|
87
|
-
}
|
88
|
-
|
89
|
-
main();
|
90
|
-
</script>
|
91
|
-
</body>
|
92
|
-
</html>
|
93
|
-
"""
|