hyperpocket 0.3.7__py3-none-any.whl → 0.4.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.
Files changed (45) hide show
  1. hyperpocket/auth/provider.py +1 -0
  2. hyperpocket/auth/weaviate/context.py +12 -0
  3. hyperpocket/auth/weaviate/token_context.py +11 -0
  4. hyperpocket/auth/weaviate/token_handler.py +68 -0
  5. hyperpocket/auth/weaviate/token_schema.py +7 -0
  6. hyperpocket/cli/eject.py +2 -7
  7. hyperpocket/cli/pull.py +2 -7
  8. hyperpocket/config/settings.py +2 -1
  9. hyperpocket/pocket_core.py +41 -68
  10. hyperpocket/pocket_main.py +37 -16
  11. hyperpocket/repository/__init__.py +3 -4
  12. hyperpocket/repository/repository.py +6 -41
  13. hyperpocket/repository/tool_reference.py +28 -0
  14. hyperpocket/server/auth/weaviate.py +27 -0
  15. hyperpocket/server/server.py +127 -61
  16. hyperpocket/session/in_memory.py +13 -3
  17. hyperpocket/tool/__init__.py +0 -3
  18. hyperpocket/tool/dock/__init__.py +3 -0
  19. hyperpocket/tool/dock/dock.py +34 -0
  20. hyperpocket/tool/function/__init__.py +1 -1
  21. hyperpocket/tool/function/tool.py +62 -32
  22. hyperpocket/tool/tool.py +1 -9
  23. hyperpocket/tool_like.py +2 -1
  24. hyperpocket/util/generate_slug.py +4 -0
  25. hyperpocket/util/json_schema_to_model.py +5 -1
  26. {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/METADATA +4 -1
  27. {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/RECORD +30 -36
  28. hyperpocket/cli/sync.py +0 -17
  29. hyperpocket/repository/lock.py +0 -240
  30. hyperpocket/repository/lockfile.py +0 -62
  31. hyperpocket/server/tool/__init__.py +0 -10
  32. hyperpocket/server/tool/dto/script.py +0 -33
  33. hyperpocket/server/tool/wasm.py +0 -46
  34. hyperpocket/tool/wasm/README.md +0 -166
  35. hyperpocket/tool/wasm/__init__.py +0 -3
  36. hyperpocket/tool/wasm/browser.py +0 -63
  37. hyperpocket/tool/wasm/invoker.py +0 -41
  38. hyperpocket/tool/wasm/script.py +0 -134
  39. hyperpocket/tool/wasm/templates/__init__.py +0 -35
  40. hyperpocket/tool/wasm/templates/node.py +0 -87
  41. hyperpocket/tool/wasm/templates/python.py +0 -93
  42. hyperpocket/tool/wasm/tool.py +0 -163
  43. /hyperpocket/{server/tool/dto → auth/weaviate}/__init__.py +0 -0
  44. {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/WHEEL +0 -0
  45. {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -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
- ```
@@ -1,3 +0,0 @@
1
- from hyperpocket.tool.wasm.tool import WasmTool
2
-
3
- __all__ = ["WasmTool"]
@@ -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()
@@ -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
@@ -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
- """