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
@@ -236,15 +236,15 @@ class AuthHandlerInterface(ABC):
236
236
  - This class perform the actual prepare, authenticate, and refresh steps.
237
237
 
238
238
  5. Add a new enum value to the `AuthProvider` Enum(only implement new auth provider)
239
- - If a new `AuthProvider` is added, update the `AuthProvider` enum in `pocket/auth/provider.py`
239
+ - If a new `AuthProvider` is added, update the `AuthProvider` enum in `hyperpocket/auth/provider.py`
240
240
 
241
241
  6. Add new auth callback server endpoint
242
- - Add the endpoint under `pocket/server/auth/` packages.
242
+ - Add the endpoint under `hyperpocket/server/auth/` packages.
243
243
  - Declare an appropriate `APIRouter` in the package. Pocket will automatically register the endpoint during
244
244
  initialization.
245
245
 
246
246
  7. Add test code(optional)
247
- - Add the test code under `pocket/auth/tests/` packages.
247
+ - Add the test code under `hyperpocket/auth/tests/` packages.
248
248
 
249
249
  ---
250
250
 
@@ -1,14 +1,6 @@
1
1
  from hyperpocket.auth.context import AuthContext
2
- from hyperpocket.auth.github.context import GitHubAuthContext
3
- from hyperpocket.auth.github.oauth2_context import GitHubOAuth2AuthContext
4
- from hyperpocket.auth.github.token_context import GitHubTokenAuthContext
5
- from hyperpocket.auth.google.context import GoogleAuthContext
6
- from hyperpocket.auth.google.oauth2_context import GoogleOAuth2AuthContext
7
2
  from hyperpocket.auth.handler import AuthHandlerInterface
8
- from hyperpocket.auth.linear.token_context import LinearTokenAuthContext
9
3
  from hyperpocket.auth.provider import AuthProvider
10
- from hyperpocket.auth.slack.oauth2_context import SlackOAuth2AuthContext
11
- from hyperpocket.auth.slack.token_context import SlackTokenAuthContext
12
4
  from hyperpocket.util.find_all_leaf_class_in_package import find_all_leaf_class_in_package
13
5
 
14
6
  PREBUILT_AUTH_HANDLERS = find_all_leaf_class_in_package("hyperpocket.auth", AuthHandlerInterface)
