hyperpocket 0.0.3__py3-none-any.whl → 0.1.8__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- hyperpocket/auth/README.md +3 -3
- hyperpocket/auth/__init__.py +0 -8
- hyperpocket/auth/gumloop/context.py +13 -0
- hyperpocket/auth/gumloop/token_context.py +15 -0
- hyperpocket/auth/gumloop/token_handler.py +66 -0
- hyperpocket/auth/gumloop/token_schema.py +8 -0
- hyperpocket/auth/linear/token_context.py +1 -1
- hyperpocket/auth/notion/README.md +28 -0
- hyperpocket/auth/notion/context.py +15 -0
- hyperpocket/auth/notion/token_context.py +14 -0
- hyperpocket/auth/notion/token_handler.py +65 -0
- hyperpocket/auth/notion/token_schema.py +10 -0
- hyperpocket/auth/provider.py +8 -5
- hyperpocket/auth/reddit/context.py +15 -0
- hyperpocket/auth/reddit/oauth2_context.py +32 -0
- hyperpocket/auth/reddit/oauth2_handler.py +151 -0
- hyperpocket/auth/reddit/oauth2_schema.py +18 -0
- hyperpocket/auth/slack/token_context.py +1 -1
- hyperpocket/builtin.py +63 -0
- hyperpocket/cli/__main__.py +12 -0
- hyperpocket/cli/auth.py +83 -0
- hyperpocket/cli/codegen/auth/__init__.py +13 -0
- hyperpocket/cli/codegen/auth/auth_context_template.py +16 -0
- hyperpocket/cli/codegen/auth/auth_token_context_template.py +16 -0
- hyperpocket/cli/codegen/auth/auth_token_handler_template.py +69 -0
- hyperpocket/cli/codegen/auth/auth_token_schema_template.py +12 -0
- hyperpocket/cli/codegen/auth/server_auth_template.py +18 -0
- hyperpocket/cli/eject.py +19 -0
- hyperpocket/cli/sync.py +5 -5
- hyperpocket/config/settings.py +2 -4
- hyperpocket/futures/futurestore.py +0 -1
- hyperpocket/pocket_auth.py +25 -5
- hyperpocket/pocket_core.py +262 -0
- hyperpocket/pocket_main.py +124 -173
- hyperpocket/prompts.py +6 -8
- hyperpocket/repository/__init__.py +2 -2
- hyperpocket/repository/lock.py +19 -0
- hyperpocket/repository/lockfile.py +19 -13
- hyperpocket/repository/repository.py +26 -1
- hyperpocket/server/auth/__init__.py +0 -6
- hyperpocket/server/auth/gumloop.py +16 -0
- hyperpocket/server/auth/notion.py +19 -0
- hyperpocket/server/auth/reddit.py +16 -0
- hyperpocket/server/server.py +52 -16
- hyperpocket/server/tool/dto/script.py +15 -2
- hyperpocket/server/tool/wasm.py +20 -8
- hyperpocket/session/README.md +2 -2
- hyperpocket/session/in_memory.py +18 -5
- hyperpocket/session/interface.py +14 -0
- hyperpocket/session/redis.py +29 -5
- hyperpocket/tool/README.md +16 -12
- hyperpocket/tool/__init__.py +4 -3
- hyperpocket/tool/function/README.md +39 -10
- hyperpocket/tool/function/__init__.py +2 -0
- hyperpocket/tool/function/annotation.py +2 -1
- hyperpocket/tool/function/tool.py +98 -13
- hyperpocket/tool/tests/test_function_tool.py +55 -0
- hyperpocket/tool/tests/test_wasm_tool.py +73 -0
- hyperpocket/tool/tool.py +65 -2
- hyperpocket/tool/wasm/README.md +27 -5
- hyperpocket/tool/wasm/script.py +40 -1
- hyperpocket/tool/wasm/templates/python.py +32 -14
- hyperpocket/tool/wasm/tool.py +21 -18
- hyperpocket/tool_like.py +5 -0
- hyperpocket/util/__init__.py +1 -1
- hyperpocket/util/extract_func_param_desc_from_docstring.py +4 -4
- hyperpocket/util/function_to_model.py +5 -2
- hyperpocket/util/json_schema_to_model.py +45 -26
- {hyperpocket-0.0.3.dist-info → hyperpocket-0.1.8.dist-info}/METADATA +101 -72
- hyperpocket-0.1.8.dist-info/RECORD +139 -0
- {hyperpocket-0.0.3.dist-info → hyperpocket-0.1.8.dist-info}/WHEEL +1 -1
- hyperpocket-0.1.8.dist-info/entry_points.txt +2 -0
- hyperpocket/auth/README.KR.md +0 -309
- hyperpocket/auth/slack/tests/test_oauth2_handler.py +0 -32
- hyperpocket/auth/slack/tests/test_token_handler.py +0 -23
- hyperpocket/auth/tests/test_google_oauth2_handler.py +0 -147
- hyperpocket/auth/tests/test_slack_oauth2_handler.py +0 -147
- hyperpocket/auth/tests/test_slack_token_handler.py +0 -66
- hyperpocket/external/__init__.py +0 -7
- hyperpocket/external/github_client.py +0 -19
- hyperpocket/session/README.KR.md +0 -62
- hyperpocket/session/tests/test_in_memory.py +0 -145
- hyperpocket/session/tests/test_redis.py +0 -151
- hyperpocket/tests/test_pocket.py +0 -116
- hyperpocket/tests/test_pocket_auth.py +0 -982
- hyperpocket/tool/README.KR.md +0 -68
- hyperpocket/tool/builtins/__init__.py +0 -0
- hyperpocket/tool/builtins/example/__init__.py +0 -0
- hyperpocket/tool/builtins/example/add_tool.py +0 -18
- hyperpocket/tool/function/README.KR.md +0 -159
- hyperpocket/tool/wasm/README.KR.md +0 -144
- hyperpocket-0.0.3.dist-info/RECORD +0 -130
- hyperpocket-0.0.3.dist-info/entry_points.txt +0 -3
- /hyperpocket/auth/{slack/tests → gumloop}/__init__.py +0 -0
- /hyperpocket/auth/{tests → notion}/__init__.py +0 -0
- /hyperpocket/{session/tests → auth/reddit}/__init__.py +0 -0
- /hyperpocket/{tests → cli/codegen}/__init__.py +0 -0
@@ -1,7 +1,11 @@
|
|
1
|
+
import asyncio
|
1
2
|
import copy
|
2
3
|
import inspect
|
3
|
-
|
4
|
+
import pathlib
|
5
|
+
from typing import Any, Coroutine
|
6
|
+
from typing import Callable, Optional
|
4
7
|
|
8
|
+
import toml
|
5
9
|
from pydantic import BaseModel
|
6
10
|
|
7
11
|
from hyperpocket.tool.tool import Tool, ToolAuth
|
@@ -13,18 +17,31 @@ class FunctionTool(Tool):
|
|
13
17
|
"""
|
14
18
|
FunctionTool is Tool executing local python method.
|
15
19
|
"""
|
16
|
-
func: Callable
|
17
|
-
afunc: Optional[Callable[...,
|
20
|
+
func: Optional[Callable[..., str]]
|
21
|
+
afunc: Optional[Callable[..., Coroutine[Any, Any, str]]]
|
18
22
|
|
19
23
|
def invoke(self, **kwargs) -> str:
|
20
24
|
binding_args = self._get_binding_args(kwargs)
|
21
|
-
|
25
|
+
if self.func is None:
|
26
|
+
if self.afunc is None:
|
27
|
+
raise ValueError("Both func and afunc are None")
|
28
|
+
try:
|
29
|
+
return str(asyncio.run(self.afunc(**binding_args)))
|
30
|
+
except Exception as e:
|
31
|
+
return "There was an error while executing the tool: " + str(e)
|
32
|
+
try:
|
33
|
+
return str(self.func(**binding_args))
|
34
|
+
except Exception as e:
|
35
|
+
return "There was an error while executing the tool: " + str(e)
|
22
36
|
|
23
37
|
async def ainvoke(self, **kwargs) -> str:
|
24
|
-
binding_args = self._get_binding_args(kwargs)
|
25
38
|
if self.afunc is None:
|
26
|
-
return str(self.
|
27
|
-
|
39
|
+
return str(self.invoke(**kwargs))
|
40
|
+
try:
|
41
|
+
binding_args = self._get_binding_args(kwargs)
|
42
|
+
return str(await self.afunc(**binding_args))
|
43
|
+
except Exception as e:
|
44
|
+
return "There was an error while executing the tool: " + str(e)
|
28
45
|
|
29
46
|
def _get_binding_args(self, kwargs):
|
30
47
|
_kwargs = copy.deepcopy(kwargs)
|
@@ -47,7 +64,7 @@ class FunctionTool(Tool):
|
|
47
64
|
if param.kind == param.VAR_KEYWORD:
|
48
65
|
# var keyword args should be passed by plain dict
|
49
66
|
binding_args |= args[param_name]
|
50
|
-
binding_args |= _kwargs.get("envs", {})
|
67
|
+
binding_args |= _kwargs.get("envs", {}) | self.tool_vars
|
51
68
|
|
52
69
|
if "envs" in _kwargs:
|
53
70
|
_kwargs.pop("envs")
|
@@ -69,11 +86,26 @@ class FunctionTool(Tool):
|
|
69
86
|
|
70
87
|
@classmethod
|
71
88
|
def from_func(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
89
|
+
cls,
|
90
|
+
func: Callable | 'FunctionTool',
|
91
|
+
afunc: Callable[..., Coroutine[Any, Any, str]] | 'FunctionTool' = None,
|
92
|
+
auth: Optional[ToolAuth] = None,
|
93
|
+
tool_vars: dict[str, str] = None,
|
76
94
|
) -> "FunctionTool":
|
95
|
+
if tool_vars is None:
|
96
|
+
tool_vars = dict()
|
97
|
+
|
98
|
+
if isinstance(func, FunctionTool):
|
99
|
+
if tool_vars is not None:
|
100
|
+
func.override_tool_variables(tool_vars)
|
101
|
+
return func
|
102
|
+
elif isinstance(afunc, FunctionTool):
|
103
|
+
if tool_vars is not None:
|
104
|
+
afunc.override_tool_variables(tool_vars)
|
105
|
+
return afunc
|
106
|
+
elif not callable(func) and not callable(afunc):
|
107
|
+
raise ValueError("FunctionTool can only be created from a callable")
|
108
|
+
|
77
109
|
model = function_to_model(func)
|
78
110
|
argument_json_schema = flatten_json_schema(model.model_json_schema())
|
79
111
|
|
@@ -83,5 +115,58 @@ class FunctionTool(Tool):
|
|
83
115
|
name=func.__name__,
|
84
116
|
description=model.__doc__,
|
85
117
|
argument_json_schema=argument_json_schema,
|
86
|
-
auth=auth
|
118
|
+
auth=auth,
|
119
|
+
default_tool_vars=tool_vars
|
87
120
|
)
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def from_dock(
|
124
|
+
cls,
|
125
|
+
dock: list[Callable[..., str]],
|
126
|
+
) -> list["FunctionTool"]:
|
127
|
+
tools = []
|
128
|
+
for func in dock:
|
129
|
+
model = function_to_model(func)
|
130
|
+
argument_json_schema = flatten_json_schema(model.model_json_schema())
|
131
|
+
if not callable(func):
|
132
|
+
raise ValueError(f"Dock element should be a list of functions, but found {func}")
|
133
|
+
is_coroutine = inspect.iscoroutinefunction(func)
|
134
|
+
auth = None
|
135
|
+
if func.__dict__.get("__auth__") is not None:
|
136
|
+
auth = ToolAuth(**func.__dict__["__auth__"])
|
137
|
+
if is_coroutine:
|
138
|
+
tools.append(cls(
|
139
|
+
func=None,
|
140
|
+
afunc=func,
|
141
|
+
name=func.__name__,
|
142
|
+
description=func.__doc__,
|
143
|
+
argument_json_schema=argument_json_schema,
|
144
|
+
auth=auth,
|
145
|
+
))
|
146
|
+
else:
|
147
|
+
tools.append(cls(
|
148
|
+
func=func,
|
149
|
+
afunc=None,
|
150
|
+
name=func.__name__,
|
151
|
+
description=func.__doc__,
|
152
|
+
argument_json_schema=argument_json_schema,
|
153
|
+
auth=auth,
|
154
|
+
))
|
155
|
+
return tools
|
156
|
+
|
157
|
+
@classmethod
|
158
|
+
def _get_tool_vars_from_config(cls, func: Callable) -> dict:
|
159
|
+
print(func.__name__)
|
160
|
+
tool_path = inspect.getfile(func)
|
161
|
+
print(tool_path)
|
162
|
+
tool_parent = "/".join(tool_path.split("/")[:-1])
|
163
|
+
tool_config_path = pathlib.Path(tool_parent) / "config.toml"
|
164
|
+
with tool_config_path.open("r") as f:
|
165
|
+
tool_config = toml.load(f)
|
166
|
+
tool_vars = tool_config.get("tool_var")
|
167
|
+
if not tool_vars:
|
168
|
+
return
|
169
|
+
tool_vars_dict = {}
|
170
|
+
for key, value in tool_vars.items():
|
171
|
+
tool_vars_dict[key] = value
|
172
|
+
return tool_vars_dict
|
@@ -29,6 +29,61 @@ class TestFunctionTool(TestCase):
|
|
29
29
|
|
30
30
|
# then
|
31
31
|
self.assertEqual(result, '3')
|
32
|
+
|
33
|
+
def test_function_tool_variables(self):
|
34
|
+
@function_tool(
|
35
|
+
tool_vars={
|
36
|
+
'a': '1',
|
37
|
+
'b': '2',
|
38
|
+
},
|
39
|
+
)
|
40
|
+
def always_three(**kwargs):
|
41
|
+
a = int(kwargs['a'])
|
42
|
+
b = int(kwargs['b'])
|
43
|
+
return str(a+b)
|
44
|
+
|
45
|
+
result = always_three.invoke(
|
46
|
+
body={}
|
47
|
+
)
|
48
|
+
self.assertEqual(result, '3')
|
49
|
+
|
50
|
+
def test_function_tool_overridden_variables(self):
|
51
|
+
@function_tool(
|
52
|
+
tool_vars={
|
53
|
+
'a': '1',
|
54
|
+
'b': '1',
|
55
|
+
},
|
56
|
+
)
|
57
|
+
def always_two(**kwargs):
|
58
|
+
a = int(kwargs['a'])
|
59
|
+
b = int(kwargs['b'])
|
60
|
+
return str(a+b)
|
61
|
+
|
62
|
+
always_two.override_tool_variables({
|
63
|
+
'a': '1',
|
64
|
+
'b': '2',
|
65
|
+
})
|
66
|
+
result = always_two.invoke(body={})
|
67
|
+
self.assertEqual(result, '3')
|
68
|
+
|
69
|
+
def test_function_tool_overridden_variables_from_func(self):
|
70
|
+
@function_tool(
|
71
|
+
tool_vars={
|
72
|
+
'a': '1',
|
73
|
+
'b': '1',
|
74
|
+
},
|
75
|
+
)
|
76
|
+
def always_two(**kwargs):
|
77
|
+
a = int(kwargs['a'])
|
78
|
+
b = int(kwargs['b'])
|
79
|
+
return str(a+b)
|
80
|
+
|
81
|
+
tool = FunctionTool.from_func(always_two, tool_vars={
|
82
|
+
"a": "1",
|
83
|
+
"b": "2",
|
84
|
+
})
|
85
|
+
result = tool.invoke(body={})
|
86
|
+
self.assertEqual(result, '3')
|
32
87
|
|
33
88
|
def test_pydantic_input_function_tool_call(self):
|
34
89
|
# given
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import pathlib
|
2
|
+
from tempfile import TemporaryDirectory
|
3
|
+
from unittest import TestCase
|
4
|
+
|
5
|
+
from hyperpocket.repository import Lockfile
|
6
|
+
from hyperpocket.tool import from_local
|
7
|
+
from hyperpocket.tool.wasm import WasmTool
|
8
|
+
from hyperpocket.tool.wasm.invoker import WasmInvoker
|
9
|
+
|
10
|
+
|
11
|
+
class TestWasmTool(TestCase):
|
12
|
+
|
13
|
+
class MockInvoker(WasmInvoker):
|
14
|
+
def invoke(self, tool_path, runtime, body, envs, **kwargs):
|
15
|
+
return str(int(envs["a"]) + int(envs["b"]))
|
16
|
+
async def ainvoke(self, tool_path, runtime, body, envs, **kwargs):
|
17
|
+
return str(int(envs["a"]) + int(envs["b"]))
|
18
|
+
|
19
|
+
def test_wasm_tool_vars_inject(self):
|
20
|
+
with TemporaryDirectory() as tool_dir:
|
21
|
+
with open(f"{tool_dir}/schema.json", "w") as f:
|
22
|
+
f.write("{}")
|
23
|
+
with open(f"{tool_dir}/config.toml", "w") as f:
|
24
|
+
f.write("""
|
25
|
+
name = "mock"
|
26
|
+
description = "mock"
|
27
|
+
language = "python"
|
28
|
+
|
29
|
+
[tool_vars]
|
30
|
+
a = "1"
|
31
|
+
b = "2"
|
32
|
+
""")
|
33
|
+
with open(f"{tool_dir}/README.md", "w") as f:
|
34
|
+
f.write("mock")
|
35
|
+
lockfile = Lockfile(pathlib.Path(tool_dir) / ".lock")
|
36
|
+
req = from_local(tool_dir)
|
37
|
+
lockfile.add_lock(req.lock)
|
38
|
+
lockfile.sync(False)
|
39
|
+
tool = WasmTool.from_tool_request(req, lockfile=lockfile)
|
40
|
+
tool._invoker = self.MockInvoker()
|
41
|
+
tool.override_tool_variables({
|
42
|
+
"a": "1",
|
43
|
+
"b": "1"
|
44
|
+
})
|
45
|
+
self.assertEqual(tool.invoke(body={}, envs={}), "2")
|
46
|
+
|
47
|
+
def test_wasm_tool_vars_inject_with_from_local(self):
|
48
|
+
with TemporaryDirectory() as tool_dir:
|
49
|
+
with open(f"{tool_dir}/schema.json", "w") as f:
|
50
|
+
f.write("{}")
|
51
|
+
with open(f"{tool_dir}/config.toml", "w") as f:
|
52
|
+
f.write("""
|
53
|
+
name = "mock"
|
54
|
+
description = "mock"
|
55
|
+
language = "python"
|
56
|
+
|
57
|
+
[tool_vars]
|
58
|
+
a = "1"
|
59
|
+
b = "2"
|
60
|
+
""")
|
61
|
+
with open(f"{tool_dir}/README.md", "w") as f:
|
62
|
+
f.write("mock")
|
63
|
+
lockfile = Lockfile(pathlib.Path(tool_dir) / ".lock")
|
64
|
+
req = from_local(tool_dir, tool_vars={
|
65
|
+
"a": "1",
|
66
|
+
"b": "1"
|
67
|
+
})
|
68
|
+
lockfile.add_lock(req.lock)
|
69
|
+
lockfile.sync(False)
|
70
|
+
tool = WasmTool.from_tool_request(req, lockfile=lockfile)
|
71
|
+
tool._invoker = self.MockInvoker()
|
72
|
+
self.assertEqual(tool.invoke(body={}, envs={}), "2")
|
73
|
+
|
hyperpocket/tool/tool.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
import abc
|
2
|
-
|
2
|
+
import pathlib
|
3
|
+
|
4
|
+
import toml
|
5
|
+
from typing import Optional, Type, Callable
|
3
6
|
|
4
7
|
from pydantic import BaseModel, Field
|
5
8
|
|
@@ -27,9 +30,33 @@ class ToolAuth(BaseModel):
|
|
27
30
|
|
28
31
|
|
29
32
|
class ToolRequest(abc.ABC):
|
33
|
+
postprocessings: Optional[list[Callable]] = None
|
34
|
+
overridden_tool_vars: dict[str, str] = Field(default_factory=dict, description="overridden tool variables")
|
35
|
+
|
30
36
|
@abc.abstractmethod
|
31
37
|
def __str__(self):
|
32
38
|
raise NotImplementedError
|
39
|
+
|
40
|
+
def add_postprocessing(self, postprocessing: Callable):
|
41
|
+
if self.postprocessings is None:
|
42
|
+
self.postprocessings = [postprocessing]
|
43
|
+
else:
|
44
|
+
self.postprocessings.append(postprocessing)
|
45
|
+
|
46
|
+
def __or__(self, other: Callable):
|
47
|
+
self.add_postprocessing(other)
|
48
|
+
return self
|
49
|
+
|
50
|
+
def with_postprocessings(self, postprocessings: list[Callable]):
|
51
|
+
if self.postprocessings is None:
|
52
|
+
self.postprocessings = postprocessings
|
53
|
+
else:
|
54
|
+
self.postprocessings.extend(postprocessings)
|
55
|
+
return self
|
56
|
+
|
57
|
+
def override_tool_variables(self, override_vars: dict[str, str]) -> 'ToolRequest':
|
58
|
+
self.overridden_tool_vars = override_vars
|
59
|
+
return self
|
33
60
|
|
34
61
|
|
35
62
|
class Tool(BaseModel, abc.ABC):
|
@@ -40,6 +67,9 @@ class Tool(BaseModel, abc.ABC):
|
|
40
67
|
description: str = Field(description="tool description")
|
41
68
|
argument_json_schema: Optional[dict] = Field(default=None, description="tool argument json schema")
|
42
69
|
auth: Optional[ToolAuth] = Field(default=None, description="authentication information to invoke tool")
|
70
|
+
postprocessings: Optional[list[Callable]] = Field(default=None, description="postprocessing functions after tool is invoked")
|
71
|
+
default_tool_vars: dict[str, str] = Field(default_factory=dict, description="default tool variables")
|
72
|
+
overridden_tool_vars: dict[str, str] = Field(default_factory=dict, description="overridden tool variables")
|
43
73
|
|
44
74
|
@abc.abstractmethod
|
45
75
|
def invoke(self, **kwargs) -> str:
|
@@ -60,6 +90,14 @@ class Tool(BaseModel, abc.ABC):
|
|
60
90
|
to include profile and thread_id as arguments when the tool is invoked
|
61
91
|
"""
|
62
92
|
return self._get_schema_model(self.name, self.argument_json_schema)
|
93
|
+
|
94
|
+
def override_tool_variables(self, override_vars: dict[str, str]) -> 'Tool':
|
95
|
+
self.overridden_tool_vars = override_vars
|
96
|
+
return self
|
97
|
+
|
98
|
+
@property
|
99
|
+
def tool_vars(self) -> dict[str, str]:
|
100
|
+
return self.default_tool_vars | self.overridden_tool_vars
|
63
101
|
|
64
102
|
@classmethod
|
65
103
|
def from_tool_request(cls, tool_req: ToolRequest, **kwargs) -> 'Tool':
|
@@ -75,7 +113,7 @@ class Tool(BaseModel, abc.ABC):
|
|
75
113
|
pocket_logger.info(f"{name} tool's json_schema is none.")
|
76
114
|
return None
|
77
115
|
if 'description' not in json_schema:
|
78
|
-
json_schema['description'] =
|
116
|
+
json_schema['description'] = 'The argument of the tool.'
|
79
117
|
extended_schema = {
|
80
118
|
'title': name,
|
81
119
|
'type': 'object',
|
@@ -104,3 +142,28 @@ class Tool(BaseModel, abc.ABC):
|
|
104
142
|
except Exception as e:
|
105
143
|
pocket_logger.warning(f"failed to get tool({name}) schema model. error : {e}")
|
106
144
|
pass
|
145
|
+
|
146
|
+
def with_postprocessing(self, postprocessing: Callable):
|
147
|
+
"""
|
148
|
+
Add a postprocessing function to the tool
|
149
|
+
"""
|
150
|
+
if self.postprocessings is None:
|
151
|
+
self.postprocessings = [postprocessing]
|
152
|
+
else:
|
153
|
+
self.postprocessings.append(postprocessing)
|
154
|
+
return self
|
155
|
+
|
156
|
+
def __or__(self, other: Callable):
|
157
|
+
self.with_postprocessing(other)
|
158
|
+
return self
|
159
|
+
|
160
|
+
def with_postprocessings(self, postprocessings: list[Callable]):
|
161
|
+
"""
|
162
|
+
Add a list of postprocessing functions to the tool
|
163
|
+
Returns the tool itself
|
164
|
+
"""
|
165
|
+
if self.postprocessings is None:
|
166
|
+
self.postprocessings = postprocessings
|
167
|
+
else:
|
168
|
+
self.postprocessings.extend(postprocessings)
|
169
|
+
return self
|
hyperpocket/tool/wasm/README.md
CHANGED
@@ -52,7 +52,6 @@ WasmToolRequest offer those build method:
|
|
52
52
|
|
53
53
|
- `from_local`
|
54
54
|
- `from_git`
|
55
|
-
- `from_github`
|
56
55
|
|
57
56
|
Each `WasmToolRequest` has it's own `Lock` object
|
58
57
|
|
@@ -98,6 +97,29 @@ classDiagram
|
|
98
97
|
Lockfile o-- Pocket : 1..1
|
99
98
|
```
|
100
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 envvars by settings.toml
|
121
|
+
If there are remaining envvars, Hyperpocket checks the `settings.toml` from the agent code directory.
|
122
|
+
|
101
123
|
## WasmTool
|
102
124
|
|
103
125
|
```python
|
@@ -112,11 +134,11 @@ class WasmTool(Tool):
|
|
112
134
|
|
113
135
|
- `_invoker`: A class for executing WASM.
|
114
136
|
- `pkg_lock`: The lock class for the tool.
|
115
|
-
|
137
|
+
- Used for determining the package path where the current WASM code is stored.
|
116
138
|
- `rel_path`: The relative path to the location of the WASM code within the package.
|
117
139
|
- `runtime`: The runtime language of the WASM code.
|
118
140
|
- `json_schema`: The JSON schema for the WASM tool.
|
119
|
-
|
141
|
+
- Information read from schema.json.
|
120
142
|
- `readme`: The README information.
|
121
143
|
|
122
144
|
## WasmInvoker
|
@@ -134,11 +156,11 @@ sequenceDiagram
|
|
134
156
|
participant WasmTool as WasmTool (Includes Server)
|
135
157
|
participant Browser as Browser (Executes WASM Runtime)
|
136
158
|
|
137
|
-
|
159
|
+
|
138
160
|
WasmTool->>WasmTool: Render HTML and Store in Memory
|
139
161
|
WasmTool->>Browser: Launch Browser
|
140
162
|
Browser->>WasmTool: Request Rendered HTML
|
141
163
|
WasmTool->>Browser: Provide Rendered HTML
|
142
164
|
Browser->>Browser: Execute Injected WASM Runtime Script
|
143
165
|
Browser->>WasmTool: Return Execution Result
|
144
|
-
```
|
166
|
+
```
|
hyperpocket/tool/wasm/script.py
CHANGED
@@ -3,8 +3,11 @@ import enum
|
|
3
3
|
import pathlib
|
4
4
|
from typing import Optional
|
5
5
|
|
6
|
+
import toml
|
6
7
|
from pydantic import BaseModel
|
7
8
|
|
9
|
+
from hyperpocket.config import pocket_logger
|
10
|
+
|
8
11
|
|
9
12
|
class ScriptRuntime(enum.Enum):
|
10
13
|
Node = "node"
|
@@ -62,7 +65,43 @@ class Script(BaseModel):
|
|
62
65
|
encoded_str = encoded_bytes.decode()
|
63
66
|
file_tree = ScriptFileNode.merge(file_tree, ScriptFileNode.create_file_tree(p, encoded_str))
|
64
67
|
return file_tree
|
65
|
-
|
68
|
+
|
69
|
+
@property
|
70
|
+
def package_name(self) -> Optional[str]:
|
71
|
+
if self.runtime != ScriptRuntime.Python:
|
72
|
+
return
|
73
|
+
pyproject = toml.load(pathlib.Path(self.tool_path) / "pyproject.toml")
|
74
|
+
if "project" in pyproject:
|
75
|
+
name = pyproject["project"]["name"]
|
76
|
+
if "tool" in pyproject and "poetry" in pyproject["tool"]:
|
77
|
+
name = pyproject["tool"]["poetry"]["name"]
|
78
|
+
if not name:
|
79
|
+
raise ValueError("Could not find package name")
|
80
|
+
return name.replace("-", "_")
|
81
|
+
|
82
|
+
@property
|
83
|
+
def entrypoint(self) -> str:
|
84
|
+
pocket_logger.info(self.tool_path)
|
85
|
+
if self.runtime == ScriptRuntime.Node:
|
86
|
+
return "dist/index.js"
|
87
|
+
elif self.runtime == ScriptRuntime.Wasm:
|
88
|
+
return "dist/main.wasm"
|
89
|
+
pyproject = toml.load(pathlib.Path(self.tool_path) / "pyproject.toml")
|
90
|
+
version = None
|
91
|
+
if "project" in pyproject:
|
92
|
+
version = pyproject["project"]["version"]
|
93
|
+
elif "tool" in pyproject and "poetry" in pyproject["tool"]:
|
94
|
+
version = pyproject["tool"]["poetry"]["version"]
|
95
|
+
else:
|
96
|
+
raise ValueError("Could not find package version")
|
97
|
+
wheel_name = f"{self.package_name}-{version}-py3-none-any.whl"
|
98
|
+
wheel_path = pathlib.Path(self.tool_path) / "dist" / wheel_name
|
99
|
+
if not wheel_path.exists():
|
100
|
+
raise ValueError(f"Wheel file {wheel_path} does not exist")
|
101
|
+
return wheel_name
|
102
|
+
|
103
|
+
def dist_file_path(self, file_name: str) -> str:
|
104
|
+
return str(pathlib.Path(self.tool_path) / "dist" / file_name)
|
66
105
|
|
67
106
|
class _ScriptStore(object):
|
68
107
|
scripts: dict[str, Script] = {}
|
@@ -16,27 +16,24 @@ python_template = '''
|
|
16
16
|
scriptID: `{{ SCRIPT_ID }}`
|
17
17
|
}
|
18
18
|
}
|
19
|
-
async function
|
19
|
+
async function _main() {
|
20
|
+
// load the script configs
|
20
21
|
loadConfig();
|
21
|
-
const b64FilesResp = await fetch(`/tools/wasm/scripts/${globalThis.toolConfigs.scriptID}/file_tree`);
|
22
|
-
const b64Files = await b64FilesResp.json();
|
23
|
-
const code = atob(b64Files.tree["main.py"].file.contents);
|
24
|
-
const requirements = atob(b64Files.tree["requirements.txt"].file.contents);
|
25
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
|
26
28
|
const pyodide = await loadPyodide({
|
27
29
|
env: JSON.parse(globalThis.toolConfigs.envs),
|
28
30
|
});
|
29
31
|
await pyodide.loadPackage("micropip");
|
30
32
|
await pyodide.loadPackage("ssl");
|
31
33
|
const micropip = pyodide.pyimport("micropip");
|
34
|
+
await micropip.install(entrypoint);
|
32
35
|
await micropip.install("pyodide-http")
|
33
|
-
|
34
|
-
if (req) {
|
35
|
-
const pkg = req.split("==")[0];
|
36
|
-
await micropip.install(pkg);
|
37
|
-
}
|
38
|
-
});
|
39
|
-
await Promise.all(installation);
|
36
|
+
|
40
37
|
let emitted = false;
|
41
38
|
const decodedBytes = atob(globalThis.toolConfigs.body);
|
42
39
|
pyodide.setStdin({
|
@@ -50,14 +47,19 @@ python_template = '''
|
|
50
47
|
autoEOF: true,
|
51
48
|
})
|
52
49
|
let stdout = "";
|
50
|
+
let stderr = "";
|
53
51
|
pyodide.setStdout({
|
54
52
|
batched: (x) => { stdout += x; },
|
55
53
|
})
|
54
|
+
pyodide.setStderr({
|
55
|
+
batched: (x) => { stderr += x; },
|
56
|
+
})
|
56
57
|
await pyodide.runPythonAsync(`
|
57
58
|
import pyodide_http
|
58
59
|
pyodide_http.patch_all()
|
59
60
|
|
60
|
-
${
|
61
|
+
import ${packageName}
|
62
|
+
${packageName}.main()
|
61
63
|
`);
|
62
64
|
console.log(stdout)
|
63
65
|
await fetch(`/tools/wasm/scripts/${globalThis.toolConfigs.scriptID}/done`, {
|
@@ -65,9 +67,25 @@ ${code}
|
|
65
67
|
headers: {
|
66
68
|
'Content-Type': 'application/json'
|
67
69
|
},
|
68
|
-
body: JSON.stringify({ stdout })
|
70
|
+
body: JSON.stringify({ stdout, stderr })
|
69
71
|
});
|
70
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
|
+
|
71
89
|
main();
|
72
90
|
</script>
|
73
91
|
</body>
|