hyperpocket 0.3.7__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.
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
- """