hyperpocket 0.0.3__py3-none-any.whl → 0.1.8__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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 +124 -173
  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.3.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.3.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.3.dist-info/RECORD +0 -130
  93. hyperpocket-0.0.3.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
@@ -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
- if self.process and self.process.is_alive():
40
- self.process.terminate()
41
- self.process.join()
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
- result = await self.child_pocket.acall(*a, **kw)
59
- _conn.send((_op, _uid, result))
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
- result = self.child_pocket.prepare_auth(*a, **kw)
63
- _conn.send((_op, _uid, result))
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
- result = await self.child_pocket.authenticate(*a, **kw)
67
- _conn.send((_op, _uid, result))
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
- result = await self.child_pocket.tool_call(*a, **kw)
71
- _conn.send((_op, _uid, result))
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,7 +135,10 @@ 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()
139
+ if error:
140
+ raise error
141
+
106
142
  future = self.future_store[uid]
107
143
  future.set_result(result)
108
144
  break
@@ -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, child_pocket):
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=(child_pocket,), daemon=True)
161
+ self.process = mp.Process(target=self._run, args=(pocket_core,), daemon=True)
126
162
  self.process.start()
127
163
 
128
- def _run(self, child_pocket):
129
- self.child_pocket = child_pocket
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()
@@ -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 ScriptStdout(BaseModel):
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')
@@ -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.ScriptStdout) -> scriptdto.ScriptStdout:
21
- FutureStore.resolve_future(script_id, req.stdout)
22
- return scriptdto.ScriptStdout(stdout=req.stdout)
23
-
24
- @wasm_tool_router.post("/scripts/{script_id}/fail")
25
- async def fail_script_page(script_id: str, req: scriptdto.ScriptStdout) -> scriptdto.ScriptStdout:
26
- pass
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))
@@ -55,7 +55,7 @@ To Be Updated (TBU)
55
55
 
56
56
  ## How to Implement
57
57
 
58
- 1. Add the SessionType enum in `pocket/config/session.py`.
59
- 2. Add the SessionConfig in `pocket/config/session.py`.
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.
@@ -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(auth_provider: AuthProvider, thread_id: str, profile: str) -> K:
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=auth_provider.name,
70
+ auth_provider=auth_provider_name,
58
71
  thread_id=thread_id,
59
72
  profile=profile,
60
73
  delimiter=SESSION_KEY_DELIMITER,
@@ -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,
@@ -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(auth_provider: AuthProvider, thread_id: str, profile: str) -> K:
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=auth_provider.name,
87
+ auth_provider=auth_provider_name,
64
88
  thread_id=thread_id,
65
89
  profile=profile,
66
90
  delimiter=SESSION_KEY_DELIMITER,
@@ -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
- - If auth_provider is not specified, the tool is considered to require no authentication.
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
- - If auth_handler is not specified, the default handler of the authentication provider will be used.
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
- - If authentication is not performed or the authentication handler is non-scoped, the value should be None.
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
- - Define the tasks to be performed when invoking the tool in invoke or ainvoke.
47
- - Define a factory method to initialize the tool.(Optional)
48
- - Inject values for the required fields during initialization.
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
- - For `WasmTool`, the input can be a ToolRequest or a str.
52
- - For `FunctionTool`, the input can be a Callable.
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
- - The initialization is based on the provided ToolLike value.
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
-
@@ -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
- "from_func",
12
- "function_tool"
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 __variable keyword__ arguments.
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: