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/builtin.py
CHANGED
@@ -12,7 +12,7 @@ def get_builtin_tools(pocket_auth: PocketAuth) -> List[Tool]:
|
|
12
12
|
Builtin Tool can access to Pocket Core.
|
13
13
|
"""
|
14
14
|
|
15
|
-
def __get_current_thread_session_state(thread_id: str = "default") -> str:
|
15
|
+
async def __get_current_thread_session_state(thread_id: str = "default") -> str:
|
16
16
|
"""
|
17
17
|
This tool retrieves the current session state list for the specified thread.
|
18
18
|
|
@@ -31,7 +31,7 @@ def get_builtin_tools(pocket_auth: PocketAuth) -> List[Tool]:
|
|
31
31
|
|
32
32
|
This tool ensures transparency about the current session but must respect user-driven intent and should never be called automatically or without a specific user request.
|
33
33
|
"""
|
34
|
-
session_list = pocket_auth.list_session_state(thread_id)
|
34
|
+
session_list = await pocket_auth.list_session_state(thread_id)
|
35
35
|
return str(session_list)
|
36
36
|
|
37
37
|
def __delete_session(
|
@@ -58,7 +58,7 @@ def get_builtin_tools(pocket_auth: PocketAuth) -> List[Tool]:
|
|
58
58
|
return str(is_deleted)
|
59
59
|
|
60
60
|
builtin_tools = [
|
61
|
-
from_func(func=__get_current_thread_session_state),
|
61
|
+
from_func(func=__get_current_thread_session_state, afunc=__get_current_thread_session_state),
|
62
62
|
from_func(func=__delete_session),
|
63
63
|
]
|
64
64
|
|
hyperpocket/cli/__main__.py
CHANGED
@@ -2,7 +2,6 @@ import click
|
|
2
2
|
|
3
3
|
from hyperpocket.cli.eject import eject
|
4
4
|
from hyperpocket.cli.pull import pull
|
5
|
-
from hyperpocket.cli.sync import sync
|
6
5
|
from hyperpocket.cli.eject import eject
|
7
6
|
from hyperpocket.cli.auth_token import create_token_auth_template
|
8
7
|
from hyperpocket.cli.auth_oauth2 import create_oauth2_auth_template
|
@@ -28,7 +27,6 @@ devtool.add_command(build_tool)
|
|
28
27
|
devtool.add_command(export_tool)
|
29
28
|
|
30
29
|
cli.add_command(pull)
|
31
|
-
cli.add_command(sync)
|
32
30
|
cli.add_command(eject)
|
33
31
|
|
34
32
|
cli()
|
hyperpocket/config/logger.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
|
+
import pathlib
|
3
4
|
from logging.handlers import RotatingFileHandler
|
4
5
|
from pathlib import Path
|
5
6
|
|
@@ -27,9 +28,7 @@ class ColorFormatter(logging.Formatter):
|
|
27
28
|
|
28
29
|
|
29
30
|
def get_logger():
|
30
|
-
|
31
|
-
package_path = Path(os.path.dirname(hyperpocket.__file__))
|
32
|
-
log_dir = package_path / ".log"
|
31
|
+
log_dir = pathlib.Path(os.getcwd()) / ".log"
|
33
32
|
os.makedirs(log_dir, exist_ok=True)
|
34
33
|
log_file = log_dir / "pocket.log"
|
35
34
|
if not log_file.exists():
|
@@ -25,7 +25,6 @@ class FutureStore(object):
|
|
25
25
|
f"the future already exists. the existing future is returned. uid: {uid}"
|
26
26
|
)
|
27
27
|
return future
|
28
|
-
|
29
28
|
loop = asyncio.get_running_loop()
|
30
29
|
future = loop.create_future()
|
31
30
|
future_data = FutureData(future=future, data=data)
|
@@ -42,7 +41,13 @@ class FutureStore(object):
|
|
42
41
|
if not future_data:
|
43
42
|
raise ValueError(f"Future not found for uid={uid}")
|
44
43
|
if not future_data.future.done():
|
45
|
-
|
44
|
+
# if the future loop is running, it should be executed in same event loop
|
45
|
+
loop = future_data.future.get_loop()
|
46
|
+
if loop.is_running():
|
47
|
+
loop.call_soon_threadsafe(future_data.future.set_result, value)
|
48
|
+
# if the future loop is not running, it can be executed from anywhere.
|
49
|
+
else:
|
50
|
+
future_data.future.set_result(value)
|
46
51
|
|
47
52
|
def delete_future(self, uid: str):
|
48
53
|
self.futures.pop(uid, None)
|
hyperpocket/pocket_auth.py
CHANGED
@@ -78,7 +78,7 @@ class PocketAuth(object):
|
|
78
78
|
handler = self.find_handler_instance(auth_handler_name, auth_provider)
|
79
79
|
return handler.make_request(auth_scopes, **kwargs)
|
80
80
|
|
81
|
-
def check(
|
81
|
+
async def check(
|
82
82
|
self,
|
83
83
|
auth_req: AuthenticateRequest,
|
84
84
|
auth_handler_name: Optional[str] = None,
|
@@ -112,12 +112,12 @@ class PocketAuth(object):
|
|
112
112
|
"""
|
113
113
|
handler = self.find_handler_instance(auth_handler_name, auth_provider)
|
114
114
|
session = self.session_storage.get(handler.provider(), thread_id, profile)
|
115
|
-
auth_state = self.get_session_state(session=session, auth_req=auth_req)
|
115
|
+
auth_state = await self.get_session_state(session=session, auth_req=auth_req)
|
116
116
|
|
117
117
|
return auth_state
|
118
118
|
|
119
119
|
@staticmethod
|
120
|
-
def get_session_state(
|
120
|
+
async def get_session_state(
|
121
121
|
session: Optional[BaseSessionValue], auth_req: Optional[AuthenticateRequest]
|
122
122
|
) -> AuthState:
|
123
123
|
if not session:
|
@@ -125,6 +125,8 @@ class PocketAuth(object):
|
|
125
125
|
|
126
126
|
if session.auth_resolve_uid:
|
127
127
|
future_data = FutureStore.get_future(session.auth_resolve_uid)
|
128
|
+
# it yields before checking future's state, because the future is being resolved on another thread's event loop.
|
129
|
+
await asyncio.sleep(0)
|
128
130
|
if future_data is not None and future_data.future.done():
|
129
131
|
return AuthState.RESOLVED
|
130
132
|
|
@@ -140,7 +142,7 @@ class PocketAuth(object):
|
|
140
142
|
|
141
143
|
return AuthState.SKIP_AUTH
|
142
144
|
|
143
|
-
def prepare(
|
145
|
+
async def prepare(
|
144
146
|
self,
|
145
147
|
auth_req: AuthenticateRequest,
|
146
148
|
auth_handler_name: Optional[str] = None,
|
@@ -166,7 +168,7 @@ class PocketAuth(object):
|
|
166
168
|
Returns:
|
167
169
|
Optional[str]: authentication URL
|
168
170
|
"""
|
169
|
-
auth_state = self.check(
|
171
|
+
auth_state = await self.check(
|
170
172
|
auth_req=auth_req,
|
171
173
|
auth_handler_name=auth_handler_name,
|
172
174
|
auth_provider=auth_provider,
|
@@ -263,7 +265,7 @@ class PocketAuth(object):
|
|
263
265
|
Returns:
|
264
266
|
AuthContext: authentication context
|
265
267
|
"""
|
266
|
-
auth_state = self.check(
|
268
|
+
auth_state = await self.check(
|
267
269
|
auth_req=auth_req,
|
268
270
|
auth_handler_name=auth_handler_name,
|
269
271
|
auth_provider=auth_provider,
|
@@ -353,7 +355,7 @@ class PocketAuth(object):
|
|
353
355
|
|
354
356
|
return session.auth_context
|
355
357
|
|
356
|
-
def list_session_state(
|
358
|
+
async def list_session_state(
|
357
359
|
self, thread_id: str, auth_provider: Optional[AuthProvider] = None
|
358
360
|
):
|
359
361
|
session_list = self.session_storage.get_by_thread_id(
|
@@ -361,7 +363,7 @@ class PocketAuth(object):
|
|
361
363
|
)
|
362
364
|
session_state_list = []
|
363
365
|
for session in session_list:
|
364
|
-
state = self.get_session_state(session=session, auth_req=None)
|
366
|
+
state = await self.get_session_state(session=session, auth_req=None)
|
365
367
|
|
366
368
|
session_state_list.append(
|
367
369
|
{
|
hyperpocket/pocket_main.py
CHANGED
@@ -1,18 +1,34 @@
|
|
1
1
|
import asyncio
|
2
|
-
import
|
3
|
-
from
|
2
|
+
import concurrent.futures
|
3
|
+
from threading import Lock
|
4
|
+
from typing import Any, List, Union, Callable, Optional
|
4
5
|
|
6
|
+
from hyperpocket.builtin import get_builtin_tools
|
5
7
|
from hyperpocket.config import pocket_logger
|
6
8
|
from hyperpocket.pocket_auth import PocketAuth
|
7
|
-
from hyperpocket.
|
8
|
-
from hyperpocket.
|
9
|
+
from hyperpocket.server.server import PocketServer
|
10
|
+
from hyperpocket.tool import Tool, from_func
|
11
|
+
from hyperpocket.tool.dock import Dock
|
9
12
|
from hyperpocket.tool_like import ToolLike
|
10
13
|
|
11
14
|
|
12
15
|
class Pocket(object):
|
13
16
|
server: PocketServer
|
14
|
-
|
15
|
-
|
17
|
+
auth: PocketAuth
|
18
|
+
tools: dict[str, Tool]
|
19
|
+
|
20
|
+
_cnt_pocket_count: int = 0
|
21
|
+
_pocket_count_lock = Lock()
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def _default_dock() -> Dock:
|
25
|
+
try:
|
26
|
+
from hyperdock_container.dock import ContainerDock
|
27
|
+
pocket_logger.info("hyperdock-container is loaded.")
|
28
|
+
return ContainerDock()
|
29
|
+
except ImportError as e:
|
30
|
+
pocket_logger.warning("Failed to import hyperdock_container.")
|
31
|
+
raise e
|
16
32
|
|
17
33
|
def __init__(
|
18
34
|
self,
|
@@ -21,29 +37,31 @@ class Pocket(object):
|
|
21
37
|
use_profile: bool = False,
|
22
38
|
):
|
23
39
|
try:
|
24
|
-
|
40
|
+
if auth is None:
|
41
|
+
auth = PocketAuth()
|
42
|
+
self.auth = auth
|
25
43
|
self.use_profile = use_profile
|
26
|
-
self.server = PocketServer.
|
27
|
-
|
28
|
-
self.
|
29
|
-
|
30
|
-
|
44
|
+
self.server = PocketServer.get_instance()
|
45
|
+
|
46
|
+
self._load_tools(tools)
|
47
|
+
pocket_logger.info(
|
48
|
+
f"All Registered Tools Loaded successfully. total registered tools : {len(self.tools)}"
|
49
|
+
)
|
50
|
+
|
51
|
+
# load builtin tool
|
52
|
+
builtin_tools = get_builtin_tools(self.auth)
|
53
|
+
for tool in builtin_tools:
|
54
|
+
self.tools[tool.name] = tool
|
55
|
+
pocket_logger.info(
|
56
|
+
f"All BuiltIn Tools Loaded successfully. total tools : {len(self.tools)}"
|
31
57
|
)
|
58
|
+
|
59
|
+
with Pocket._pocket_count_lock:
|
60
|
+
Pocket._cnt_pocket_count += 1
|
32
61
|
except Exception as e:
|
33
62
|
self.teardown()
|
34
63
|
pocket_logger.error(f"Failed to initialize pocket server. error : {e}")
|
35
|
-
# self._teardown_server()
|
36
64
|
raise e
|
37
|
-
|
38
|
-
try:
|
39
|
-
asyncio.get_running_loop()
|
40
|
-
except RuntimeError:
|
41
|
-
loop = asyncio.new_event_loop()
|
42
|
-
else:
|
43
|
-
import nest_asyncio
|
44
|
-
loop = asyncio.new_event_loop()
|
45
|
-
nest_asyncio.apply(loop=loop)
|
46
|
-
loop.run_until_complete(self.server.plug_core(self._uid, self.core))
|
47
65
|
|
48
66
|
def invoke(
|
49
67
|
self,
|
@@ -176,17 +194,12 @@ class Pocket(object):
|
|
176
194
|
**kwargs,
|
177
195
|
}
|
178
196
|
|
179
|
-
result, paused = await self.
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
"body": body,
|
186
|
-
"thread_id": thread_id,
|
187
|
-
"profile": profile,
|
188
|
-
**kwargs,
|
189
|
-
},
|
197
|
+
result, paused = await self.acall(
|
198
|
+
tool_name=tool_name,
|
199
|
+
body=body,
|
200
|
+
thread_id=thread_id,
|
201
|
+
profile=profile,
|
202
|
+
**kwargs
|
190
203
|
)
|
191
204
|
if not isinstance(result, str):
|
192
205
|
result = str(result)
|
@@ -213,15 +226,16 @@ class Pocket(object):
|
|
213
226
|
Returns:
|
214
227
|
List[str]: A list of authentication URIs for the tools that require authentication.
|
215
228
|
"""
|
216
|
-
tool_by_provider = self.
|
229
|
+
tool_by_provider = self.grouping_tool_by_auth_provider()
|
217
230
|
|
218
231
|
prepare_list = {}
|
219
232
|
for provider, tools in tool_by_provider.items():
|
220
233
|
tool_name_list = [tool.name for tool in tools]
|
221
|
-
prepare = await self.
|
222
|
-
tool_name=tool_name_list,
|
234
|
+
prepare = await self.prepare_auth(
|
235
|
+
tool_name=tool_name_list,
|
236
|
+
thread_id=thread_id,
|
237
|
+
profile=profile,
|
223
238
|
)
|
224
|
-
|
225
239
|
if prepare is not None:
|
226
240
|
prepare_list[provider] = prepare
|
227
241
|
|
@@ -245,16 +259,17 @@ class Pocket(object):
|
|
245
259
|
or `False` if the process was interrupted or failed.
|
246
260
|
"""
|
247
261
|
try:
|
248
|
-
tool_by_provider = self.
|
262
|
+
tool_by_provider = self.grouping_tool_by_auth_provider()
|
249
263
|
|
250
264
|
waiting_futures = []
|
251
265
|
for provider, tools in tool_by_provider.items():
|
252
266
|
if len(tools) == 0:
|
253
267
|
continue
|
254
|
-
|
255
268
|
waiting_futures.append(
|
256
|
-
self.
|
257
|
-
tool_name=tools[0].name,
|
269
|
+
self.authenticate(
|
270
|
+
tool_name=tools[0].name,
|
271
|
+
thread_id=thread_id,
|
272
|
+
profile=profile,
|
258
273
|
)
|
259
274
|
)
|
260
275
|
|
@@ -266,51 +281,150 @@ class Pocket(object):
|
|
266
281
|
pocket_logger.error("authentication time out.")
|
267
282
|
raise e
|
268
283
|
|
269
|
-
async def
|
284
|
+
async def acall(
|
270
285
|
self,
|
271
|
-
tool_name:
|
286
|
+
tool_name: str,
|
287
|
+
body: Any,
|
272
288
|
thread_id: str = "default",
|
273
289
|
profile: str = "default",
|
274
290
|
*args,
|
275
291
|
**kwargs,
|
276
|
-
):
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
292
|
+
) -> tuple[str, bool]:
|
293
|
+
"""
|
294
|
+
Invoke tool asynchronously, not that different from `Pocket.invoke`
|
295
|
+
But this method is called only in subprocess.
|
296
|
+
|
297
|
+
This function performs the following steps:
|
298
|
+
1. `prepare_auth` : preparing the authentication process for the tool if necessary.
|
299
|
+
2. `authenticate` : performing authentication that needs to invoke tool.
|
300
|
+
3. `tool_call` : Executing tool actually with authentication information.
|
301
|
+
|
302
|
+
Args:
|
303
|
+
tool_name(str): tool name to invoke
|
304
|
+
body(Any): tool arguments. should be json format
|
305
|
+
thread_id(str): thread id
|
306
|
+
profile(str): profile name
|
307
|
+
|
308
|
+
Returns:
|
309
|
+
tuple[str, bool]: tool result and state.
|
310
|
+
"""
|
311
|
+
pocket_logger.debug(f"{tool_name} tool call. body: {body}")
|
312
|
+
tool = self._tool_instance(tool_name)
|
313
|
+
if tool.auth is not None:
|
314
|
+
callback_info = await self.prepare_auth(tool_name, thread_id, profile, **kwargs)
|
315
|
+
if callback_info:
|
316
|
+
return callback_info, True
|
317
|
+
# 02. authenticate
|
318
|
+
credentials = await self.authenticate(tool_name, thread_id, profile, **kwargs)
|
319
|
+
# 03. call tool
|
320
|
+
result = await self.tool_call(tool_name, body=body, envs=credentials, **kwargs)
|
321
|
+
pocket_logger.debug(f"{tool_name} tool call result: {result}")
|
322
|
+
return result, False
|
323
|
+
|
324
|
+
async def prepare_auth(
|
325
|
+
self,
|
326
|
+
tool_name: Union[str, List[str]],
|
327
|
+
thread_id: str = "default",
|
328
|
+
profile: str = "default",
|
329
|
+
**kwargs,
|
330
|
+
) -> Optional[str]:
|
331
|
+
"""
|
332
|
+
Prepares the authentication process for the tool if necessary.
|
333
|
+
Returns callback URL and whether the tool requires authentication.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
tool_name(Union[str,List[str]]): tool name to invoke
|
337
|
+
thread_id(str): thread id
|
338
|
+
profile(str): profile name
|
339
|
+
|
340
|
+
Returns:
|
341
|
+
Optional[str]: callback URI if necessary
|
342
|
+
"""
|
343
|
+
|
344
|
+
if isinstance(tool_name, str):
|
345
|
+
tool_name = [tool_name]
|
346
|
+
|
347
|
+
tools: List[Tool] = []
|
348
|
+
for name in tool_name:
|
349
|
+
tool = self._tool_instance(name)
|
350
|
+
if tool.auth is not None:
|
351
|
+
tools.append(tool)
|
352
|
+
|
353
|
+
if len(tools) == 0:
|
354
|
+
return None
|
355
|
+
|
356
|
+
auth_handler_name = tools[0].auth.auth_handler
|
357
|
+
auth_provider = tools[0].auth.auth_provider
|
358
|
+
auth_scopes = set()
|
359
|
+
|
360
|
+
for tool in tools:
|
361
|
+
if tool.auth.auth_handler != auth_handler_name:
|
362
|
+
pocket_logger.error(
|
363
|
+
f"All Tools should have same auth handler. but it's different {tool.auth.auth_handler}, {auth_handler_name}"
|
364
|
+
)
|
365
|
+
|
366
|
+
return f"All Tools should have same auth handler. but it's different {tool.auth.auth_handler}, {auth_handler_name}"
|
367
|
+
if tool.auth.auth_provider != auth_provider:
|
368
|
+
pocket_logger.error(
|
369
|
+
f"All Tools should have same auth provider. but it's different {tool.auth.auth_provider}, {auth_provider}"
|
370
|
+
)
|
371
|
+
return f"All Tools should have same auth provider. but it's different {tool.auth.auth_provider}, {auth_provider}"
|
372
|
+
|
373
|
+
if tool.auth.scopes is not None:
|
374
|
+
auth_scopes |= set(tool.auth.scopes)
|
375
|
+
|
376
|
+
auth_req = self.auth.make_request(
|
377
|
+
auth_handler_name=auth_handler_name,
|
378
|
+
auth_provider=auth_provider,
|
379
|
+
auth_scopes=list(auth_scopes),
|
287
380
|
)
|
288
381
|
|
289
|
-
return prepare
|
382
|
+
return await self.auth.prepare(
|
383
|
+
auth_req=auth_req,
|
384
|
+
auth_handler_name=auth_handler_name,
|
385
|
+
auth_provider=auth_provider,
|
386
|
+
thread_id=thread_id,
|
387
|
+
profile=profile,
|
388
|
+
**kwargs,
|
389
|
+
)
|
290
390
|
|
291
|
-
async def
|
391
|
+
async def authenticate(
|
292
392
|
self,
|
293
393
|
tool_name: str,
|
294
394
|
thread_id: str = "default",
|
295
395
|
profile: str = "default",
|
296
|
-
*args,
|
297
396
|
**kwargs,
|
298
|
-
):
|
299
|
-
|
300
|
-
|
301
|
-
self._uid,
|
302
|
-
args,
|
303
|
-
{
|
304
|
-
"tool_name": tool_name,
|
305
|
-
"thread_id": thread_id,
|
306
|
-
"profile": profile,
|
307
|
-
**kwargs,
|
308
|
-
},
|
309
|
-
)
|
397
|
+
) -> dict[str, str]:
|
398
|
+
"""
|
399
|
+
Authenticates the handler included in the tool and returns credentials.
|
310
400
|
|
311
|
-
|
401
|
+
Args:
|
402
|
+
tool_name(str): tool name to invoke
|
403
|
+
thread_id(str): thread id
|
404
|
+
profile(str): profile name
|
405
|
+
|
406
|
+
Returns:
|
407
|
+
dict[str, str]: credentials
|
408
|
+
"""
|
409
|
+
tool = self._tool_instance(tool_name)
|
410
|
+
if tool.auth is None:
|
411
|
+
return {}
|
412
|
+
auth_req = self.auth.make_request(
|
413
|
+
auth_handler_name=tool.auth.auth_handler,
|
414
|
+
auth_provider=tool.auth.auth_provider,
|
415
|
+
auth_scopes=tool.auth.scopes,
|
416
|
+
)
|
417
|
+
auth_ctx = await self.auth.authenticate_async(
|
418
|
+
auth_req=auth_req,
|
419
|
+
auth_handler_name=tool.auth.auth_handler,
|
420
|
+
auth_provider=tool.auth.auth_provider,
|
421
|
+
thread_id=thread_id,
|
422
|
+
profile=profile,
|
423
|
+
**kwargs,
|
424
|
+
)
|
425
|
+
return auth_ctx.to_dict()
|
312
426
|
|
313
|
-
async def
|
427
|
+
async def tool_call(
|
314
428
|
self,
|
315
429
|
tool_name: str,
|
316
430
|
body: Any,
|
@@ -319,42 +433,76 @@ class Pocket(object):
|
|
319
433
|
*args,
|
320
434
|
**kwargs,
|
321
435
|
):
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
436
|
+
try:
|
437
|
+
tool = self._tool_instance(tool_name)
|
438
|
+
result = await asyncio.wait_for(
|
439
|
+
tool.ainvoke(body=body, thread_id=thread_id, profile=profile, **kwargs), timeout=180)
|
440
|
+
except asyncio.TimeoutError:
|
441
|
+
pocket_logger.warning("Timeout tool call.")
|
442
|
+
return "timeout tool call"
|
443
|
+
|
444
|
+
# TODO(moon): extract
|
445
|
+
if tool.postprocessings is not None:
|
446
|
+
for postprocessing in tool.postprocessings:
|
447
|
+
try:
|
448
|
+
result = postprocessing(result)
|
449
|
+
except Exception as e:
|
450
|
+
exception_str = (
|
451
|
+
f"Error in postprocessing `{postprocessing.__name__}`: {e}"
|
452
|
+
)
|
453
|
+
pocket_logger.error(exception_str)
|
454
|
+
return exception_str
|
334
455
|
|
335
456
|
return result
|
336
457
|
|
458
|
+
def grouping_tool_by_auth_provider(self) -> dict[str, List[Tool]]:
|
459
|
+
tool_by_provider = {}
|
460
|
+
for tool_name, tool in self.tools.items():
|
461
|
+
if tool.auth is None:
|
462
|
+
continue
|
463
|
+
|
464
|
+
auth_provider_name = tool.auth.auth_provider.name
|
465
|
+
if tool_by_provider.get(auth_provider_name):
|
466
|
+
tool_by_provider[auth_provider_name].append(tool)
|
467
|
+
else:
|
468
|
+
tool_by_provider[auth_provider_name] = [tool]
|
469
|
+
return tool_by_provider
|
470
|
+
|
471
|
+
def _load_tools(self, tools):
|
472
|
+
self.tools = dict()
|
473
|
+
dock = self._default_dock()
|
474
|
+
|
475
|
+
def _load(tool_like):
|
476
|
+
if isinstance(tool_like, str) or isinstance(tool_like, tuple):
|
477
|
+
return dock(tool_like)
|
478
|
+
elif isinstance(tool_like, Tool):
|
479
|
+
return tool_like
|
480
|
+
elif isinstance(tool_like, Callable):
|
481
|
+
return from_func(tool_like)
|
482
|
+
else:
|
483
|
+
raise ValueError(f"Invalid tool type: {type(tool_like)}")
|
484
|
+
|
485
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=10, thread_name_prefix="tool-loader") as executor:
|
486
|
+
futures = [executor.submit(_load, tool_like) for tool_like in tools]
|
487
|
+
for future in concurrent.futures.as_completed(futures):
|
488
|
+
tool = future.result()
|
489
|
+
self.tools[tool.name] = tool
|
490
|
+
|
491
|
+
def _tool_instance(self, tool_name: str) -> Tool:
|
492
|
+
return self.tools[tool_name]
|
493
|
+
|
337
494
|
def _teardown_server(self):
|
338
|
-
self.
|
339
|
-
|
495
|
+
self.teardown()
|
496
|
+
|
340
497
|
def teardown(self):
|
341
498
|
if hasattr(self, 'server'):
|
342
|
-
|
499
|
+
with Pocket._pocket_count_lock:
|
500
|
+
Pocket._cnt_pocket_count -= 1
|
501
|
+
if Pocket._cnt_pocket_count <= 0:
|
502
|
+
self.server.teardown()
|
343
503
|
|
344
504
|
def __enter__(self):
|
345
505
|
return self
|
346
506
|
|
347
507
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
348
508
|
self.teardown()
|
349
|
-
|
350
|
-
def __del__(self):
|
351
|
-
self.teardown()
|
352
|
-
|
353
|
-
def __getstate__(self):
|
354
|
-
state = self.__dict__.copy()
|
355
|
-
if "server" in state:
|
356
|
-
del state["server"]
|
357
|
-
return state
|
358
|
-
|
359
|
-
def __setstate__(self, state):
|
360
|
-
self.__dict__.update(state)
|