hyperpocket 0.0.2__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.
Files changed (97) hide show
  1. hyperpocket/auth/README.md +3 -3
  2. hyperpocket/auth/__init__.py +0 -8
  3. hyperpocket/auth/gumloop/context.py +13 -0
  4. hyperpocket/auth/gumloop/token_context.py +15 -0
  5. hyperpocket/auth/gumloop/token_handler.py +66 -0
  6. hyperpocket/auth/gumloop/token_schema.py +8 -0
  7. hyperpocket/auth/linear/token_context.py +1 -1
  8. hyperpocket/auth/notion/README.md +28 -0
  9. hyperpocket/auth/notion/context.py +15 -0
  10. hyperpocket/auth/notion/token_context.py +14 -0
  11. hyperpocket/auth/notion/token_handler.py +65 -0
  12. hyperpocket/auth/notion/token_schema.py +10 -0
  13. hyperpocket/auth/provider.py +8 -5
  14. hyperpocket/auth/reddit/context.py +15 -0
  15. hyperpocket/auth/reddit/oauth2_context.py +32 -0
  16. hyperpocket/auth/reddit/oauth2_handler.py +151 -0
  17. hyperpocket/auth/reddit/oauth2_schema.py +18 -0
  18. hyperpocket/auth/slack/token_context.py +1 -1
  19. hyperpocket/builtin.py +63 -0
  20. hyperpocket/cli/__main__.py +12 -0
  21. hyperpocket/cli/auth.py +83 -0
  22. hyperpocket/cli/codegen/auth/__init__.py +13 -0
  23. hyperpocket/cli/codegen/auth/auth_context_template.py +16 -0
  24. hyperpocket/cli/codegen/auth/auth_token_context_template.py +16 -0
  25. hyperpocket/cli/codegen/auth/auth_token_handler_template.py +69 -0
  26. hyperpocket/cli/codegen/auth/auth_token_schema_template.py +12 -0
  27. hyperpocket/cli/codegen/auth/server_auth_template.py +18 -0
  28. hyperpocket/cli/eject.py +19 -0
  29. hyperpocket/cli/sync.py +5 -5
  30. hyperpocket/config/settings.py +2 -4
  31. hyperpocket/futures/futurestore.py +0 -1
  32. hyperpocket/pocket_auth.py +25 -5
  33. hyperpocket/pocket_core.py +262 -0
  34. hyperpocket/pocket_main.py +125 -171
  35. hyperpocket/prompts.py +6 -8
  36. hyperpocket/repository/__init__.py +2 -2
  37. hyperpocket/repository/lock.py +19 -0
  38. hyperpocket/repository/lockfile.py +19 -13
  39. hyperpocket/repository/repository.py +26 -1
  40. hyperpocket/server/auth/__init__.py +0 -6
  41. hyperpocket/server/auth/gumloop.py +16 -0
  42. hyperpocket/server/auth/notion.py +19 -0
  43. hyperpocket/server/auth/reddit.py +16 -0
  44. hyperpocket/server/server.py +52 -16
  45. hyperpocket/server/tool/dto/script.py +15 -2
  46. hyperpocket/server/tool/wasm.py +20 -8
  47. hyperpocket/session/README.md +2 -2
  48. hyperpocket/session/in_memory.py +18 -5
  49. hyperpocket/session/interface.py +14 -0
  50. hyperpocket/session/redis.py +29 -5
  51. hyperpocket/tool/README.md +16 -12
  52. hyperpocket/tool/__init__.py +4 -3
  53. hyperpocket/tool/function/README.md +39 -10
  54. hyperpocket/tool/function/__init__.py +2 -0
  55. hyperpocket/tool/function/annotation.py +2 -1
  56. hyperpocket/tool/function/tool.py +98 -13
  57. hyperpocket/tool/tests/test_function_tool.py +55 -0
  58. hyperpocket/tool/tests/test_wasm_tool.py +73 -0
  59. hyperpocket/tool/tool.py +65 -2
  60. hyperpocket/tool/wasm/README.md +27 -5
  61. hyperpocket/tool/wasm/script.py +40 -1
  62. hyperpocket/tool/wasm/templates/python.py +32 -14
  63. hyperpocket/tool/wasm/tool.py +21 -18
  64. hyperpocket/tool_like.py +5 -0
  65. hyperpocket/util/__init__.py +1 -1
  66. hyperpocket/util/extract_func_param_desc_from_docstring.py +4 -4
  67. hyperpocket/util/function_to_model.py +5 -2
  68. hyperpocket/util/json_schema_to_model.py +45 -26
  69. {hyperpocket-0.0.2.dist-info → hyperpocket-0.1.8.dist-info}/METADATA +101 -72
  70. hyperpocket-0.1.8.dist-info/RECORD +139 -0
  71. {hyperpocket-0.0.2.dist-info → hyperpocket-0.1.8.dist-info}/WHEEL +1 -1
  72. hyperpocket-0.1.8.dist-info/entry_points.txt +2 -0
  73. hyperpocket/auth/README.KR.md +0 -309
  74. hyperpocket/auth/slack/tests/test_oauth2_handler.py +0 -32
  75. hyperpocket/auth/slack/tests/test_token_handler.py +0 -23
  76. hyperpocket/auth/tests/test_google_oauth2_handler.py +0 -147
  77. hyperpocket/auth/tests/test_slack_oauth2_handler.py +0 -147
  78. hyperpocket/auth/tests/test_slack_token_handler.py +0 -66
  79. hyperpocket/external/__init__.py +0 -7
  80. hyperpocket/external/github_client.py +0 -19
  81. hyperpocket/session/README.KR.md +0 -62
  82. hyperpocket/session/tests/test_in_memory.py +0 -145
  83. hyperpocket/session/tests/test_redis.py +0 -151
  84. hyperpocket/tests/test_pocket.py +0 -116
  85. hyperpocket/tests/test_pocket_auth.py +0 -982
  86. hyperpocket/tool/README.KR.md +0 -68
  87. hyperpocket/tool/builtins/__init__.py +0 -0
  88. hyperpocket/tool/builtins/example/__init__.py +0 -0
  89. hyperpocket/tool/builtins/example/add_tool.py +0 -18
  90. hyperpocket/tool/function/README.KR.md +0 -159
  91. hyperpocket/tool/wasm/README.KR.md +0 -144
  92. hyperpocket-0.0.2.dist-info/RECORD +0 -130
  93. hyperpocket-0.0.2.dist-info/entry_points.txt +0 -3
  94. /hyperpocket/auth/{slack/tests → gumloop}/__init__.py +0 -0
  95. /hyperpocket/auth/{tests → notion}/__init__.py +0 -0
  96. /hyperpocket/{session/tests → auth/reddit}/__init__.py +0 -0
  97. /hyperpocket/{tests → cli/codegen}/__init__.py +0 -0
@@ -1,7 +1,11 @@
1
+ import asyncio
1
2
  import copy
2
3
  import inspect
3
- from typing import Callable, Awaitable, Optional
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[..., Awaitable[str]]]
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
- return str(self.func(**binding_args))
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.func(**binding_args))
27
- return str(await self.afunc(**binding_args))
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
- cls,
73
- func: Callable,
74
- afunc: Callable[..., Awaitable[str]] = None,
75
- auth: Optional[ToolAuth] = None
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
- from typing import TypeVar, Optional, Type
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'] = f'The argument of the tool.'
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
@@ -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
- - Used to determine the package path where the current WASM code is stored.
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
- - Information read from schema.json.
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
+ ```
@@ -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 main() {
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
- const installation = requirements.split("\\n").map(async (req) => {
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
- ${code}
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>