hyperpocket 0.3.7__py3-none-any.whl → 0.4.0__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/auth/provider.py +1 -0
- hyperpocket/auth/weaviate/context.py +12 -0
- hyperpocket/auth/weaviate/token_context.py +11 -0
- hyperpocket/auth/weaviate/token_handler.py +68 -0
- hyperpocket/auth/weaviate/token_schema.py +7 -0
- hyperpocket/cli/eject.py +2 -7
- hyperpocket/cli/pull.py +2 -7
- hyperpocket/config/settings.py +2 -1
- hyperpocket/pocket_core.py +41 -68
- hyperpocket/pocket_main.py +37 -16
- hyperpocket/repository/__init__.py +3 -4
- hyperpocket/repository/repository.py +6 -41
- hyperpocket/repository/tool_reference.py +28 -0
- hyperpocket/server/auth/weaviate.py +27 -0
- hyperpocket/server/server.py +127 -61
- hyperpocket/session/in_memory.py +13 -3
- hyperpocket/tool/__init__.py +0 -3
- hyperpocket/tool/dock/__init__.py +3 -0
- hyperpocket/tool/dock/dock.py +34 -0
- hyperpocket/tool/function/__init__.py +1 -1
- hyperpocket/tool/function/tool.py +62 -32
- hyperpocket/tool/tool.py +1 -9
- hyperpocket/tool_like.py +2 -1
- hyperpocket/util/generate_slug.py +4 -0
- hyperpocket/util/json_schema_to_model.py +5 -1
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/METADATA +4 -1
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/RECORD +30 -36
- hyperpocket/cli/sync.py +0 -17
- hyperpocket/repository/lock.py +0 -240
- hyperpocket/repository/lockfile.py +0 -62
- hyperpocket/server/tool/__init__.py +0 -10
- hyperpocket/server/tool/dto/script.py +0 -33
- hyperpocket/server/tool/wasm.py +0 -46
- hyperpocket/tool/wasm/README.md +0 -166
- hyperpocket/tool/wasm/__init__.py +0 -3
- hyperpocket/tool/wasm/browser.py +0 -63
- hyperpocket/tool/wasm/invoker.py +0 -41
- hyperpocket/tool/wasm/script.py +0 -134
- hyperpocket/tool/wasm/templates/__init__.py +0 -35
- hyperpocket/tool/wasm/templates/node.py +0 -87
- hyperpocket/tool/wasm/templates/python.py +0 -93
- hyperpocket/tool/wasm/tool.py +0 -163
- /hyperpocket/{server/tool/dto → auth/weaviate}/__init__.py +0 -0
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/WHEEL +0 -0
- {hyperpocket-0.3.7.dist-info → hyperpocket-0.4.0.dist-info}/entry_points.txt +0 -0
    
        hyperpocket/server/server.py
    CHANGED
    
    | @@ -10,7 +10,6 @@ from uvicorn import Config, Server | |
| 10 10 | 
             
            from hyperpocket.config import config, pocket_logger
         | 
| 11 11 | 
             
            from hyperpocket.pocket_core import PocketCore
         | 
| 12 12 | 
             
            from hyperpocket.server.auth import auth_router
         | 
| 13 | 
            -
            from hyperpocket.server.tool import tool_router
         | 
| 14 13 |  | 
| 15 14 |  | 
| 16 15 | 
             
            class PocketServerOperations(enum.Enum):
         | 
| @@ -18,10 +17,12 @@ class PocketServerOperations(enum.Enum): | |
| 18 17 | 
             
                PREPARE_AUTH = "prepare_auth"
         | 
| 19 18 | 
             
                AUTHENTICATE = "authenticate"
         | 
| 20 19 | 
             
                TOOL_CALL = "tool_call"
         | 
| 20 | 
            +
                PLUG_CORE = "plug_core"
         | 
| 21 21 |  | 
| 22 22 |  | 
| 23 23 | 
             
            class PocketServer(object):
         | 
| 24 | 
            -
                 | 
| 24 | 
            +
                fastapi_app: Optional[FastAPI]
         | 
| 25 | 
            +
                main_server: Optional[Server]
         | 
| 25 26 | 
             
                internal_server_port: int
         | 
| 26 27 | 
             
                proxy_server: Optional[Server]
         | 
| 27 28 | 
             
                proxy_port: int
         | 
| @@ -29,15 +30,64 @@ class PocketServer(object): | |
| 29 30 | 
             
                process: mp.Process
         | 
| 30 31 | 
             
                future_store: dict[str, asyncio.Future]
         | 
| 31 32 | 
             
                torn_down: bool = False
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                 | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
                     | 
| 37 | 
            -
             | 
| 38 | 
            -
                    self. | 
| 39 | 
            -
                    self. | 
| 33 | 
            +
                _uidset: set
         | 
| 34 | 
            +
                _cores: dict[str, PocketCore]
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def __init__(self):
         | 
| 37 | 
            +
                    self._initialized = False
         | 
| 38 | 
            +
                    self.internal_server_port = config().internal_server_port
         | 
| 39 | 
            +
                    self.proxy_port = config().public_server_port
         | 
| 40 | 
            +
                    self._uidset = set()
         | 
| 40 41 | 
             
                    self.future_store = dict()
         | 
| 42 | 
            +
                    self.fastapi_app = None
         | 
| 43 | 
            +
                    self.main_server = None
         | 
| 44 | 
            +
                    self._cores = dict()
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # should be called in child process
         | 
| 47 | 
            +
                def _plug_core(self, pocket_uid: str, pocket_core: PocketCore, *_a, **_kw):
         | 
| 48 | 
            +
                    # extend http routers from each docks
         | 
| 49 | 
            +
                    dock_routes = set([str(r) for r in self.fastapi_app.routes])
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    for dock in pocket_core.docks:
         | 
| 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
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                        dock_routes.update(dock_route)
         | 
| 58 | 
            +
                        self.fastapi_app.include_router(dock.router)
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    # keep pocket core
         | 
| 61 | 
            +
                    self._cores[pocket_uid] = pocket_core
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                # should be called in parent process
         | 
| 64 | 
            +
                async def plug_core(self, pocket_uid: str, pocket_core: PocketCore):
         | 
| 65 | 
            +
                    await self.call_in_subprocess(
         | 
| 66 | 
            +
                        PocketServerOperations.PLUG_CORE,
         | 
| 67 | 
            +
                        pocket_uid,
         | 
| 68 | 
            +
                        tuple(),
         | 
| 69 | 
            +
                        {
         | 
| 70 | 
            +
                            "pocket_uid": pocket_uid,
         | 
| 71 | 
            +
                            "pocket_core": pocket_core,
         | 
| 72 | 
            +
                        },
         | 
| 73 | 
            +
                    )
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                @classmethod
         | 
| 76 | 
            +
                def get_instance_and_refcnt_up(cls, uid: str):
         | 
| 77 | 
            +
                    if cls.__dict__.get("_instance") is None:
         | 
| 78 | 
            +
                        cls._instance = cls()
         | 
| 79 | 
            +
                        cls._instance.run()
         | 
| 80 | 
            +
                    cls._instance.refcnt_up(uid)
         | 
| 81 | 
            +
                    return cls._instance
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def refcnt_up(self, uid: str):
         | 
| 84 | 
            +
                    self._uidset.add(uid)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def refcnt_down(self, uid: str):
         | 
| 87 | 
            +
                    if uid in self._uidset:
         | 
| 88 | 
            +
                        self._uidset.remove(uid)
         | 
| 89 | 
            +
                        if len(self._uidset) == 0:
         | 
| 90 | 
            +
                            self._instance.teardown()
         | 
| 41 91 |  | 
| 42 92 | 
             
                def teardown(self):
         | 
| 43 93 | 
             
                    # @XXX(seokju) is it ok to call this method both in __del__ and __exit__?
         | 
| @@ -63,20 +113,21 @@ class PocketServer(object): | |
| 63 113 | 
             
                    loop = asyncio.get_running_loop()
         | 
| 64 114 | 
             
                    _, conn = self.pipe
         | 
