hyperpocket 0.4.5__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hyperpocket/builtin.py +3 -3
- hyperpocket/cli/__main__.py +0 -2
- hyperpocket/config/logger.py +2 -3
- hyperpocket/futures/futurestore.py +7 -2
- hyperpocket/pocket_auth.py +10 -8
- hyperpocket/pocket_main.py +247 -99
- hyperpocket/server/server.py +70 -239
- hyperpocket/session/in_memory.py +20 -26
- hyperpocket/tool/__init__.py +1 -2
- hyperpocket/tool/dock/dock.py +6 -25
- hyperpocket/tool/function/tool.py +4 -1
- hyperpocket/tool/tool.py +6 -35
- hyperpocket/tool_like.py +2 -3
- hyperpocket/util/git_parser.py +63 -0
- hyperpocket/util/short_hashing_str.py +5 -0
- {hyperpocket-0.4.5.dist-info → hyperpocket-0.5.1.dist-info}/METADATA +5 -5
- {hyperpocket-0.4.5.dist-info → hyperpocket-0.5.1.dist-info}/RECORD +19 -23
- hyperpocket/pocket_core.py +0 -283
- hyperpocket/repository/__init__.py +0 -4
- hyperpocket/repository/repository.py +0 -8
- hyperpocket/repository/tool_reference.py +0 -28
- hyperpocket/tool/tests/__init__.py +0 -0
- hyperpocket/util/generate_slug.py +0 -4
- {hyperpocket-0.4.5.dist-info → hyperpocket-0.5.1.dist-info}/WHEEL +0 -0
- {hyperpocket-0.4.5.dist-info → hyperpocket-0.5.1.dist-info}/entry_points.txt +0 -0
hyperpocket/server/server.py
CHANGED
@@ -1,101 +1,91 @@
|
|
1
1
|
import asyncio
|
2
|
-
import
|
3
|
-
import uuid
|
2
|
+
import threading
|
4
3
|
from typing import Optional
|
5
4
|
|
6
|
-
import multiprocess as mp
|
7
5
|
from fastapi import FastAPI
|
8
6
|
from uvicorn import Config, Server
|
9
7
|
|
10
8
|
from hyperpocket.config import config, pocket_logger
|
11
|
-
from hyperpocket.pocket_core import PocketCore
|
12
9
|
from hyperpocket.server.auth import auth_router
|
13
10
|
|
14
11
|
|
15
|
-
class PocketServerOperations(enum.Enum):
|
16
|
-
CALL = "call"
|
17
|
-
PREPARE_AUTH = "prepare_auth"
|
18
|
-
AUTHENTICATE = "authenticate"
|
19
|
-
TOOL_CALL = "tool_call"
|
20
|
-
PLUG_CORE = "plug_core"
|
21
|
-
|
22
|
-
|
23
12
|
class PocketServer(object):
|
13
|
+
_instance: "PocketServer" = None
|
14
|
+
|
24
15
|
fastapi_app: Optional[FastAPI]
|
25
16
|
main_server: Optional[Server]
|
26
17
|
internal_server_port: int
|
27
18
|
proxy_server: Optional[Server]
|
28
19
|
proxy_port: int
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
_cores: dict[str, PocketCore]
|
20
|
+
|
21
|
+
thread: threading.Thread
|
22
|
+
_initialized: bool
|
23
|
+
_initialization_event: threading.Event
|
24
|
+
_initialization_error: Optional[Exception]
|
35
25
|
|
36
26
|
def __init__(self):
|
37
27
|
self._initialized = False
|
28
|
+
self._initialization_event = threading.Event()
|
29
|
+
self._initialization_error = None
|
30
|
+
|
38
31
|
self.internal_server_port = config().internal_server_port
|
39
32
|
self.proxy_port = config().public_server_port
|
40
|
-
self._uidset = set()
|
41
|
-
self.future_store = dict()
|
42
33
|
self.fastapi_app = None
|
43
34
|
self.main_server = None
|
44
|
-
self._cores = dict()
|
45
35
|
|
46
|
-
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# check duplicated api route
|
53
|
-
dock_route = set([str(r) for r in dock.router.routes])
|
54
|
-
if dock_route in dock_routes:
|
55
|
-
continue
|
36
|
+
@classmethod
|
37
|
+
def get_instance(cls):
|
38
|
+
if cls._instance is None:
|
39
|
+
cls._instance = cls()
|
40
|
+
cls._instance.run()
|
41
|
+
return cls._instance
|
56
42
|
|
57
|
-
|
58
|
-
|
43
|
+
def teardown(self):
|
44
|
+
try:
|
45
|
+
loop = asyncio.get_event_loop()
|
46
|
+
except RuntimeError:
|
47
|
+
loop = asyncio.new_event_loop()
|
48
|
+
asyncio.set_event_loop(loop)
|
49
|
+
loop.run_until_complete(self._teardown())
|
50
|
+
loop.close()
|
51
|
+
|
52
|
+
async def _teardown(self):
|
53
|
+
if self.thread.is_alive():
|
54
|
+
if self.main_server:
|
55
|
+
self.main_server.should_exit = True
|
56
|
+
if self.proxy_server:
|
57
|
+
self.proxy_server.should_exit = True
|
58
|
+
|
59
|
+
while self.thread.is_alive():
|
60
|
+
await asyncio.sleep(0)
|
59
61
|
|
60
|
-
|
61
|
-
self._cores[pocket_uid] = pocket_core
|
62
|
+
self.thread.join()
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
pocket_uid,
|
68
|
-
tuple(),
|
69
|
-
{
|
70
|
-
"pocket_uid": pocket_uid,
|
71
|
-
"pocket_core": pocket_core,
|
72
|
-
},
|
64
|
+
def run(self):
|
65
|
+
self.thread = threading.Thread(
|
66
|
+
target=self._run,
|
67
|
+
daemon=True
|
73
68
|
)
|
69
|
+
self.thread.start()
|
70
|
+
self._wait_initialized()
|
74
71
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
72
|
+
def _run(self):
|
73
|
+
try:
|
74
|
+
# init process
|
75
|
+
self.fastapi_app = self._create_fastapi_app()
|
76
|
+
self.main_server = self._create_main_server(self.fastapi_app)
|
77
|
+
self.proxy_server = self._create_https_proxy_server()
|
78
|
+
self._report_initialized()
|
82
79
|
|
83
|
-
|
84
|
-
|
80
|
+
loop = asyncio.new_event_loop()
|
81
|
+
error = loop.run_until_complete(self._run_async())
|
82
|
+
loop.close()
|
85
83
|
|
86
|
-
|
87
|
-
|
88
|
-
self._uidset.remove(uid)
|
89
|
-
if len(self._uidset) == 0:
|
90
|
-
self._instance.teardown()
|
84
|
+
if error:
|
85
|
+
raise error
|
91
86
|
|
92
|
-
|
93
|
-
|
94
|
-
if self.torn_down:
|
95
|
-
return
|
96
|
-
self.torn_down = True
|
97
|
-
self.process.terminate()
|
98
|
-
self.process.join()
|
87
|
+
except Exception as error:
|
88
|
+
self._report_initialized(error)
|
99
89
|
|
100
90
|
async def _run_async(self):
|
101
91
|
try:
|
@@ -104,175 +94,34 @@ class PocketServer(object):
|
|
104
94
|
self.proxy_server.serve()
|
105
95
|
if self.proxy_server is not None
|
106
96
|
else asyncio.sleep(0),
|
107
|
-
|
97
|
+
return_exceptions=True
|
108
98
|
)
|
99
|
+
return None
|
109
100
|
except Exception as e:
|
110
101
|
pocket_logger.warning(f"failed to start pocket server. error : {e}")
|
111
|
-
|
112
|
-
async def poll_in_child(self):
|
113
|
-
loop = asyncio.get_running_loop()
|
114
|
-
_, conn = self.pipe
|
115
|
-
|
116
|
-
async def _acall(_conn, _future_uid, _core_uid, _args, _kwargs):
|
117
|
-
try:
|
118
|
-
core = self._cores[_core_uid]
|
119
|
-
result = await core.acall(*_args, **_kwargs)
|
120
|
-
error = None
|
121
|
-
except Exception as e:
|
122
|
-
pocket_logger.error(f"failed in pocket subprocess. error: {e}")
|
123
|
-
result = None
|
124
|
-
error = e
|
125
|
-
_conn.send((_future_uid, result, error))
|
126
|
-
|
127
|
-
async def _prepare(_conn, _future_uid, _core_uid, a, kw):
|
128
|
-
try:
|
129
|
-
core = self._cores[_core_uid]
|
130
|
-
result = core.prepare_auth(*a, **kw)
|
131
|
-
error = None
|
132
|
-
except Exception as e:
|
133
|
-
pocket_logger.error(
|
134
|
-
f"failed to prepare in pocket subprocess. error: {e}"
|
135
|
-
)
|
136
|
-
result = None
|
137
|
-
error = e
|
138
|
-
|
139
|
-
_conn.send((_future_uid, result, error))
|
140
|
-
|
141
|
-
async def _authenticate(_conn, _future_uid, _core_uid, a, kw):
|
142
|
-
try:
|
143
|
-
core = self._cores[_core_uid]
|
144
|
-
result = await core.authenticate(*a, **kw)
|
145
|
-
error = None
|
146
|
-
except Exception as e:
|
147
|
-
pocket_logger.error(
|
148
|
-
f"failed to authenticate in pocket subprocess. error: {e}"
|
149
|
-
)
|
150
|
-
result = None
|
151
|
-
error = e
|
152
|
-
|
153
|
-
_conn.send((_future_uid, result, error))
|
154
|
-
|
155
|
-
async def _tool_call(_conn, _future_uid, _core_uid, a, kw):
|
156
|
-
try:
|
157
|
-
core = self._cores[_core_uid]
|
158
|
-
result = await core.tool_call(*a, **kw)
|
159
|
-
error = None
|
160
|
-
except Exception as e:
|
161
|
-
pocket_logger.error(
|
162
|
-
f"failed to tool_call in pocket subprocess. error: {e}"
|
163
|
-
)
|
164
|
-
result = None
|
165
|
-
error = e
|
166
|
-
|
167
|
-
_conn.send((_future_uid, result, error))
|
168
|
-
|
169
|
-
async def _plug_core(_conn, _future_uid, _core_uid, a, kw):
|
170
|
-
try:
|
171
|
-
self._plug_core(*a, **kw)
|
172
|
-
_conn.send((_future_uid, None, None))
|
173
|
-
except Exception as e:
|
174
|
-
pocket_logger.error(
|
175
|
-
f"failed to plug_core in pocket subprocess. error: {e}"
|
176
|
-
)
|
177
|
-
_conn.send((_future_uid, None, e))
|
178
|
-
|
179
|
-
while True:
|
180
|
-
if conn.poll():
|
181
|
-
op, future_uid, core_uid, args, kwargs = conn.recv()
|
182
|
-
if op == PocketServerOperations.CALL:
|
183
|
-
loop.create_task(_acall(conn, future_uid, core_uid, args, kwargs))
|
184
|
-
elif op == PocketServerOperations.PREPARE_AUTH:
|
185
|
-
loop.create_task(_prepare(conn, future_uid, core_uid, args, kwargs))
|
186
|
-
elif op == PocketServerOperations.AUTHENTICATE:
|
187
|
-
loop.create_task(_authenticate(conn, future_uid, core_uid, args, kwargs))
|
188
|
-
elif op == PocketServerOperations.TOOL_CALL:
|
189
|
-
loop.create_task(_tool_call(conn, future_uid, core_uid, args, kwargs))
|
190
|
-
elif op == PocketServerOperations.PLUG_CORE:
|
191
|
-
loop.create_task(_plug_core(conn, future_uid, core_uid, args, kwargs))
|
192
|
-
else:
|
193
|
-
raise ValueError(f"Unknown operation: {op}")
|
194
|
-
else:
|
195
|
-
await asyncio.sleep(0)
|
196
|
-
|
197
|
-
def send_in_parent(self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict):
|
198
|
-
conn, _ = self.pipe
|
199
|
-
future_uid = str(uuid.uuid4())
|
200
|
-
message = (op, future_uid, pocket_uid, args, kwargs)
|
201
|
-
future = asyncio.Future()
|
202
|
-
self.future_store[future_uid] = future
|
203
|
-
conn.send(message)
|
204
|
-
return future_uid
|
205
|
-
|
206
|
-
async def poll_in_parent(self):
|
207
|
-
conn, _ = self.pipe
|
208
|
-
while True:
|
209
|
-
if conn.poll():
|
210
|
-
uid, result, error = conn.recv()
|
211
|
-
future = self.future_store[uid]
|
212
|
-
if error:
|
213
|
-
future.set_exception(error)
|
214
|
-
else:
|
215
|
-
future.set_result(result)
|
216
|
-
break
|
217
|
-
else:
|
218
|
-
await asyncio.sleep(0)
|
219
|
-
|
220
|
-
async def call_in_subprocess(
|
221
|
-
self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict
|
222
|
-
):
|
223
|
-
uid = self.send_in_parent(op, pocket_uid, args, kwargs)
|
224
|
-
loop = asyncio.get_running_loop()
|
225
|
-
loop.create_task(self.poll_in_parent())
|
226
|
-
return await self.future_store[uid]
|
227
|
-
|
228
|
-
def run(self):
|
229
|
-
self._set_mp_start_method()
|
230
|
-
|
231
|
-
error_queue = mp.Queue()
|
232
|
-
self.pipe = mp.Pipe()
|
233
|
-
self.process = mp.Process(
|
234
|
-
target=self._run
|
235
|
-
)
|
236
|
-
self.process.start() # process start
|
237
|
-
|
238
|
-
if not error_queue.empty():
|
239
|
-
error_message = error_queue.get()
|
240
|
-
raise error_message
|
102
|
+
return e
|
241
103
|
|
242
104
|
def _report_initialized(self, error: Optional[Exception] = None):
|
243
|
-
|
244
|
-
|
105
|
+
if error:
|
106
|
+
pocket_logger.warning(f"Server initialization failed: {error}")
|
107
|
+
self._initialization_error = error
|
108
|
+
self._initialization_event.set()
|
245
109
|
|
246
|
-
def
|
110
|
+
def _wait_initialized(self):
|
247
111
|
if self._initialized:
|
248
112
|
return
|
249
|
-
conn, _ = self.pipe
|
250
|
-
while True:
|
251
|
-
if conn.poll():
|
252
|
-
_, error = conn.recv()
|
253
|
-
if error:
|
254
|
-
raise error
|
255
|
-
break
|
256
|
-
self._initialized = True
|
257
113
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
self.fastapi_app = self._create_fastapi_app()
|
262
|
-
self.main_server = self._create_main_server(self.fastapi_app)
|
263
|
-
self.proxy_server = self._create_https_proxy_server()
|
264
|
-
self._report_initialized()
|
114
|
+
self._initialization_event.wait()
|
115
|
+
if self._initialization_error:
|
116
|
+
raise self._initialization_error
|
265
117
|
|
266
|
-
|
267
|
-
|
268
|
-
loop.close()
|
269
|
-
except Exception as error:
|
270
|
-
self._report_initialized(error)
|
118
|
+
self._initialization_event.clear()
|
119
|
+
self._initialized = True
|
271
120
|
|
272
121
|
def _create_fastapi_app(self) -> FastAPI:
|
273
122
|
app = FastAPI()
|
274
|
-
app.include_router(auth_router)
|
275
123
|
app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
|
124
|
+
app.include_router(auth_router)
|
276
125
|
return app
|
277
126
|
|
278
127
|
def _create_main_server(self, app: FastAPI) -> Server:
|
@@ -307,21 +156,3 @@ class PocketServer(object):
|
|
307
156
|
)
|
308
157
|
proxy_server = Server(_config)
|
309
158
|
return proxy_server
|
310
|
-
|
311
|
-
def _set_mp_start_method(self):
|
312
|
-
import platform
|
313
|
-
|
314
|
-
os_name = platform.system()
|
315
|
-
if os_name == "Windows":
|
316
|
-
mp.set_start_method("spawn", force=True)
|
317
|
-
pocket_logger.debug("Process start method set to 'spawn' for Windows.")
|
318
|
-
elif os_name == "Darwin": # macOS
|
319
|
-
mp.set_start_method("spawn", force=True)
|
320
|
-
pocket_logger.debug("Process start method set to 'spawn' for macOS.")
|
321
|
-
elif os_name == "Linux":
|
322
|
-
mp.set_start_method("fork", force=True)
|
323
|
-
pocket_logger.debug("Process start method set to 'fork' for Linux.")
|
324
|
-
else:
|
325
|
-
pocket_logger.debug(
|
326
|
-
f"Unrecognized OS: {os_name}. Default start method will be used."
|
327
|
-
)
|
hyperpocket/session/in_memory.py
CHANGED
@@ -19,49 +19,42 @@ InMemorySessionValue = BaseSessionValue
|
|
19
19
|
class InMemorySessionStorage(
|
20
20
|
SessionStorageInterface[InMemorySessionKey, InMemorySessionValue]
|
21
21
|
):
|
22
|
-
|
23
|
-
_is_initialized = False
|
24
|
-
|
25
|
-
def __new__(cls, *args, **kwargs):
|
26
|
-
if cls._instance is None:
|
27
|
-
cls._instance = super(InMemorySessionStorage, cls).__new__(cls)
|
28
|
-
cls._instance._is_initialized = False
|
29
|
-
return cls._instance
|
22
|
+
storage = {}
|
30
23
|
|
31
24
|
def __init__(self, session_config: SessionConfigInMemory):
|
32
|
-
|
33
|
-
super().__init__()
|
34
|
-
self.storage: Dict[InMemorySessionKey, InMemorySessionValue] = {}
|
35
|
-
self._is_initialized = True
|
25
|
+
super().__init__()
|
36
26
|
|
37
27
|
@classmethod
|
38
28
|
def session_storage_type(cls) -> SessionType:
|
39
29
|
return SessionType.IN_MEMORY
|
40
30
|
|
31
|
+
@classmethod
|
41
32
|
def get(
|
42
|
-
|
33
|
+
cls, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs
|
43
34
|
) -> Optional[V]:
|
44
|
-
key =
|
45
|
-
return
|
35
|
+
key = cls._make_session_key(auth_provider.name, thread_id, profile)
|
36
|
+
return cls.storage.get(key, None)
|
46
37
|
|
38
|
+
@classmethod
|
47
39
|
def get_by_thread_id(
|
48
|
-
|
40
|
+
cls, thread_id: str, auth_provider: Optional[AuthProvider] = None, **kwargs
|
49
41
|
) -> List[V]:
|
50
42
|
if auth_provider is None:
|
51
43
|
auth_provider_name = ".*"
|
52
44
|
else:
|
53
45
|
auth_provider_name = auth_provider.name
|
54
46
|
|
55
|
-
pattern = rf"{
|
47
|
+
pattern = rf"{cls._make_session_key(auth_provider_name, thread_id, '.*')}"
|
56
48
|
compiled = re.compile(pattern)
|
57
49
|
|
58
50
|
session_list = [
|
59
|
-
value for key, value in
|
51
|
+
value for key, value in cls.storage.items() if compiled.match(key)
|
60
52
|
]
|
61
53
|
return session_list
|
62
54
|
|
55
|
+
@classmethod
|
63
56
|
def set(
|
64
|
-
|
57
|
+
cls,
|
65
58
|
auth_provider: AuthProvider,
|
66
59
|
thread_id: str,
|
67
60
|
profile: str,
|
@@ -71,8 +64,8 @@ class InMemorySessionStorage(
|
|
71
64
|
is_auth_scope_universal: bool,
|
72
65
|
**kwargs,
|
73
66
|
) -> V:
|
74
|
-
key =
|
75
|
-
session =
|
67
|
+
key = cls._make_session_key(auth_provider.name, thread_id, profile)
|
68
|
+
session = cls._make_session(
|
76
69
|
auth_provider_name=auth_provider.name,
|
77
70
|
auth_scopes=auth_scopes,
|
78
71
|
auth_resolve_uid=auth_resolve_uid,
|
@@ -80,15 +73,16 @@ class InMemorySessionStorage(
|
|
80
73
|
is_auth_scope_universal=is_auth_scope_universal,
|
81
74
|
)
|
82
75
|
|
83
|
-
|
76
|
+
cls.storage[key] = session
|
84
77
|
return session
|
85
78
|
|
79
|
+
@classmethod
|
86
80
|
def delete(
|
87
|
-
|
81
|
+
cls, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs
|
88
82
|
) -> bool:
|
89
|
-
key =
|
90
|
-
if key in
|
91
|
-
|
83
|
+
key = cls._make_session_key(auth_provider.name, thread_id, profile)
|
84
|
+
if key in cls.storage:
|
85
|
+
cls.storage.pop(key)
|
92
86
|
return True
|
93
87
|
|
94
88
|
return False
|
hyperpocket/tool/__init__.py
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
from hyperpocket.tool.function import from_dock, from_func, function_tool
|
2
|
-
from hyperpocket.tool.tool import Tool, ToolAuth
|
2
|
+
from hyperpocket.tool.tool import Tool, ToolAuth
|
3
3
|
|
4
4
|
__all__ = [
|
5
5
|
"Tool",
|
6
6
|
"ToolAuth",
|
7
|
-
"ToolRequest",
|
8
7
|
"from_dock",
|
9
8
|
"from_func",
|
10
9
|
"function_tool",
|
hyperpocket/tool/dock/dock.py
CHANGED
@@ -1,34 +1,15 @@
|
|
1
1
|
import abc
|
2
|
-
from typing import
|
2
|
+
from typing import TypeVar, Generic
|
3
3
|
|
4
|
-
from fastapi import APIRouter
|
5
|
-
|
6
|
-
from hyperpocket.tool import ToolRequest
|
7
4
|
from hyperpocket.tool.function import FunctionTool
|
8
5
|
|
6
|
+
DockToolLike = TypeVar("DockToolLike")
|
9
7
|
|
10
|
-
class Dock(abc.ABC):
|
11
|
-
_tool_requests: list[ToolRequest]
|
12
|
-
_dock_http_router: APIRouter
|
13
|
-
_dock_vars: dict[str, str]
|
14
|
-
|
15
|
-
def __init__(self, dock_vars: dict[str, str] = None):
|
16
|
-
self._dock_http_router = APIRouter()
|
17
|
-
self._tool_requests = []
|
18
|
-
self._dock_vars = dock_vars if dock_vars is not None else {}
|
19
|
-
|
20
|
-
@property
|
21
|
-
def router(self):
|
22
|
-
return self._dock_http_router
|
23
|
-
|
24
|
-
@abc.abstractmethod
|
25
|
-
def plug(self, req_like: Any, **kwargs):
|
26
|
-
raise NotImplementedError
|
27
8
|
|
9
|
+
class Dock(Generic[DockToolLike], abc.ABC):
|
28
10
|
@abc.abstractmethod
|
29
|
-
def
|
11
|
+
def dock(self, tool_like: DockToolLike, *args, **kwargs) -> FunctionTool:
|
30
12
|
raise NotImplementedError
|
31
13
|
|
32
|
-
|
33
|
-
|
34
|
-
raise NotImplementedError
|
14
|
+
def __call__(self, *args, **kwargs):
|
15
|
+
return self.dock(*args, **kwargs)
|
@@ -121,6 +121,9 @@ class FunctionTool(Tool):
|
|
121
121
|
if tool_vars is None:
|
122
122
|
tool_vars = dict()
|
123
123
|
|
124
|
+
if afunc is None and inspect.iscoroutinefunction(func):
|
125
|
+
afunc = func
|
126
|
+
|
124
127
|
if isinstance(func, FunctionTool):
|
125
128
|
if tool_vars is not None:
|
126
129
|
func.override_tool_variables(tool_vars)
|
@@ -133,7 +136,7 @@ class FunctionTool(Tool):
|
|
133
136
|
raise ValueError("FunctionTool can only be created from a callable")
|
134
137
|
|
135
138
|
name = name or (func and func.__name__) or (afunc and afunc.__name__)
|
136
|
-
description = description or (func and func.__doc__) or (afunc and func.__doc__)
|
139
|
+
description = description or (func and func.__doc__) or (afunc and func.__doc__) or name
|
137
140
|
schema = json_schema or \
|
138
141
|
(func and function_to_model(func).model_json_schema()) or \
|
139
142
|
(afunc and function_to_model(afunc).model_json_schema())
|
hyperpocket/tool/tool.py
CHANGED
@@ -17,52 +17,20 @@ class ToolAuth(BaseModel):
|
|
17
17
|
scopes: list[str] = Field(
|
18
18
|
default=None,
|
19
19
|
description="Indicates which authentication provider’s credentials are required to invoke the tool. "
|
20
|
-
|
20
|
+
"If auth_provider is not specified, the tool is considered to require no authentication.",
|
21
21
|
)
|
22
22
|
auth_provider: Optional[AuthProvider] = Field(
|
23
23
|
default=None,
|
24
24
|
description="Specifies which authentication handler should be used when invoking the tool. "
|
25
|
-
|
25
|
+
"If auth_handler is not specified, the default handler of the authentication provider will be used.",
|
26
26
|
)
|
27
27
|
auth_handler: Optional[str] = Field(
|
28
28
|
default=None,
|
29
29
|
description="Indicates the authentication scopes required to invoke the tool. "
|
30
|
-
|
30
|
+
"If authentication is not performed or the authentication handler is non-scoped, the value should be None.",
|
31
31
|
)
|
32
32
|
|
33
33
|
|
34
|
-
class ToolRequest(abc.ABC):
|
35
|
-
postprocessings: Optional[list[Callable]] = None
|
36
|
-
overridden_tool_vars: dict[str, str] = Field(
|
37
|
-
default_factory=dict, description="overridden tool variables"
|
38
|
-
)
|
39
|
-
|
40
|
-
@abc.abstractmethod
|
41
|
-
def __str__(self):
|
42
|
-
raise NotImplementedError
|
43
|
-
|
44
|
-
def add_postprocessing(self, postprocessing: Callable):
|
45
|
-
if self.postprocessings is None:
|
46
|
-
self.postprocessings = [postprocessing]
|
47
|
-
else:
|
48
|
-
self.postprocessings.append(postprocessing)
|
49
|
-
|
50
|
-
def __or__(self, other: Callable):
|
51
|
-
self.add_postprocessing(other)
|
52
|
-
return self
|
53
|
-
|
54
|
-
def with_postprocessings(self, postprocessings: list[Callable]):
|
55
|
-
if self.postprocessings is None:
|
56
|
-
self.postprocessings = postprocessings
|
57
|
-
else:
|
58
|
-
self.postprocessings.extend(postprocessings)
|
59
|
-
return self
|
60
|
-
|
61
|
-
def override_tool_variables(self, override_vars: dict[str, str]) -> "ToolRequest":
|
62
|
-
self.overridden_tool_vars = override_vars
|
63
|
-
return self
|
64
|
-
|
65
|
-
|
66
34
|
class Tool(BaseModel, abc.ABC):
|
67
35
|
"""
|
68
36
|
Pocket Tool Interface
|
@@ -191,3 +159,6 @@ class Tool(BaseModel, abc.ABC):
|
|
191
159
|
else:
|
192
160
|
self.postprocessings.extend(postprocessings)
|
193
161
|
return self
|
162
|
+
|
163
|
+
def __str__(self) -> str:
|
164
|
+
return self.name
|
hyperpocket/tool_like.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from typing import Callable, Union
|
2
2
|
|
3
|
-
from hyperpocket.tool import Tool
|
4
|
-
from hyperpocket.tool.dock import Dock
|
3
|
+
from hyperpocket.tool import Tool
|
5
4
|
|
6
|
-
ToolLike = Union[Tool, str, tuple, Callable
|
5
|
+
ToolLike = Union[Tool, str, tuple, Callable]
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import git
|
2
|
+
|
3
|
+
|
4
|
+
class GitParser:
|
5
|
+
git_branches_cache: dict[str, dict[str, str]] = {}
|
6
|
+
|
7
|
+
@classmethod
|
8
|
+
def get_git_branches(cls, repo_url) -> dict[str, str]:
|
9
|
+
if cls.git_branches_cache.get(repo_url) is None:
|
10
|
+
ls_lists = git.cmd.Git().ls_remote(repo_url)
|
11
|
+
|
12
|
+
branches = {}
|
13
|
+
for line in ls_lists.split("\n"):
|
14
|
+
sha, ref = line.split("\t")
|
15
|
+
if ref == "HEAD":
|
16
|
+
branches["HEAD"] = sha
|
17
|
+
elif ref.startswith("refs/heads/"):
|
18
|
+
branch_name = ref.replace("refs/heads/", "")
|
19
|
+
branches[branch_name] = sha
|
20
|
+
cls.git_branches_cache[repo_url] = branches
|
21
|
+
return cls.git_branches_cache[repo_url]
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
def parse_repo_url(cls, repo_url: str) -> tuple[str, str, str, str]:
|
25
|
+
"""
|
26
|
+
Parses a GitHub repository URL with optional branch and path information.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
Tuple[str, str, str, str]: base_repo, branch_name, directory_path, git_sha
|
30
|
+
"""
|
31
|
+
if not repo_url.startswith("https://github.com/"):
|
32
|
+
raise AttributeError("Only GitHub URLs are supported")
|
33
|
+
|
34
|
+
# Remove the base URL and split the path
|
35
|
+
repo_path = repo_url.removeprefix("https://github.com/")
|
36
|
+
repo_path_list = repo_path.split("/")
|
37
|
+
|
38
|
+
# Check if the URL contains 'tree' (indicating branch and sub-path information)
|
39
|
+
if "tree" not in repo_path_list:
|
40
|
+
# If no 'tree', return the full repository URL
|
41
|
+
git_sha = cls.get_git_branches(repo_url)["HEAD"]
|
42
|
+
return repo_url, "HEAD", "", git_sha
|
43
|
+
|
44
|
+
# Parse base repo URL and remaining path
|
45
|
+
tree_index = repo_path_list.index("tree")
|
46
|
+
base_repo = f"https://github.com/{'/'.join(repo_path_list[:tree_index])}"
|
47
|
+
sub_path = repo_path_list[tree_index + 1:]
|
48
|
+
|
49
|
+
# Fetch branch information
|
50
|
+
branches = cls.get_git_branches(base_repo)
|
51
|
+
|
52
|
+
# Find branch and sub-directory path
|
53
|
+
for idx in range(1, len(sub_path) + 1):
|
54
|
+
branch_name = "/".join(sub_path[:idx])
|
55
|
+
if branch_name in branches:
|
56
|
+
git_sha = branches[branch_name]
|
57
|
+
directory_path = (
|
58
|
+
"/".join(sub_path[idx:]) if idx < len(sub_path) else None
|
59
|
+
)
|
60
|
+
return base_repo, branch_name, directory_path, git_sha
|
61
|
+
|
62
|
+
# If no valid branch is found, raise an error
|
63
|
+
raise ValueError("Branch not found in repository")
|