@@ -0,0 +1,13 @@
1
+ from hyperpocket.auth.context import AuthContext
2
+
3
+
4
+ class GumLoopContext(AuthContext):
5
+ _ACCESS_TOKEN_KEY: str = "GUMLOOP_TOKEN"
6
+
7
+ def to_dict(self) -> dict[str, str]:
8
+ return {self._ACCESS_TOKEN_KEY: self.access_token}
9
+
10
+ def to_profiled_dict(self, profile: str) -> dict[str, str]:
11
+ return {
12
+ f"{profile.upper()}_{self._ACCESS_TOKEN_KEY}": self.access_token,
13
+ }
@@ -0,0 +1,15 @@
1
+ from hyperpocket.auth.gumloop.context import GumLoopContext
2
+ from hyperpocket.auth.gumloop.token_schema import GumloopTokenResponse
3
+
4
+
5
+ class GumLoopTokenContext(GumLoopContext):
6
+
7
+ @classmethod
8
+ def from_gumloop_token_response(cls, response: GumloopTokenResponse):
9
+ description = "Gumloop Token Context logged in"
10
+
11
+ return cls(
12
+ access_token=response.access_token,
13
+ description=description,
14
+ expires_at=None
15
+ )
@@ -0,0 +1,66 @@
1
+ from typing import Optional
2
+ from urllib.parse import urljoin, urlencode
3
+
4
+ from hyperpocket.auth import AuthHandlerInterface, AuthContext, AuthProvider
5
+ from hyperpocket.auth.gumloop.token_context import GumLoopTokenContext
6
+ from hyperpocket.auth.gumloop.token_schema import GumloopTokenRequest, GumloopTokenResponse
7
+ from hyperpocket.auth.schema import AuthenticateRequest
8
+ from hyperpocket.config import config
9
+ from hyperpocket.futures import FutureStore
10
+
11
+
12
+ class GumloopTokenAuthHandler(AuthHandlerInterface):
13
+ name: str = "gumloop-token"
14
+ description: str = "This handler is used to authenticate users using the gumloop token"
15
+ scoped: bool = False
16
+
17
+ _TOKEN_URL = urljoin(config.public_base_url + "/", f"{config.callback_url_rewrite_prefix}/auth/token")
18
+
19
+ @staticmethod
20
+ def provider() -> AuthProvider:
21
+ return AuthProvider.GUMLOOP
22
+
23
+ @staticmethod
24
+ def provider_default() -> bool:
25
+ return True
26
+
27
+ @staticmethod
28
+ def recommended_scopes() -> set[str]:
29
+ return set()
30
+
31
+ def make_request(self, auth_scopes: Optional[list[str]] = None, **kwargs) -> AuthenticateRequest:
32
+ return GumloopTokenRequest()
33
+
34
+ def prepare(self, auth_req: AuthenticateRequest, thread_id: str, profile: str, future_uid: str, *args,
35
+ **kwargs) -> str:
36
+ redirect_uri = urljoin(
37
+ config.public_base_url + "/",
38
+ f"{config.callback_url_rewrite_prefix}/auth/gumloop/token/callback",
39
+ )
40
+ auth_url = self._make_auth_url(auth_req=auth_req, redirect_uri=redirect_uri, state=future_uid)
41
+ FutureStore.create_future(future_uid, data={
42
+ "redirect_uri": redirect_uri,
43
+ "thread_id": thread_id,
44
+ "profile": profile,
45
+ })
46
+
47
+ return f'User needs to authenticate using the following URL: {auth_url}'
48
+
49
+ async def authenticate(self, auth_req: AuthenticateRequest, future_uid: str, *args, **kwargs) -> AuthContext:
50
+ future_data = FutureStore.get_future(future_uid)
51
+ access_token = await future_data.future
52
+
53
+ response = GumloopTokenResponse(access_token=access_token)
54
+ context = GumLoopTokenContext.from_gumloop_token_response(response)
55
+
56
+ return context
57
+
58
+ async def refresh(self, auth_req: AuthenticateRequest, context: AuthContext, *args, **kwargs) -> AuthContext:
59
+ raise Exception("gumloop token doesn't support refresh")
60
+
61
+ def _make_auth_url(self, auth_req: AuthenticateRequest, redirect_uri: str, state: str):
62
+ params = {
63
+ "redirect_uri": redirect_uri,
64
+ "state": state,
65
+ }
66
+ return f"{self._TOKEN_URL}?{urlencode(params)}"
@@ -0,0 +1,8 @@
1
+ from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
2
+
3
+
4
+ class GumloopTokenRequest(AuthenticateRequest):
5
+ pass
6
+
7
+ class GumloopTokenResponse(AuthenticateResponse):
8
+ access_token: str
@@ -6,7 +6,7 @@ class LinearTokenAuthContext(LinearAuthContext):
6
6
 
7
7
  @classmethod
8
8
  def from_linear_token_response(cls, response: LinearTokenResponse):
9
- description = f'Linear Token Context logged in'
9
+ description = 'Linear Token Context logged in'
10
10
 