| 65 115 |  | 
| 66 | 
            -
                    async def _acall(_conn,  | 
| 116 | 
            +
                    async def _acall(_conn, _future_uid, _core_uid, _args, _kwargs):
         | 
| 67 117 | 
             
                        try:
         | 
| 68 | 
            -
                             | 
| 118 | 
            +
                            core = self._cores[_core_uid]
         | 
| 119 | 
            +
                            result = await core.acall(*_args, **_kwargs)
         | 
| 69 120 | 
             
                            error = None
         | 
| 70 121 | 
             
                        except Exception as e:
         | 
| 71 | 
            -
                            pocket_logger.error(f"failed  | 
| 122 | 
            +
                            pocket_logger.error(f"failed in pocket subprocess. error: {e}")
         | 
| 72 123 | 
             
                            result = None
         | 
| 73 124 | 
             
                            error = e
         | 
| 125 | 
            +
                        _conn.send((_future_uid, result, error))
         | 
| 74 126 |  | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
                    async def _prepare(_conn, _op, _uid, a, kw):
         | 
| 127 | 
            +
                    async def _prepare(_conn, _future_uid, _core_uid, a, kw):
         | 
| 78 128 | 
             
                        try:
         | 
| 79 | 
            -
                             | 
| 129 | 
            +
                            core = self._cores[_core_uid]
         | 
| 130 | 
            +
                            result = core.prepare_auth(*a, **kw)
         | 
| 80 131 | 
             
                            error = None
         | 
| 81 132 | 
             
                        except Exception as e:
         | 
| 82 133 | 
             
                            pocket_logger.error(
         | 
| @@ -85,11 +136,12 @@ class PocketServer(object): | |
| 85 136 | 
             
                            result = None
         | 
| 86 137 | 
             
                            error = e
         | 
| 87 138 |  | 
| 88 | 
            -
                        _conn.send(( | 
| 139 | 
            +
                        _conn.send((_future_uid, result, error))
         | 
| 89 140 |  | 
| 90 | 
            -
                    async def _authenticate(_conn,  | 
| 141 | 
            +
                    async def _authenticate(_conn, _future_uid, _core_uid, a, kw):
         | 
| 91 142 | 
             
                        try:
         | 
| 92 | 
            -
                             | 
| 143 | 
            +
                            core = self._cores[_core_uid]
         | 
| 144 | 
            +
                            result = await core.authenticate(*a, **kw)
         | 
| 93 145 | 
             
                            error = None
         | 
| 94 146 | 
             
                        except Exception as e:
         | 
| 95 147 | 
             
                            pocket_logger.error(
         | 
| @@ -98,11 +150,12 @@ class PocketServer(object): | |
| 98 150 | 
             
                            result = None
         | 
| 99 151 | 
             
                            error = e
         | 
| 100 152 |  | 
| 101 | 
            -
                        _conn.send(( | 
| 153 | 
            +
                        _conn.send((_future_uid, result, error))
         | 
| 102 154 |  | 
| 103 | 
            -
                    async def _tool_call(_conn,  | 
| 155 | 
            +
                    async def _tool_call(_conn, _future_uid, _core_uid, a, kw):
         | 
| 104 156 | 
             
                        try:
         | 
| 105 | 
            -
                             | 
| 157 | 
            +
                            core = self._cores[_core_uid]
         | 
| 158 | 
            +
                            result = await core.tool_call(*a, **kw)
         | 
| 106 159 | 
             
                            error = None
         | 
| 107 160 | 
             
                        except Exception as e:
         | 
| 108 161 | 
             
                            pocket_logger.error(
         | 
| @@ -111,38 +164,50 @@ class PocketServer(object): | |
| 111 164 | 
             
                            result = None
         | 
| 112 165 | 
             
                            error = e
         | 
| 113 166 |  | 
| 114 | 
            -
                        _conn.send(( | 
| 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))
         | 
| 115 178 |  | 
| 116 179 | 
             
                    while True:
         | 
| 117 180 | 
             
                        if conn.poll():
         | 
| 118 | 
            -
                            op,  | 
| 119 | 
            -
                            if op == PocketServerOperations.CALL | 
| 120 | 
            -
                                loop.create_task(_acall(conn,  | 
| 121 | 
            -
                            elif op == PocketServerOperations.PREPARE_AUTH | 
| 122 | 
            -
                                loop.create_task(_prepare(conn,  | 
| 123 | 
            -
                            elif op == PocketServerOperations.AUTHENTICATE | 
| 124 | 
            -
                                loop.create_task(_authenticate(conn,  | 
| 125 | 
            -
                            elif op == PocketServerOperations.TOOL_CALL | 
| 126 | 
            -
                                loop.create_task(_tool_call(conn,  | 
| 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))
         | 
| 127 192 | 
             
                            else:
         | 
| 128 | 
            -
                                raise  | 
| 193 | 
            +
                                raise ValueError(f"Unknown operation: {op}")
         | 
| 129 194 | 
             
                        else:
         | 
| 130 195 | 
             
                            await asyncio.sleep(0)
         | 
| 131 196 |  | 
| 132 | 
            -
                def send_in_parent(self, op: PocketServerOperations, args: tuple, kwargs: dict):
         | 
| 197 | 
            +
                def send_in_parent(self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict):
         | 
| 133 198 | 
             
                    conn, _ = self.pipe
         | 
| 134 | 
            -
                     | 
| 135 | 
            -
                    message = (op | 
| 199 | 
            +
                    future_uid = str(uuid.uuid4())
         | 
| 200 | 
            +
                    message = (op, future_uid, pocket_uid, args, kwargs)
         | 
| 136 201 | 
             
                    future = asyncio.Future()
         | 
| 137 | 
            -
                    self.future_store[ | 
| 202 | 
            +
                    self.future_store[future_uid] = future
         | 
| 138 203 | 
             
                    conn.send(message)
         | 
| 139 | 
            -
                    return  | 
| 204 | 
            +
                    return future_uid
         | 
| 140 205 |  | 
| 141 206 | 
             
                async def poll_in_parent(self):
         | 
| 142 207 | 
             
                    conn, _ = self.pipe
         | 
| 143 208 | 
             
                    while True:
         | 
| 144 209 | 
             
                        if conn.poll():
         | 
| 145 | 
            -
                             | 
| 210 | 
            +
                            uid, result, error = conn.recv()
         | 
| 146 211 | 
             
                            future = self.future_store[uid]
         | 
| 147 212 | 
             
                            if error:
         | 
| 148 213 | 
             
                                future.set_exception(error)
         | 
| @@ -153,19 +218,21 @@ class PocketServer(object): | |
| 153 218 | 
             
                            await asyncio.sleep(0)
         | 
| 154 219 |  | 
| 155 220 | 
             
                async def call_in_subprocess(
         | 
| 156 | 
            -
                    self, op: PocketServerOperations, args: tuple, kwargs: dict
         | 
| 221 | 
            +
                    self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict
         | 
| 157 222 | 
             
                ):
         | 
| 158 | 
            -
                    uid = self.send_in_parent(op, args, kwargs)
         | 
| 223 | 
            +
                    uid = self.send_in_parent(op, pocket_uid, args, kwargs)
         | 
| 159 224 | 
             
                    loop = asyncio.get_running_loop()
         | 
| 160 225 | 
             
                    loop.create_task(self.poll_in_parent())
         | 
| 161 226 | 
             
                    return await self.future_store[uid]
         | 
| 162 227 |  | 
| 163 | 
            -
                def run(self | 
| 228 | 
            +
                def run(self):
         | 
| 164 229 | 
             
                    self._set_mp_start_method()
         | 
| 165 230 |  | 
| 166 231 | 
             
                    error_queue = mp.Queue()
         | 
| 167 232 | 
             
                    self.pipe = mp.Pipe()
         | 
| 168 | 
            -
                    self.process = mp.Process( | 
| 233 | 
            +
                    self.process = mp.Process(
         | 
| 234 | 
            +
                        target=self._run
         | 
| 235 | 
            +
                    )
         | 
| 169 236 | 
             
                    self.process.start()  # process start
         | 
| 170 237 |  | 
| 171 238 | 
             
                    if not error_queue.empty():
         | 
| @@ -174,27 +241,25 @@ class PocketServer(object): | |
| 174 241 |  | 
| 175 242 | 
             
                def _report_initialized(self, error: Optional[Exception] = None):
         | 
| 176 243 | 
             
                    _, conn = self.pipe
         | 
| 177 | 
            -
                    conn.send(
         | 
| 178 | 
            -
                        (
         | 
| 179 | 
            -
                            "server-initialization",
         | 
| 180 | 
            -
                            error,
         | 
| 181 | 
            -
                        )
         | 
| 182 | 
            -
                    )
         | 
| 244 | 
            +
                    conn.send(('server-initialization', error,))
         | 
| 183 245 |  | 
| 184 246 | 
             
                def wait_initialized(self):
         | 
| 247 | 
            +
                    if self._initialized:
         | 
| 248 | 
            +
                        return
         | 
| 185 249 | 
             
                    conn, _ = self.pipe
         | 
| 186 250 | 
             
                    while True:
         | 
| 187 251 | 
             
                        if conn.poll():
         | 
| 188 252 | 
             
                            _, error = conn.recv()
         | 
| 189 253 | 
             
                            if error:
         | 
| 190 254 | 
             
                                raise error
         | 
| 191 | 
            -
                             | 
| 255 | 
            +
                            break
         | 
| 256 | 
            +
                    self._initialized = True
         | 
| 192 257 |  | 
| 193 | 
            -
                def _run(self | 
| 258 | 
            +
                def _run(self):
         | 
| 194 259 | 
             
                    try:
         | 
| 195 260 | 
             
                        # init process
         | 
| 196 | 
            -
                        self. | 
| 197 | 
            -
                        self.main_server = self._create_main_server()
         | 
| 261 | 
            +
                        self.fastapi_app = self._create_fastapi_app()
         | 
| 262 | 
            +
                        self.main_server = self._create_main_server(self.fastapi_app)
         | 
| 198 263 | 
             
                        self.proxy_server = self._create_https_proxy_server()
         | 
| 199 264 | 
             
                        self._report_initialized()
         | 
| 200 265 |  | 
| @@ -204,20 +269,21 @@ class PocketServer(object): | |
| 204 269 | 
             
                    except Exception as error:
         | 
| 205 270 | 
             
                        self._report_initialized(error)
         | 
| 206 271 |  | 
| 207 | 
            -
                def  | 
| 272 | 
            +
                def _create_fastapi_app(self) -> FastAPI:
         | 
| 208 273 | 
             
                    app = FastAPI()
         | 
| 274 | 
            +
                    app.include_router(auth_router)
         | 
| 275 | 
            +
                    app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
         | 
| 276 | 
            +
                    return app
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                def _create_main_server(self, app: FastAPI) -> Server:
         | 
| 209 279 | 
             
                    _config = Config(
         | 
| 210 280 | 
             
                        app,
         | 
| 211 281 | 
             
                        host="0.0.0.0",
         | 
| 212 282 | 
             
                        port=self.internal_server_port,
         | 
| 213 283 | 
             
                        log_level=config().log_level,
         | 
| 214 284 | 
             
                    )
         | 
| 215 | 
            -
                     | 
| 216 | 
            -
                     | 
| 217 | 
            -
                    app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
         | 
| 218 | 
            -
             | 
| 219 | 
            -
                    app = Server(_config)
         | 
| 220 | 
            -
                    return app
         | 
| 285 | 
            +
                    server = Server(_config)
         | 
| 286 | 
            +
                    return server
         | 
| 221 287 |  | 
| 222 288 | 
             
                def _create_https_proxy_server(self) -> Optional[Server]:
         | 
| 223 289 | 
             
                    if not config().enable_local_callback_proxy:
         | 
    
        hyperpocket/session/in_memory.py
    CHANGED
    
    | @@ -19,10 +19,20 @@ InMemorySessionValue = BaseSessionValue | |
| 19 19 | 
             
            class InMemorySessionStorage(
         | 
| 20 20 | 
             
                SessionStorageInterface[InMemorySessionKey, InMemorySessionValue]
         | 
| 21 21 | 
             
            ):
         | 
| 22 | 
            -
                 | 
| 22 | 
            +
                _instance = None
         | 
| 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
         | 
| 30 | 
            +
             | 
| 23 31 | 
             
                def __init__(self, session_config: SessionConfigInMemory):
         | 
| 24 | 
            -
                     | 
| 25 | 
            -
             | 
| 32 | 
            +
                    if not self._is_initialized:
         | 
| 33 | 
            +
                        super().__init__()
         | 
| 34 | 
            +
                        self.storage: Dict[InMemorySessionKey, InMemorySessionValue] = {}
         | 
| 35 | 
            +
                        self._is_initialized = True
         | 
| 26 36 |  | 
| 27 37 | 
             
                @classmethod
         | 
| 28 38 | 
             
                def session_storage_type(cls) -> SessionType:
         | 
    
        hyperpocket/tool/__init__.py
    CHANGED
    
    | @@ -1,13 +1,10 @@ | |
| 1 1 | 
             
            from hyperpocket.tool.function import from_dock, from_func, function_tool
         | 
| 2 2 | 
             
            from hyperpocket.tool.tool import Tool, ToolAuth, ToolRequest
         | 
| 3 | 
            -
            from hyperpocket.tool.wasm.tool import from_git, from_local
         | 
| 4 3 |  | 
| 5 4 | 
             
            __all__ = [
         | 
| 6 5 | 
             
                "Tool",
         | 
| 7 6 | 
             
                "ToolAuth",
         | 
| 8 7 | 
             
                "ToolRequest",
         | 
| 9 | 
            -
                "from_local",
         | 
| 10 | 
            -
                "from_git",
         | 
| 11 8 | 
             
                "from_dock",
         | 
| 12 9 | 
             
                "from_func",
         | 
| 13 10 | 
             
                "function_tool",
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            import abc
         | 
| 2 | 
            +
            from typing import Any
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            from fastapi import APIRouter
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            from hyperpocket.tool import ToolRequest
         | 
| 7 | 
            +
            from hyperpocket.tool.function import FunctionTool
         | 
| 8 | 
            +
             | 
| 9 | 
            +
             | 
| 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 | 
            +
             | 
| 28 | 
            +
                @abc.abstractmethod
         | 
| 29 | 
            +
                def tools(self) -> list[FunctionTool]:
         | 
| 30 | 
            +
                    raise NotImplementedError
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                @abc.abstractmethod
         | 
| 33 | 
            +
                async def teardown(self):
         | 
| 34 | 
            +
                    raise NotImplementedError
         | 
| @@ -17,19 +17,36 @@ class FunctionTool(Tool): | |
| 17 17 |  | 
| 18 18 | 
             
                func: Optional[Callable[..., str]]
         | 
| 19 19 | 
             
                afunc: Optional[Callable[..., Coroutine[Any, Any, str]]]
         | 
| 20 | 
            +
                keep_structured_arguments: bool = False
         | 
| 20 21 |  | 
| 21 22 | 
             
                def invoke(self, **kwargs) -> str:
         | 
| 22 23 | 
             
                    binding_args = self._get_binding_args(kwargs)
         | 
| 23 24 | 
             
                    if self.func is None:
         | 
| 24 25 | 
             
                        if self.afunc is None:
         | 
| 25 26 | 
             
                            raise ValueError("Both func and afunc are None")
         | 
| 27 | 
            +
                        try:
         | 
| 28 | 
            +
                            loop = asyncio.get_running_loop()
         | 
| 29 | 
            +
                            return str(loop.run_until_complete(self.afunc(**binding_args)))
         | 
| 30 | 
            +
                        except RuntimeError:
         | 
| 31 | 
            +
                            pass
         | 
| 32 | 
            +
                        except Exception as e:
         | 
| 33 | 
            +
                            import traceback
         | 
| 34 | 
            +
                            traceback.print_exc()
         | 
| 35 | 
            +
                            traceback.print_stack()
         | 
| 36 | 
            +
                            return "There was an error while executing the tool: " + str(e)
         | 
| 26 37 | 
             
                        try:
         | 
| 27 38 | 
             
                            return str(asyncio.run(self.afunc(**binding_args)))
         | 
| 28 39 | 
             
                        except Exception as e:
         | 
| 40 | 
            +
                            import traceback
         | 
| 41 | 
            +
                            traceback.print_exc()
         | 
| 42 | 
            +
                            traceback.print_stack()
         | 
| 29 43 | 
             
                            return "There was an error while executing the tool: " + str(e)
         | 
| 30 44 | 
             
                    try:
         | 
| 31 45 | 
             
                        return str(self.func(**binding_args))
         | 
| 32 46 | 
             
                    except Exception as e:
         | 
| 47 | 
            +
                        import traceback
         | 
| 48 | 
            +
                        traceback.print_exc()
         | 
| 49 | 
            +
                        traceback.print_stack()
         | 
| 33 50 | 
             
                        return "There was an error while executing the tool: " + str(e)
         | 
| 34 51 |  | 
| 35 52 | 
             
                async def ainvoke(self, **kwargs) -> str:
         | 
| @@ -39,9 +56,16 @@ class FunctionTool(Tool): | |
| 39 56 | 
             
                        binding_args = self._get_binding_args(kwargs)
         | 
| 40 57 | 
             
                        return str(await self.afunc(**binding_args))
         | 
| 41 58 | 
             
                    except Exception as e:
         | 
| 59 | 
            +
                        import traceback
         | 
| 60 | 
            +
                        traceback.print_exc()
         | 
| 61 | 
            +
                        traceback.print_stack()
         | 
| 42 62 | 
             
                        return "There was an error while executing the tool: " + str(e)
         | 
| 43 63 |  | 
| 44 64 | 
             
                def _get_binding_args(self, kwargs):
         | 
| 65 | 
            +
                    if self.keep_structured_arguments:
         | 
| 66 | 
            +
                        if kwargs.get("envs") is not None:
         | 
| 67 | 
            +
                            kwargs["envs"] |= self.tool_vars
         | 
| 68 | 
            +
                        return kwargs
         | 
| 45 69 | 
             
                    _kwargs = copy.deepcopy(kwargs)
         | 
| 46 70 |  | 
| 47 71 | 
             
                    # make body args to model
         | 
| @@ -54,28 +78,23 @@ class FunctionTool(Tool): | |
| 54 78 |  | 
| 55 79 | 
             
                    # binding args
         | 
| 56 80 | 
             
                    binding_args = {}
         | 
| 57 | 
            -
                     | 
| 58 | 
            -
             | 
| 59 | 
            -
                         | 
| 60 | 
            -
             | 
| 61 | 
            -
                    else:
         | 
| 62 | 
            -
                        sig = inspect.signature(self.func)
         | 
| 63 | 
            -
                        for param_name, param in sig.parameters.items():
         | 
| 64 | 
            -
                            if param_name not in args:
         | 
| 65 | 
            -
                                continue
         | 
| 81 | 
            +
                    sig = inspect.signature(self.func)
         | 
| 82 | 
            +
                    for param_name, param in sig.parameters.items():
         | 
| 83 | 
            +
                        if param_name not in args:
         | 
| 84 | 
            +
                            continue
         | 
| 66 85 |  | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 86 | 
            +
                        if param.kind == param.VAR_KEYWORD:
         | 
| 87 | 
            +
                            # var keyword args should be passed by plain dict
         | 
| 88 | 
            +
                            binding_args |= args[param_name]
         | 
| 89 | 
            +
                            binding_args |= _kwargs.get("envs", {}) | self.tool_vars
         | 
| 71 90 |  | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 91 | 
            +
                            if "envs" in _kwargs:
         | 
| 92 | 
            +
                                _kwargs.pop("envs")
         | 
| 74 93 |  | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 94 | 
            +
                            binding_args |= _kwargs  # add other kwargs
         | 
| 95 | 
            +
                            continue
         | 
| 77 96 |  | 
| 78 | 
            -
             | 
| 97 | 
            +
                        binding_args[param_name] = args[param_name]
         | 
| 79 98 |  | 
| 80 99 | 
             
                    return binding_args
         | 
| 81 100 |  | 
| @@ -89,11 +108,15 @@ class FunctionTool(Tool): | |
| 89 108 |  | 
| 90 109 | 
             
                @classmethod
         | 
| 91 110 | 
             
                def from_func(
         | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 111 | 
            +
                        cls,
         | 
| 112 | 
            +
                        func: Callable | "FunctionTool",
         | 
| 113 | 
            +
                        afunc: Callable[..., Coroutine[Any, Any, str]] | "FunctionTool" = None,
         | 
| 114 | 
            +
                        name: str = None,
         | 
| 115 | 
            +
                        description: str = None,
         | 
| 116 | 
            +
                        json_schema: dict[str, Any] = None,
         | 
| 117 | 
            +
                        auth: Optional[ToolAuth] = None,
         | 
| 118 | 
            +
                        tool_vars: dict[str, str] = None,
         | 
| 119 | 
            +
                        keep_structured_arguments: bool = False,
         | 
| 97 120 | 
             
                ) -> "FunctionTool":
         | 
| 98 121 | 
             
                    if tool_vars is None:
         | 
| 99 122 | 
             
                        tool_vars = dict()
         | 
| @@ -109,28 +132,35 @@ class FunctionTool(Tool): | |
| 109 132 | 
             
                    elif not callable(func) and not callable(afunc):
         | 
| 110 133 | 
             
                        raise ValueError("FunctionTool can only be created from a callable")
         | 
| 111 134 |  | 
| 112 | 
            -
                     | 
| 113 | 
            -
                     | 
| 135 | 
            +
                    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__)
         | 
| 137 | 
            +
                    schema = json_schema or \
         | 
| 138 | 
            +
                             (func and function_to_model(func).model_json_schema()) or \
         | 
| 139 | 
            +
                             (afunc and function_to_model(afunc).model_json_schema())
         | 
| 140 | 
            +
                    argument_json_schema = flatten_json_schema(schema)
         | 
| 114 141 |  | 
| 115 142 | 
             
                    return cls(
         | 
| 116 143 | 
             
                        func=func,
         | 
| 117 144 | 
             
                        afunc=afunc,
         | 
| 118 | 
            -
                        name= | 
| 119 | 
            -
                        description= | 
| 145 | 
            +
                        name=name,
         | 
| 146 | 
            +
                        description=description,
         | 
| 120 147 | 
             
                        argument_json_schema=argument_json_schema,
         | 
| 121 148 | 
             
                        auth=auth,
         | 
| 122 149 | 
             
                        default_tool_vars=tool_vars,
         | 
| 150 | 
            +
                        keep_structured_arguments=keep_structured_arguments,
         | 
| 123 151 | 
             
                    )
         | 
| 124 152 |  | 
| 125 153 | 
             
                @classmethod
         | 
| 126 154 | 
             
                def from_dock(
         | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 155 | 
            +
                        cls,
         | 
| 156 | 
            +
                        dock: list[Callable[..., str]],
         | 
| 157 | 
            +
                        tool_vars: Optional[dict[str, str]] = None,
         | 
| 130 158 | 
             
                ) -> list["FunctionTool"]:
         | 
| 131 159 | 
             
                    if tool_vars is None:
         | 
| 132 160 | 
             
                        tool_vars = dict()
         | 
| 133 161 | 
             
                    tools = []
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                    # @moon: will be refactored.
         | 
| 134 164 | 
             
                    for func in dock:
         | 
| 135 165 | 
             
                        if (_model := func.__dict__.get("__model__")) is not None:
         | 
| 136 166 | 
             
                            model = _model
         | 
| @@ -155,7 +185,7 @@ class FunctionTool(Tool): | |
| 155 185 | 
             
                                    argument_json_schema=argument_json_schema,
         | 
| 156 186 | 
             
                                    auth=auth,
         | 
| 157 187 | 
             
                                    default_tool_vars=(
         | 
| 158 | 
            -
             | 
| 188 | 
            +
                                            tool_vars | func.__dict__.get("__vars__", {})
         | 
| 159 189 | 
             
                                    ),
         | 
| 160 190 | 
             
                                )
         | 
| 161 191 | 
             
                            )
         | 
| @@ -169,7 +199,7 @@ class FunctionTool(Tool): | |
| 169 199 | 
             
                                    argument_json_schema=argument_json_schema,
         | 
| 170 200 | 
             
                                    auth=auth,
         | 
| 171 201 | 
             
                                    default_tool_vars=(
         | 
| 172 | 
            -
             | 
| 202 | 
            +
                                            tool_vars | func.__dict__.get("__vars__", {})
         | 
| 173 203 | 
             
                                    ),
         | 
| 174 204 | 
             
                                )
         | 
| 175 205 | 
             
                            )
         | 
    
        hyperpocket/tool/tool.py
    CHANGED
    
    | @@ -116,21 +116,13 @@ class Tool(BaseModel, abc.ABC): | |
| 116 116 | 
             
                        return self.description
         | 
| 117 117 |  | 
| 118 118 | 
             
                def override_tool_variables(self, override_vars: dict[str, str]) -> "Tool":
         | 
| 119 | 
            -
                    self.overridden_tool_vars  | 
| 119 | 
            +
                    self.overridden_tool_vars |= override_vars
         | 
| 120 120 | 
             
                    return self
         | 
| 121 121 |  | 
| 122 122 | 
             
                @property
         | 
| 123 123 | 
             
                def tool_vars(self) -> dict[str, str]:
         | 
| 124 124 | 
             
                    return self.default_tool_vars | self.overridden_tool_vars
         | 
| 125 125 |  | 
| 126 | 
            -
                @classmethod
         | 
| 127 | 
            -
                def from_tool_request(cls, tool_req: ToolRequest, **kwargs) -> "Tool":
         | 
| 128 | 
            -
                    from hyperpocket.tool.wasm.tool import WasmTool, WasmToolRequest
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                    if isinstance(tool_req, WasmToolRequest):
         | 
| 131 | 
            -
                        return WasmTool.from_tool_request(tool_req, **kwargs)
         | 
| 132 | 
            -
                    raise ValueError("Unknown tool request type")
         | 
| 133 | 
            -
             | 
| 134 126 | 
             
                @classmethod
         | 
| 135 127 | 
             
                def _get_schema_model(
         | 
| 136 128 | 
             
                    cls, name: str, json_schema: Optional[dict], use_profile: bool
         | 
    
        hyperpocket/tool_like.py
    CHANGED
    
    
| @@ -60,13 +60,17 @@ def json_schema_to_model( | |
| 60 60 | 
             
                        fields["additional_properties"] = (dict[str, additional_model], {})
         | 
| 61 61 |  | 
| 62 62 | 
             
                # Create the model
         | 
| 63 | 
            -
                model = create_model(model_name, **fields)
         | 
| 63 | 
            +
                model = create_model(f"{model_name}", **fields)
         | 
| 64 64 |  | 
| 65 65 | 
             
                # Add custom Config class to handle extra properties
         | 
| 66 66 | 
             
                class Config:
         | 
| 67 67 | 
             
                    extra = config_extra
         | 
| 68 68 |  | 
| 69 69 | 
             
                model.Config = Config
         | 
| 70 | 
            +
                
         | 
| 71 | 
            +
                # workaround for pickling dynamic classes
         | 
| 72 | 
            +
                model.__module__ = "__main__"
         | 
| 73 | 
            +
                model.__qualname__ = model.__name__.split('.')[-1]
         | 
| 70 74 |  | 
| 71 75 | 
             
                return model
         | 
| 72 76 |  | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Metadata-Version: 2.4
         | 
| 2 2 | 
             
            Name: hyperpocket
         | 
| 3 | 
            -
            Version: 0. | 
| 3 | 
            +
            Version: 0.4.0
         | 
| 4 4 | 
             
            Summary: Building AI agent with hyperpocket tool in a flash
         | 
| 5 5 | 
             
            Project-URL: Homepage, https://vessl-ai.github.io/hyperpocket
         | 
| 6 6 | 
             
            Project-URL: Repository, https://github.com/vessl-ai/hyperpocket
         | 
| @@ -13,6 +13,7 @@ Requires-Dist: gitpython>=3.1.43 | |
| 13 13 | 
             
            Requires-Dist: httpx==0.27
         | 
| 14 14 | 
             
            Requires-Dist: jinja2>=3.1.4
         | 
| 15 15 | 
             
            Requires-Dist: multiprocess>=0.70.17
         | 
| 16 | 
            +
            Requires-Dist: nest-asyncio>=1.6.0
         | 
| 16 17 | 
             
            Requires-Dist: playwright>=1.49.0
         | 
| 17 18 | 
             
            Requires-Dist: pydantic>=2.10.2
         | 
| 18 19 | 
             
            Requires-Dist: pygithub>=2.5.0
         | 
| @@ -21,6 +22,8 @@ Requires-Dist: redis>=5.2.1 | |
| 21 22 | 
             
            Requires-Dist: requests>=2.32.3
         | 
| 22 23 | 
             
            Requires-Dist: toml>=0.10.2
         | 
| 23 24 | 
             
            Requires-Dist: uvicorn>=0.32.1
         | 
| 25 | 
            +
            Provides-Extra: standard
         | 
| 26 | 
            +
            Requires-Dist: hyperdock-container; extra == 'standard'
         | 
| 24 27 | 
             
            Description-Content-Type: text/markdown
         | 
| 25 28 |  | 
| 26 29 | 
             
            <p align="center">
         |