hyperpocket 0.0.3__py3-none-any.whl → 0.1.9__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 +14 -12
- hyperpocket/futures/futurestore.py +0 -1
- hyperpocket/pocket_auth.py +25 -5
- hyperpocket/pocket_core.py +264 -0
- hyperpocket/pocket_main.py +127 -174
- hyperpocket/prompts.py +6 -8
- hyperpocket/repository/__init__.py +2 -2
- hyperpocket/repository/lock.py +71 -1
- 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 +56 -20
- 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 +108 -29
- hyperpocket/tool/tool.py +100 -28
- hyperpocket/tool/wasm/README.md +27 -5
- hyperpocket/tool/wasm/browser.py +2 -7
- 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 +47 -26
- {hyperpocket-0.0.3.dist-info → hyperpocket-0.1.9.dist-info}/METADATA +107 -88
- hyperpocket-0.1.9.dist-info/RECORD +137 -0
- {hyperpocket-0.0.3.dist-info → hyperpocket-0.1.9.dist-info}/WHEEL +1 -1
- hyperpocket-0.1.9.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/tests/test_function_tool.py +0 -266
- 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
hyperpocket/server/server.py
CHANGED
@@ -8,6 +8,7 @@ from fastapi import FastAPI
|
|
8
8
|
from uvicorn import Config, Server
|
9
9
|
|
10
10
|
from hyperpocket.config import config, pocket_logger
|
11
|
+
from hyperpocket.pocket_core import PocketCore
|
11
12
|
from hyperpocket.server.auth import auth_router
|
12
13
|
from hyperpocket.server.tool import tool_router
|
13
14
|
|
@@ -27,6 +28,7 @@ class PocketServer(object):
|
|
27
28
|
pipe: mp.Pipe
|
28
29
|
process: mp.Process
|
29
30
|
future_store: dict[str, asyncio.Future]
|
31
|
+
torn_down: bool = False
|
30
32
|
|
31
33
|
def __init__(self,
|
32
34
|
internal_server_port: int = config.internal_server_port,
|
@@ -36,9 +38,12 @@ class PocketServer(object):
|
|
36
38
|
self.future_store = dict()
|
37
39
|
|
38
40
|
def teardown(self):
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
# @XXX(seokju) is it ok to call this method both in __del__ and __exit__?
|
42
|
+
if self.torn_down:
|
43
|
+
return
|
44
|
+
self.torn_down = True
|
45
|
+
self.process.terminate()
|
46
|
+
self.process.join()
|
42
47
|
|
43
48
|
async def _run_async(self):
|
44
49
|
try:
|
@@ -55,20 +60,48 @@ class PocketServer(object):
|
|
55
60
|
_, conn = self.pipe
|
56
61
|
|
57
62
|
async def _acall(_conn, _op, _uid, a, kw):
|
58
|
-
|
59
|
-
|
63
|
+
try:
|
64
|
+
result = await self.pocket_core.acall(*a, **kw)
|
65
|
+
error = None
|
66
|
+
except Exception as e:
|
67
|
+
pocket_logger.error(f"failed to acall in pocket subprocess. error: {e}")
|
68
|
+
result = None
|
69
|
+
error = e
|
70
|
+
|
71
|
+
_conn.send((_op, _uid, result, error))
|
60
72
|
|
61
73
|
async def _prepare(_conn, _op, _uid, a, kw):
|
62
|
-
|
63
|
-
|
74
|
+
try:
|
75
|
+
result = self.pocket_core.prepare_auth(*a, **kw)
|
76
|
+
error = None
|
77
|
+
except Exception as e:
|
78
|
+
pocket_logger.error(f"failed to prepare in pocket subprocess. error: {e}")
|
79
|
+
result = None
|
80
|
+
error = e
|
81
|
+
|
82
|
+
_conn.send((_op, _uid, result, error))
|
64
83
|
|
65
84
|
async def _authenticate(_conn, _op, _uid, a, kw):
|
66
|
-
|
67
|
-
|
85
|
+
try:
|
86
|
+
result = await self.pocket_core.authenticate(*a, **kw)
|
87
|
+
error = None
|
88
|
+
except Exception as e:
|
89
|
+
pocket_logger.error(f"failed to authenticate in pocket subprocess. error: {e}")
|
90
|
+
result = None
|
91
|
+
error = e
|
92
|
+
|
93
|
+
_conn.send((_op, _uid, result, error))
|
68
94
|
|
69
95
|
async def _tool_call(_conn, _op, _uid, a, kw):
|
70
|
-
|
71
|
-
|
96
|
+
try:
|
97
|
+
result = await self.pocket_core.tool_call(*a, **kw)
|
98
|
+
error = None
|
99
|
+
except Exception as e:
|
100
|
+
pocket_logger.error(f"failed to tool_call in pocket subprocess. error: {e}")
|
101
|
+
result = None
|
102
|
+
error = e
|
103
|
+
|
104
|
+
_conn.send((_op, _uid, result, error))
|
72
105
|
|
73
106
|
while True:
|
74
107
|
if conn.poll():
|
@@ -102,9 +135,12 @@ class PocketServer(object):
|
|
102
135
|
conn, _ = self.pipe
|
103
136
|
while True:
|
104
137
|
if conn.poll():
|
105
|
-
op, uid, result = conn.recv()
|
138
|
+
op, uid, result, error = conn.recv()
|
106
139
|
future = self.future_store[uid]
|
107
|
-
|
140
|
+
if error:
|
141
|
+
future.set_exception(error)
|
142
|
+
else:
|
143
|
+
future.set_result(result)
|
108
144
|
break
|
109
145
|
else:
|
110
146
|
await asyncio.sleep(0)
|
@@ -118,15 +154,15 @@ class PocketServer(object):
|
|
118
154
|
loop.create_task(self.poll_in_parent())
|
119
155
|
return await self.future_store[uid]
|
120
156
|
|
121
|
-
def run(self,
|
157
|
+
def run(self, pocket_core: PocketCore):
|
122
158
|
self._set_mp_start_method()
|
123
159
|
|
124
160
|
self.pipe = mp.Pipe()
|
125
|
-
self.process = mp.Process(target=self._run, args=(
|
161
|
+
self.process = mp.Process(target=self._run, args=(pocket_core,))
|
126
162
|
self.process.start()
|
127
163
|
|
128
|
-
def _run(self,
|
129
|
-
self.
|
164
|
+
def _run(self, pocket_core):
|
165
|
+
self.pocket_core = pocket_core
|
130
166
|
self.main_server = self._create_main_server()
|
131
167
|
self.proxy_server = self._create_https_proxy_server()
|
132
168
|
loop = asyncio.new_event_loop()
|
@@ -149,9 +185,9 @@ class PocketServer(object):
|
|
149
185
|
from hyperpocket.server.proxy import _generate_ssl_certificates
|
150
186
|
from hyperpocket.server.proxy import https_proxy_app
|
151
187
|
|
152
|
-
from hyperpocket.config.settings import
|
153
|
-
ssl_keypath =
|
154
|
-
ssl_certpath =
|
188
|
+
from hyperpocket.config.settings import POCKET_ROOT
|
189
|
+
ssl_keypath = POCKET_ROOT / "callback_server.key"
|
190
|
+
ssl_certpath = POCKET_ROOT / "callback_server.crt"
|
155
191
|
|
156
192
|
if not ssl_keypath.exists() or not ssl_certpath.exists():
|
157
193
|
_generate_ssl_certificates(ssl_keypath, ssl_certpath)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
from typing import Optional
|
1
2
|
from pydantic import BaseModel, Field
|
2
3
|
|
3
4
|
from hyperpocket.tool.wasm.script import ScriptFileNode
|
@@ -8,8 +9,20 @@ class Script(BaseModel):
|
|
8
9
|
tool_id: str = Field(alias='tool_id')
|
9
10
|
|
10
11
|
|
11
|
-
class
|
12
|
-
stdout: str = Field(alias='stdout')
|
12
|
+
class ScriptResult(BaseModel):
|
13
|
+
stdout: Optional[str] = Field(alias='stdout', default=None)
|
14
|
+
stderr: Optional[str] = Field(alias='stderr', default=None)
|
15
|
+
error: Optional[str] = Field(alias='error', default=None)
|
13
16
|
|
14
17
|
class ScriptFileTree(BaseModel):
|
15
18
|
tree: dict[str, ScriptFileNode] = Field(alias='tree')
|
19
|
+
|
20
|
+
class ScriptEntrypoint(BaseModel):
|
21
|
+
package_name: Optional[str] = Field(alias='package_name')
|
22
|
+
entrypoint: str = Field(alias='entrypoint')
|
23
|
+
|
24
|
+
class ScriptEncodedFile(BaseModel):
|
25
|
+
encoded_file: str = Field(alias='encoded_file')
|
26
|
+
|
27
|
+
class ScriptFileRequest(BaseModel):
|
28
|
+
path: str = Field(alias='path')
|
hyperpocket/server/tool/wasm.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from fastapi import APIRouter
|
2
|
-
from fastapi.responses import HTMLResponse
|
2
|
+
from fastapi.responses import HTMLResponse, FileResponse
|
3
3
|
|
4
4
|
from hyperpocket.futures import FutureStore
|
5
5
|
from hyperpocket.server.tool.dto import script as scriptdto
|
@@ -17,15 +17,27 @@ async def browse_script_page(script_id: str):
|
|
17
17
|
|
18
18
|
|
19
19
|
@wasm_tool_router.post("/scripts/{script_id}/done")
|
20
|
-
async def done_script_page(script_id: str, req: scriptdto.
|
21
|
-
FutureStore.resolve_future(script_id,
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
async def done_script_page(script_id: str, req: scriptdto.ScriptResult) -> scriptdto.ScriptResult:
|
21
|
+
FutureStore.resolve_future(script_id, {
|
22
|
+
'stdout': req.stdout,
|
23
|
+
'stderr': req.stderr,
|
24
|
+
'error': req.error
|
25
|
+
})
|
26
|
+
return req
|
27
27
|
|
28
28
|
@wasm_tool_router.get("/scripts/{script_id}/file_tree")
|
29
29
|
async def get_file_tree(script_id: str) -> scriptdto.ScriptFileTree:
|
30
30
|
script = ScriptStore.get_script(script_id)
|
31
31
|
return scriptdto.ScriptFileTree(tree=script.load_file_tree())
|
32
|
+
|
33
|
+
@wasm_tool_router.get("/scripts/{script_id}/entrypoint")
|
34
|
+
async def get_entrypoint(script_id: str) -> scriptdto.ScriptEntrypoint:
|
35
|
+
script = ScriptStore.get_script(script_id)
|
36
|
+
package_name = script.package_name
|
37
|
+
entrypoint = f"/tools/wasm/scripts/{script_id}/file/{script.entrypoint}"
|
38
|
+
return scriptdto.ScriptEntrypoint(package_name=package_name, entrypoint=entrypoint)
|
39
|
+
|
40
|
+
@wasm_tool_router.get("/scripts/{script_id}/file/{file_name}", response_class=FileResponse)
|
41
|
+
async def get_dist_file(script_id: str, file_name: str):
|
42
|
+
script = ScriptStore.get_script(script_id)
|
43
|
+
return FileResponse(script.dist_file_path(file_name))
|
hyperpocket/session/README.md
CHANGED
@@ -55,7 +55,7 @@ To Be Updated (TBU)
|
|
55
55
|
|
56
56
|
## How to Implement
|
57
57
|
|
58
|
-
1. Add the SessionType enum in `
|
59
|
-
2. Add the SessionConfig in `
|
58
|
+
1. Add the SessionType enum in `hyperpocket/config/session.py`.
|
59
|
+
2. Add the SessionConfig in `hyperpocket/config/session.py`.
|
60
60
|
3. Implement the SessionStorageInterface
|
61
61
|
- The session storage must be initialized with the SessionConfig defined above.
|
hyperpocket/session/in_memory.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import re
|
1
2
|
from typing import Dict, List, Optional
|
2
3
|
|
3
4
|
from hyperpocket.auth import AuthProvider
|
@@ -22,9 +23,21 @@ class InMemorySessionStorage(SessionStorageInterface[InMemorySessionKey, InMemor
|
|
22
23
|
return SessionType.IN_MEMORY
|
23
24
|
|
24
25
|
def get(self, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs) -> Optional[V]:
|
25
|
-
key = self._make_session_key(auth_provider, thread_id, profile)
|
26
|
+
key = self._make_session_key(auth_provider.name, thread_id, profile)
|
26
27
|
return self.storage.get(key, None)
|
27
28
|
|
29
|
+
def get_by_thread_id(self, thread_id: str, auth_provider: Optional[AuthProvider] = None, **kwargs) -> List[V]:
|
30
|
+
if auth_provider is None:
|
31
|
+
auth_provider_name = ".*"
|
32
|
+
else:
|
33
|
+
auth_provider_name = auth_provider.name
|
34
|
+
|
35
|
+
pattern = rf'{self._make_session_key(auth_provider_name, thread_id, ".*")}'
|
36
|
+
compiled = re.compile(pattern)
|
37
|
+
|
38
|
+
session_list = [value for key, value in self.storage.items() if compiled.match(key)]
|
39
|
+
return session_list
|
40
|
+
|
28
41
|
def set(self, auth_provider: AuthProvider,
|
29
42
|
thread_id: str,
|
30
43
|
profile: str,
|
@@ -32,7 +45,7 @@ class InMemorySessionStorage(SessionStorageInterface[InMemorySessionKey, InMemor
|
|
32
45
|
auth_resolve_uid: Optional[str],
|
33
46
|
auth_context: Optional[AuthContext],
|
34
47
|
is_auth_scope_universal: bool, **kwargs) -> V:
|
35
|
-
key = self._make_session_key(auth_provider, thread_id, profile)
|
48
|
+
key = self._make_session_key(auth_provider.name, thread_id, profile)
|
36
49
|
session = self._make_session(
|
37
50
|
auth_provider_name=auth_provider.name,
|
38
51
|
auth_scopes=auth_scopes,
|
@@ -44,7 +57,7 @@ class InMemorySessionStorage(SessionStorageInterface[InMemorySessionKey, InMemor
|
|
44
57
|
return session
|
45
58
|
|
46
59
|
def delete(self, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs) -> bool:
|
47
|
-
key = self._make_session_key(auth_provider, thread_id, profile)
|
60
|
+
key = self._make_session_key(auth_provider.name, thread_id, profile)
|
48
61
|
if key in self.storage:
|
49
62
|
self.storage.pop(key)
|
50
63
|
return True
|
@@ -52,9 +65,9 @@ class InMemorySessionStorage(SessionStorageInterface[InMemorySessionKey, InMemor
|
|
52
65
|
return False
|
53
66
|
|
54
67
|
@staticmethod
|
55
|
-
def _make_session_key(
|
68
|
+
def _make_session_key(auth_provider_name: str, thread_id: str, profile: str) -> K:
|
56
69
|
return "{auth_provider}{delimiter}{thread_id}{delimiter}{profile}".format(
|
57
|
-
auth_provider=
|
70
|
+
auth_provider=auth_provider_name,
|
58
71
|
thread_id=thread_id,
|
59
72
|
profile=profile,
|
60
73
|
delimiter=SESSION_KEY_DELIMITER,
|
hyperpocket/session/interface.py
CHANGED
@@ -68,6 +68,20 @@ class SessionStorageInterface(ABC, Generic[K, V]):
|
|
68
68
|
"""
|
69
69
|
raise NotImplementedError
|
70
70
|
|
71
|
+
@abstractmethod
|
72
|
+
def get_by_thread_id(self, thread_id: str, auth_provider: Optional[AuthProvider] = None, **kwargs) -> List[V]:
|
73
|
+
"""
|
74
|
+
Get session list by thread id
|
75
|
+
|
76
|
+
Args:
|
77
|
+
auth_provider (AuthProvider): auth provider
|
78
|
+
thread_id (str): thread id
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
List[V(BaseSessionValue)]: Session List
|
82
|
+
"""
|
83
|
+
raise NotImplementedError
|
84
|
+
|
71
85
|
@abstractmethod
|
72
86
|
def set(self,
|
73
87
|
auth_provider: AuthProvider,
|
hyperpocket/session/redis.py
CHANGED
@@ -25,7 +25,7 @@ class RedisSessionStorage(SessionStorageInterface[RedisSessionKey, RedisSessionV
|
|
25
25
|
return SessionType.REDIS
|
26
26
|
|
27
27
|
def get(self, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs) -> Optional[V]:
|
28
|
-
key = self._make_session_key(auth_provider, thread_id, profile)
|
28
|
+
key = self._make_session_key(auth_provider.name, thread_id, profile)
|
29
29
|
raw_session: Any = self.client.get(key)
|
30
30
|
if raw_session is None:
|
31
31
|
return None
|
@@ -33,6 +33,30 @@ class RedisSessionStorage(SessionStorageInterface[RedisSessionKey, RedisSessionV
|
|
33
33
|
session = self._deserialize(raw_session)
|
34
34
|
return session
|
35
35
|
|
36
|
+
def get_by_thread_id(self, thread_id: str, auth_provider: Optional[AuthProvider] = None, **kwargs) -> List[V]:
|
37
|
+
if auth_provider is None:
|
38
|
+
auth_provider_name = "*"
|
39
|
+
else:
|
40
|
+
auth_provider_name = auth_provider.name
|
41
|
+
|
42
|
+
pattern = self._make_session_key(auth_provider_name, thread_id, "*")
|
43
|
+
key_list = []
|
44
|
+
cursor = 0
|
45
|
+
while True:
|
46
|
+
cursor, keys = self.client.scan(cursor=cursor, match=pattern)
|
47
|
+
key_list.extend(keys)
|
48
|
+
if cursor == 0:
|
49
|
+
break
|
50
|
+
|
51
|
+
with self.client.pipeline() as pipe:
|
52
|
+
for key in key_list:
|
53
|
+
pipe.get(key)
|
54
|
+
|
55
|
+
raw_sessions = pipe.execute()
|
56
|
+
session_list = [self._deserialize(raw) for raw in raw_sessions]
|
57
|
+
|
58
|
+
return session_list
|
59
|
+
|
36
60
|
def set(self, auth_provider: AuthProvider,
|
37
61
|
thread_id: str,
|
38
62
|
profile: str,
|
@@ -47,20 +71,20 @@ class RedisSessionStorage(SessionStorageInterface[RedisSessionKey, RedisSessionV
|
|
47
71
|
auth_resolve_uid=auth_resolve_uid,
|
48
72
|
is_auth_scope_universal=is_auth_scope_universal)
|
49
73
|
|
50
|
-
key = self._make_session_key(auth_provider, thread_id, profile)
|
74
|
+
key = self._make_session_key(auth_provider.name, thread_id, profile)
|
51
75
|
|
52
76
|
raw_session = self._serialize(session)
|
53
77
|
self.client.set(key, raw_session)
|
54
78
|
return session
|
55
79
|
|
56
80
|
def delete(self, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs) -> bool:
|
57
|
-
key = self._make_session_key(auth_provider, thread_id, profile)
|
81
|
+
key = self._make_session_key(auth_provider.name, thread_id, profile)
|
58
82
|
return self.client.delete(key) == 1
|
59
83
|
|
60
84
|
@staticmethod
|
61
|
-
def _make_session_key(
|
85
|
+
def _make_session_key(auth_provider_name: str, thread_id: str, profile: str) -> K:
|
62
86
|
return "{auth_provider}{delimiter}{thread_id}{delimiter}{profile}".format(
|
63
|
-
auth_provider=
|
87
|
+
auth_provider=auth_provider_name,
|
64
88
|
thread_id=thread_id,
|
65
89
|
profile=profile,
|
66
90
|
delimiter=SESSION_KEY_DELIMITER,
|
hyperpocket/tool/README.md
CHANGED
@@ -13,13 +13,15 @@ The class that contains authentication information to invoke tool
|
|
13
13
|
authentication information fields are:
|
14
14
|
|
15
15
|
- `auth_provider`: Indicates which authentication provider’s credentials are required to invoke the tool.
|
16
|
-
|
16
|
+
|
17
|
+
- If auth_provider is not specified, the tool is considered to require no authentication.
|
17
18
|
|
18
19
|
- `auth_handler`: Specifies which authentication handler should be used when invoking the tool.
|
19
|
-
|
20
|
+
|
21
|
+
- If auth_handler is not specified, the default handler of the authentication provider will be used.
|
20
22
|
|
21
23
|
- `scopes`: Indicates the authentication scopes required to invoke the tool.
|
22
|
-
|
24
|
+
- If authentication is not performed or the authentication handler is non-scoped, the value should be None.
|
23
25
|
|
24
26
|
## Tool
|
25
27
|
|
@@ -31,6 +33,9 @@ class Tool(BaseModel, abc.ABC):
|
|
31
33
|
description: str = Field(description="tool description")
|
32
34
|
argument_json_schema: Optional[dict] = Field(default=None, description="tool argument json schema")
|
33
35
|
auth: Optional[ToolAuth] = Field(default=None, description="authentication information to invoke tool")
|
36
|
+
postprocessings: Optional[list[Callable]] = Field(default=None, description="postprocessing functions after tool is invoked")
|
37
|
+
default_tool_vars: dict[str, str] = Field(default_factory=dict, description="default tool variables")
|
38
|
+
overridden_tool_vars: dict[str, str] = Field(default_factory=dict, description="overridden tool variables")
|
34
39
|
```
|
35
40
|
|
36
41
|
### schema_model
|
@@ -43,16 +48,18 @@ In this process, the original `argument_json_schema` is moved under the `body` f
|
|
43
48
|
## How to implement
|
44
49
|
|
45
50
|
1. Create a class that inherits from Tool
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
|
52
|
+
- Define the tasks to be performed when invoking the tool in invoke or ainvoke.
|
53
|
+
- Define a factory method to initialize the tool.(Optional)
|
54
|
+
- Inject values for the required fields during initialization.
|
49
55
|
|
50
56
|
2. Add the class to `ToolLike` in Pocket (Optional)
|
51
|
-
|
52
|
-
|
57
|
+
|
58
|
+
- For `WasmTool`, the input can be a ToolRequest or a str.
|
59
|
+
- For `FunctionTool`, the input can be a Callable.
|
53
60
|
|
54
61
|
3. Perform tool initialization in `Pocket.__init__` or `Pocket._load_tool`
|
55
|
-
|
62
|
+
- The initialization is based on the provided ToolLike value.
|
56
63
|
|
57
64
|
## Invoke Flow
|
58
65
|
|
@@ -70,6 +77,3 @@ flowchart TD
|
|
70
77
|
B -->|No| F
|
71
78
|
E --> F["Tool: Perform Operations"]
|
72
79
|
```
|
73
|
-
|
74
|
-
|
75
|
-
|
hyperpocket/tool/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from hyperpocket.tool.function import from_func, function_tool
|
1
|
+
from hyperpocket.tool.function import from_dock, from_func, function_tool
|
2
2
|
from hyperpocket.tool.tool import Tool, ToolRequest, ToolAuth
|
3
3
|
from hyperpocket.tool.wasm.tool import from_local, from_git
|
4
4
|
|
@@ -8,6 +8,7 @@ __all__ = [
|
|
8
8
|
'ToolRequest',
|
9
9
|
'from_local',
|
10
10
|
'from_git',
|
11
|
-
|
12
|
-
|
11
|
+
'from_dock',
|
12
|
+
'from_func',
|
13
|
+
'function_tool'
|
13
14
|
]
|
@@ -64,7 +64,7 @@ Pocket(tools=[
|
|
64
64
|
])
|
65
65
|
```
|
66
66
|
|
67
|
-
Authentication access tokens are passed to the Python function as
|
67
|
+
Authentication access tokens are passed to the Python function as **variable keyword** arguments.
|
68
68
|
|
69
69
|
```python
|
70
70
|
from hyperpocket.tool import function_tool
|
@@ -82,6 +82,35 @@ def my_function(**kwargs):
|
|
82
82
|
|
83
83
|
- Check the `_ACCESS_TOKEN_KEY` field of each provider for the mapping key of their access tokens.
|
84
84
|
|
85
|
+
### Inject tool variables
|
86
|
+
|
87
|
+
If the user specifies `tool_vars` in the `@function_tool` decorator, which are allowed to be injected dynamically when the user develops an agent, it can be injected through the following steps.
|
88
|
+
|
89
|
+
```python
|
90
|
+
@function_tool(
|
91
|
+
tool_vars={
|
92
|
+
'a': '1',
|
93
|
+
'b': '1',
|
94
|
+
},
|
95
|
+
)
|
96
|
+
def always_two(**kwargs):
|
97
|
+
a = int(kwargs['a'])
|
98
|
+
b = int(kwargs['b'])
|
99
|
+
return str(a+b)
|
100
|
+
```
|
101
|
+
|
102
|
+
1. Injecting tool_vars when importing tool in code
|
103
|
+
|
104
|
+
```python
|
105
|
+
from_func('https://github.com/your-organization/your-repository/tree/main',
|
106
|
+
tool_vars = {
|
107
|
+
"b": "2"
|
108
|
+
})
|
109
|
+
```
|
110
|
+
|
111
|
+
2. Injecting tool_vars by settings.toml
|
112
|
+
Hyperpocket checks the `settings.toml` from the agent code directory and modify the tool_vars.
|
113
|
+
|
85
114
|
## Docstring Parsing
|
86
115
|
|
87
116
|
Parse docstrings to understand the meanings of arguments.
|
@@ -99,7 +128,7 @@ The following docstring styles are supported:
|
|
99
128
|
def my_function(a: int, b: int):
|
100
129
|
"""
|
101
130
|
My Function
|
102
|
-
|
131
|
+
|
103
132
|
Args:
|
104
133
|
a (int): First argument
|
105
134
|
b (int): Second argument
|
@@ -113,7 +142,7 @@ def my_function(a: int, b: int):
|
|
113
142
|
def my_function(a: int, b: int):
|
114
143
|
"""
|
115
144
|
My Function
|
116
|
-
|
145
|
+
|
117
146
|
:param a: First argument
|
118
147
|
:param b: Second argument
|
119
148
|
"""
|
@@ -126,7 +155,7 @@ def my_function(a: int, b: int):
|
|
126
155
|
def my_function(a: int, b: int):
|
127
156
|
"""
|
128
157
|
My Function
|
129
|
-
|
158
|
+
|
130
159
|
@param a: First argument
|
131
160
|
@param b: Second argument
|
132
161
|
"""
|
@@ -139,12 +168,12 @@ def my_function(a: int, b: int):
|
|
139
168
|
def my_function(a: int, b: int):
|
140
169
|
"""
|
141
170
|
My Function
|
142
|
-
|
171
|
+
|
143
172
|
@arg a: First argument
|
144
173
|
@arg b: Second argument
|
145
|
-
|
146
|
-
or
|
147
|
-
|
174
|
+
|
175
|
+
or
|
176
|
+
|
148
177
|
:arg a: First argument
|
149
178
|
:arg b: Second argument
|
150
179
|
"""
|
@@ -157,7 +186,7 @@ def my_function(a: int, b: int):
|
|
157
186
|
def my_function(a: int, b: int):
|
158
187
|
"""
|
159
188
|
My Function
|
160
|
-
|
189
|
+
|
161
190
|
a(int): First argument
|
162
191
|
b(int): Second argument
|
163
192
|
"""
|
@@ -166,4 +195,4 @@ def my_function(a: int, b: int):
|
|
166
195
|
|
167
196
|
## Limitations
|
168
197
|
|
169
|
-
Input types are limited to Python built-in types and Pydantic.
|
198
|
+
Input types are limited to Python built-in types and Pydantic.
|
@@ -2,8 +2,10 @@ from hyperpocket.tool.function.annotation import function_tool
|
|
2
2
|
from hyperpocket.tool.function.tool import FunctionTool
|
3
3
|
|
4
4
|
from_func = FunctionTool.from_func
|
5
|
+
from_dock = FunctionTool.from_dock
|
5
6
|
|
6
7
|
__all__ = [
|
7
8
|
"from_func",
|
9
|
+
"from_dock",
|
8
10
|
"function_tool"
|
9
11
|
]
|
@@ -6,7 +6,7 @@ from hyperpocket.tool.tool import ToolAuth
|
|
6
6
|
|
7
7
|
|
8
8
|
def function_tool(func: Optional[Callable] = None, *, auth_provider: AuthProvider = None, scopes: List[str] = None,
|
9
|
-
auth_handler: str = None):
|
9
|
+
auth_handler: str = None, tool_vars: dict[str, str] = None):
|
10
10
|
def decorator(inner_func: Callable):
|
11
11
|
if not callable(inner_func):
|
12
12
|
raise ValueError("FunctionTool can only be created from a callable")
|
@@ -21,6 +21,7 @@ def function_tool(func: Optional[Callable] = None, *, auth_provider: AuthProvide
|
|
21
21
|
return FunctionTool.from_func(
|
22
22
|
func=inner_func,
|
23
23
|
auth=auth,
|
24
|
+
tool_vars=tool_vars
|
24
25
|
)
|
25
26
|
|
26
27
|
if func is not None:
|