11
11
  return cls(
12
12
  access_token=response.access_token,
@@ -0,0 +1,28 @@
1
+ ### Notion Token Auth
2
+
3
+ This module provides authentication using Notion tokens.
4
+
5
+ 1. To use this authentication in your tool, include the following in your `config.toml`:
6
+
7
+ ```toml
8
+ [auth]
9
+ auth_provider = "notion"
10
+ auth_handler = "notion-token"
11
+ scopes = []
12
+ ```
13
+
14
+ 2. To use it with `function_tool`, you can define your function as follows:
15
+
16
+ ```python
17
+ from hyperpocket.tool import function_tool
18
+ from hyperpocket.auth import AuthProvider
19
+
20
+
21
+ @function_tool(
22
+ auth_provider=AuthProvider.NOTION
23
+ )
24
+ def my_function(**kwargs):
25
+ token = kwargs["NOTION_TOKEN"]
26
+
27
+ # ...
28
+ ```
@@ -0,0 +1,15 @@
1
+
2
+ from hyperpocket.auth.context import AuthContext
3
+
4
+ class NotionAuthContext(AuthContext):
5
+ _ACCESS_TOKEN_KEY: str = "NOTION_TOKEN"
6
+
7
+ def to_dict(self) -> dict[str, str]:
8
+ return {
9
+ self._ACCESS_TOKEN_KEY: self.access_token,
10
+ }
11
+
12
+ def to_profiled_dict(self, profile: str) -> dict[str, str]:
13
+ return {
14
+ f"{profile.upper()}_{self._ACCESS_TOKEN_KEY}": self.access_token,
15
+ }
@@ -0,0 +1,14 @@
1
+ from hyperpocket.auth.notion.context import NotionAuthContext
2
+ from hyperpocket.auth.notion.token_schema import NotionTokenResponse
3
+
4
+
5
+ class NotionTokenAuthContext(NotionAuthContext):
6
+ @classmethod
7
+ def from_notion_token_response(cls, response: NotionTokenResponse):
8
+ description = 'Notion Token Context logged in'
9
+
10
+ return cls(
11
+ access_token=response.access_token,
12
+ description=description,
13
+ expires_at=None
14
+ )
@@ -0,0 +1,65 @@
1
+
2
+ from typing import Optional
3
+ from urllib.parse import urljoin, urlencode
4
+
5
+ from hyperpocket.auth import AuthProvider
6
+ from hyperpocket.auth.context import AuthContext
7
+ from hyperpocket.auth.handler import AuthHandlerInterface
8
+ from hyperpocket.auth.notion.token_context import NotionTokenAuthContext
9
+ from hyperpocket.auth.notion.token_schema import NotionTokenResponse, NotionTokenRequest
10
+ from hyperpocket.config import config
11
+ from hyperpocket.futures import FutureStore
12
+
13
+
14
+ class NotionTokenAuthHandler(AuthHandlerInterface):
15
+ name: str = "notion-token"
16
+ description: str = "This handler is used to authenticate users using the Notion token."
17
+ scoped: bool = False
18
+
19
+ _TOKEN_URL: str = urljoin(config.public_base_url + "/", f"{config.callback_url_rewrite_prefix}/auth/token")
20
+
21
+ @staticmethod
22
+ def provider() -> AuthProvider:
23
+ return AuthProvider.NOTION
24
+
25
+ @staticmethod
26
+ def recommended_scopes() -> set[str]:
27
+ return set()
28
+
29
+ def prepare(self, auth_req: NotionTokenRequest, thread_id: str, profile: str,
30
+ future_uid: str, *args, **kwargs) -> str:
31
+ redirect_uri = urljoin(
32
+ config.public_base_url + "/",
33
+ f"{config.callback_url_rewrite_prefix}/auth/notion/token/callback",
34
+ )
35
+ url = self._make_auth_url(auth_req=auth_req, redirect_uri=redirect_uri, state=future_uid)
36
+ FutureStore.create_future(future_uid, data={
37
+ "redirect_uri": redirect_uri,
38
+ "thread_id": thread_id,
39
+ "profile": profile,
40
+ })
41
+
42
+ return f'User needs to authenticate using the following URL: {url}'
43
+
44
+ async def authenticate(self, auth_req: NotionTokenRequest, future_uid: str, *args, **kwargs) -> AuthContext:
45
+ future_data = FutureStore.get_future(future_uid)
46
+ access_token = await future_data.future
47
+
48
+ response = NotionTokenResponse(access_token=access_token)
49
+ context = NotionTokenAuthContext.from_notion_token_response(response)
50
+
51
+ return context
52
+
53
+ async def refresh(self, auth_req: NotionTokenRequest, context: AuthContext, *args, **kwargs) -> AuthContext:
54
+ raise Exception("Notion token doesn't support refresh")
55
+
56
+ def _make_auth_url(self, auth_req: NotionTokenRequest, redirect_uri: str, state: str):
57
+ params = {
58
+ "redirect_uri": redirect_uri,
59
+ "state": state,
60
+ }
61
+ auth_url = f"{self._TOKEN_URL}?{urlencode(params)}"
62
+ return auth_url
63
+
64
+ def make_request(self, auth_scopes: Optional[list[str]] = None, **kwargs) -> NotionTokenRequest:
65
+ return NotionTokenRequest()
@@ -0,0 +1,10 @@
1
+
2
+ from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
3
+
4
+
5
+ class NotionTokenRequest(AuthenticateRequest):
6
+ pass
7
+
8
+
9
+ class NotionTokenResponse(AuthenticateResponse):
10
+ access_token: str
@@ -2,11 +2,14 @@ from enum import Enum
2
2
 
3
3
 
4
4
  class AuthProvider(Enum):
5
- SLACK = 'slack'
6
- LINEAR = 'linear'
7
- GITHUB = 'github'
8
- GOOGLE = 'google'
9
- CALENDLY = 'calendly'
5
+ SLACK = "slack"
6
+ LINEAR = "linear"
7
+ GITHUB = "github"
8
+ GOOGLE = "google"
9
+ CALENDLY = "calendly"
10
+ NOTION = "notion"
11
+ REDDIT = "reddit"
12
+ GUMLOOP = "gumloop"
10
13
 
11
14
  @classmethod
12
15
  def get_auth_provider(cls, auth_provider_name: str) -> "AuthProvider":
@@ -0,0 +1,15 @@
1
+ from hyperpocket.auth.context import AuthContext
2
+
3
+
4
+ class RedditAuthContext(AuthContext):
5
+ _ACCESS_TOKEN_KEY: str = "REDDIT_BOT_TOKEN"
6
+
7
+ def to_dict(self) -> dict[str, str]:
8
+ return {
9
+ self._ACCESS_TOKEN_KEY: self.access_token,
10
+ }
11
+
12
+ def to_profiled_dict(self, profile: str) -> dict[str, str]:
13
+ return {
14
+ f"{profile.upper()}_{self._ACCESS_TOKEN_KEY}": self.access_token,
15
+ }
@@ -0,0 +1,32 @@
1
+ from datetime import datetime, timedelta, timezone
2
+ from typing import Optional
3
+
4
+ from pydantic import Field
5
+
6
+ from hyperpocket.auth.reddit.context import RedditAuthContext
7
+ from hyperpocket.auth.reddit.oauth2_schema import RedditOAuth2Response
8
+
9
+
10
+ class RedditOAuth2AuthContext(RedditAuthContext):
11
+ refresh_token: Optional[str] = Field(default=None, description="refresh token")
12
+
13
+ @classmethod
14
+ def from_reddit_oauth2_response(cls, response: RedditOAuth2Response):
15
+ now = datetime.now(tz=timezone.utc)
16
+
17
+ access_token = response.access_token
18
+ refresh_token = response.refresh_token
19
+ expires_in = response.expires_in
20
+
21
+ if expires_in:
22
+ expires_at = now + timedelta(seconds=expires_in)
23
+ else:
24
+ expires_at = None
25
+
26
+ return cls(
27
+ access_token=access_token,
28
+ refresh_token=refresh_token,
29
+ expires_at=expires_at,
30
+ description="Reddit OAuth2 authentication context",
31
+ detail=response,
32
+ )
@@ -0,0 +1,151 @@
1
+ import base64
2
+ from os import access
3
+ from typing import Optional
4
+ from urllib.parse import urljoin, urlencode
5
+
6
+ import httpx
7
+
8
+ from hyperpocket.auth import AuthProvider
9
+ from hyperpocket.auth.context import AuthContext
10
+ from hyperpocket.auth.handler import AuthHandlerInterface
11
+ from hyperpocket.auth.reddit.oauth2_context import RedditOAuth2AuthContext
12
+ from hyperpocket.auth.reddit.oauth2_schema import (
13
+ RedditOAuth2Response,
14
+ RedditOAuth2Request,
15
+ )
16
+ from hyperpocket.config import config as config
17
+ from hyperpocket.futures import FutureStore
18
+
19
+
20
+ class RedditOAuth2AuthHandler(AuthHandlerInterface):
21
+ _REDDIT_OAUTH_URL: str = "https://www.reddit.com/api/v1/authorize"
22
+ _REDDIT_TOKEN_URL: str = "https://www.reddit.com/api/v1/access_token"
23
+
24
+ name: str = "reddit-oauth2"
25
+ description: str = "This handler is used to authenticate users using the Reddit OAuth2 authentication method."
26
+ scoped: bool = True
27
+
28
+ @staticmethod
29
+ def provider() -> AuthProvider:
30
+ return AuthProvider.REDDIT
31
+
32
+ @staticmethod
33
+ def provider_default() -> bool:
34
+ return True
35
+
36
+ @staticmethod
37
+ def recommended_scopes() -> set[str]:
38
+ if config.auth.reddit.use_recommended_scope:
39
+ recommended_scopes = {"account", "identity", "read"}
40
+ else:
41
+ recommended_scopes = {}
42
+ return recommended_scopes
43
+
44
+ def prepare(
45
+ self,
46
+ auth_req: RedditOAuth2Request,
47
+ thread_id: str,
48
+ profile: str,
49
+ future_uid: str,
50
+ *args,
51
+ **kwargs,
52
+ ) -> str:
53
+ redirect_uri = urljoin(
54
+ config.public_base_url + "/",
55
+ f"{config.callback_url_rewrite_prefix}/auth/reddit/oauth2/callback",
56
+ )
57
+ print(f"redirect_uri: {redirect_uri}")
58
+ auth_url = self._make_auth_url(
59
+ req=auth_req, redirect_uri=redirect_uri, state=future_uid
60
+ )
61
+
62
+ FutureStore.create_future(
63
+ future_uid,
64
+ data={
65
+ "redirect_uri": redirect_uri,
66
+ "thread_id": thread_id,
67
+ "profile": profile,
68
+ },
69
+ )
70
+
71
+ return f"User needs to authenticate using the following URL: {auth_url}"
72
+
73
+ async def authenticate(
74
+ self, auth_req: RedditOAuth2Request, future_uid: str, *args, **kwargs
75
+ ) -> AuthContext:
76
+ future_data = FutureStore.get_future(future_uid)
77
+ auth_code = await future_data.future
78
+
79
+ basic_auth = f"{auth_req.client_id}:{auth_req.client_secret}"
80
+ basic_auth_encoded = base64.b64encode(basic_auth.encode()).decode()
81
+ async with httpx.AsyncClient() as client:
82
+ resp = await client.post(
83
+ url=self._REDDIT_TOKEN_URL,
84
+ data={
85
+ "code": auth_code,
86
+ "redirect_uri": future_data.data["redirect_uri"],
87
+ "grant_type": "authorization_code",
88
+ },
89
+ headers={
90
+ "Authorization": f"Basic {basic_auth_encoded}",
91
+ },
92
+ )
93
+ if resp.status_code != 200:
94
+ raise Exception(f"failed to authenticate. status_code : {resp.status_code}")
95
+
96
+ resp_json = resp.json()
97
+
98
+ resp_typed = RedditOAuth2Response(**resp_json)
99
+ return RedditOAuth2AuthContext.from_reddit_oauth2_response(resp_typed)
100
+
101
+ async def refresh(
102
+ self, auth_req: RedditOAuth2Request, context: AuthContext, *args, **kwargs
103
+ ) -> AuthContext:
104
+ reddit_context: RedditOAuth2AuthContext = context
105
+ refresh_token = reddit_context.refresh_token
106
+
107
+ async with httpx.AsyncClient() as client:
108
+ resp = await client.post(
109
+ url=self._REDDIT_TOKEN_URL,
110
+ data={
111
+ "client_id": config.auth.reddit.client_id,
112
+ "client_secret": config.auth.reddit.client_secret,
113
+ "grant_type": "refresh_token",
114
+ "refresh_token": refresh_token,
115
+ },
116
+ )
117
+
118
+ if resp.status_code != 200:
119
+ raise Exception(f"failed to refresh. status_code : {resp.status_code}")
120
+
121
+ resp_json = resp.json()
122
+
123
+ new_resp: RedditOAuth2Response = RedditOAuth2Response(
124
+ access_token=resp_json["access_token"],
125
+ token_type=resp_json["token_type"],
126
+ expires_in=resp_json["expires_in"],
127
+ scope=resp_json["scope"],
128
+ )
129
+
130
+ return RedditOAuth2AuthContext.from_reddit_oauth2_response(new_resp)
131
+
132
+ def _make_auth_url(self, req: RedditOAuth2Request, redirect_uri: str, state: str):
133
+ params = {
134
+ "scope": ",".join(req.auth_scopes),
135
+ "client_id": req.client_id,
136
+ "redirect_uri": redirect_uri,
137
+ "state": state,
138
+ "response_type": "code",
139
+ "duration": "permanent",
140
+ }
141
+ auth_url = f"{self._REDDIT_OAUTH_URL}?{urlencode(params)}"
142
+ return auth_url
143
+
144
+ def make_request(
145
+ self, auth_scopes: Optional[list[str]] = None, **kwargs
146
+ ) -> RedditOAuth2Request:
147
+ return RedditOAuth2Request(
148
+ auth_scopes=auth_scopes,
149
+ client_id=config.auth.reddit.client_id,
150
+ client_secret=config.auth.reddit.client_secret,
151
+ )
@@ -0,0 +1,18 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from hyperpocket.auth.schema import AuthenticateRequest, AuthenticateResponse
6
+
7
+
8
+ class RedditOAuth2Request(AuthenticateRequest):
9
+ client_id: str
10
+ client_secret: str
11
+
12
+
13
+ class RedditOAuth2Response(AuthenticateResponse):
14
+ access_token: Optional[str] = None
15
+ token_type: Optional[str] = None
16
+ refresh_token: Optional[str] = None
17
+ expires_in: Optional[int] = None
18
+ scope: Optional[str] = None
@@ -5,7 +5,7 @@ from hyperpocket.auth.slack.token_schema import SlackTokenResponse
5
5
  class SlackTokenAuthContext(SlackAuthContext):
6
6
  @classmethod
7
7
  def from_slack_token_response(cls, response: SlackTokenResponse):
8
- description = f'Slack Token Context logged in'
8
+ description = 'Slack Token Context logged in'
9
9
 
10
10
  return cls(
11
11
  access_token=response.access_token,
hyperpocket/builtin.py ADDED
@@ -0,0 +1,63 @@
1
+ from typing import List
2
+
3
+ from hyperpocket.auth import AuthProvider
4
+ from hyperpocket.pocket_auth import PocketAuth
5
+ from hyperpocket.tool import from_func, Tool
6
+
7
+
8
+ def get_builtin_tools(pocket_auth: PocketAuth) -> List[Tool]:
9
+ """
10
+ Get Builtin Tools
11
+
12
+ Builtin Tool can access to Pocket Core.
13
+ """
14
+
15
+ def __get_current_thread_session_state(thread_id: str = "default") -> str:
16
+ """
17
+ This tool retrieves the current session state list for the specified thread.
18
+
19
+ The tool should only be called when a user explicitly requests to view their session information.
20
+
21
+ The output includes a list of session states in the format:
22
+ - `[AUTH PROVIDER] [state] [scopes, ...] : explanation`
23
+
24
+ It does not contain any sensitive information.
25
+
26
+ Args:
27
+ - thread_id (str): Thread ID
28
+
29
+ Returns:
30
+ - str: A list of session states describing the current authentication status.
31
+
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
+ """
34
+ session_list = pocket_auth.list_session_state(thread_id)
35
+ return str(session_list)
36
+
37
+ def __delete_session(auth_provider_name: str, thread_id: str = "default", profile: str = "default") -> str:
38
+ """
39
+ This tool deletes a saved session for a specified authentication provider in the given thread and profile.
40
+
41
+ The tool should only be called when a user explicitly requests to delete a session.
42
+
43
+ Args:
44
+ - auth_provider_name (str): The name of the authentication provider for the session to be deleted.
45
+ - thread_id (str): Thread ID
46
+ - profile (str): Profile
47
+
48
+ Returns:
49
+ - str: A flag indicating whether the session was successfully deleted.
50
+
51
+ This tool should only be used in response to a user's explicit intent to manage their sessions. Automatic or unauthorized invocation is strictly prohibited.
52
+ """
53
+
54
+ auth_provider = AuthProvider.get_auth_provider(auth_provider_name)
55
+ is_deleted = pocket_auth.delete_session(auth_provider, thread_id, profile)
56
+ return str(is_deleted)
57
+
58
+ builtin_tools = [
59
+ from_func(func=__get_current_thread_session_state),
60
+ from_func(func=__delete_session),
61
+ ]
62
+
63
+ return builtin_tools
@@ -1,12 +1,24 @@
1
1
  import click
2
2
 
3
3
  from hyperpocket.cli.pull import pull
4
+ from hyperpocket.cli.sync import sync
5
+ from hyperpocket.cli.eject import eject
6
+ from hyperpocket.cli.auth import create_token_auth_template
4
7
 
5
8
 
6
9
  @click.group()
7
10
  def cli():
8
11
  pass
9
12
 
13
+ @click.group()
14
+ def devtool():
15
+ pass
16
+
17
+ cli.add_command(devtool)
18
+ devtool.add_command(create_token_auth_template)
19
+
10
20
  cli.add_command(pull)
21
+ cli.add_command(sync)
22
+ cli.add_command(eject)
11
23
 
12
24
  cli()