botrun-flow-lang 5.10.32__py3-none-any.whl → 5.10.82__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.
Files changed (84) hide show
  1. botrun_flow_lang/api/auth_api.py +39 -39
  2. botrun_flow_lang/api/auth_utils.py +183 -183
  3. botrun_flow_lang/api/botrun_back_api.py +65 -65
  4. botrun_flow_lang/api/flow_api.py +3 -3
  5. botrun_flow_lang/api/hatch_api.py +481 -481
  6. botrun_flow_lang/api/langgraph_api.py +796 -796
  7. botrun_flow_lang/api/line_bot_api.py +1357 -1357
  8. botrun_flow_lang/api/model_api.py +300 -300
  9. botrun_flow_lang/api/rate_limit_api.py +32 -32
  10. botrun_flow_lang/api/routes.py +79 -79
  11. botrun_flow_lang/api/search_api.py +53 -53
  12. botrun_flow_lang/api/storage_api.py +316 -316
  13. botrun_flow_lang/api/subsidy_api.py +290 -290
  14. botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
  15. botrun_flow_lang/api/user_setting_api.py +70 -70
  16. botrun_flow_lang/api/version_api.py +31 -31
  17. botrun_flow_lang/api/youtube_api.py +26 -26
  18. botrun_flow_lang/constants.py +13 -13
  19. botrun_flow_lang/langgraph_agents/agents/agent_runner.py +174 -174
  20. botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
  21. botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
  22. botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
  23. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
  24. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
  25. botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +548 -548
  26. botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
  27. botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
  28. botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
  29. botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
  30. botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
  31. botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
  32. botrun_flow_lang/langgraph_agents/agents/util/local_files.py +345 -345
  33. botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
  34. botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
  35. botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +160 -160
  36. botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
  37. botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
  38. botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
  39. botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
  40. botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
  41. botrun_flow_lang/llm_agent/llm_agent.py +19 -19
  42. botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
  43. botrun_flow_lang/log/.gitignore +2 -2
  44. botrun_flow_lang/main.py +61 -61
  45. botrun_flow_lang/main_fast.py +51 -51
  46. botrun_flow_lang/mcp_server/__init__.py +10 -10
  47. botrun_flow_lang/mcp_server/default_mcp.py +711 -711
  48. botrun_flow_lang/models/nodes/utils.py +205 -205
  49. botrun_flow_lang/models/token_usage.py +34 -34
  50. botrun_flow_lang/requirements.txt +21 -21
  51. botrun_flow_lang/services/base/firestore_base.py +30 -30
  52. botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
  53. botrun_flow_lang/services/hatch/hatch_fs_store.py +372 -372
  54. botrun_flow_lang/services/storage/storage_cs_store.py +202 -202
  55. botrun_flow_lang/services/storage/storage_factory.py +12 -12
  56. botrun_flow_lang/services/storage/storage_store.py +65 -65
  57. botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
  58. botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
  59. botrun_flow_lang/static/docs/tools/index.html +926 -926
  60. botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
  61. botrun_flow_lang/tests/api_stress_test.py +357 -357
  62. botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
  63. botrun_flow_lang/tests/test_botrun_app.py +46 -46
  64. botrun_flow_lang/tests/test_html_util.py +31 -31
  65. botrun_flow_lang/tests/test_img_analyzer.py +190 -190
  66. botrun_flow_lang/tests/test_img_util.py +39 -39
  67. botrun_flow_lang/tests/test_local_files.py +114 -114
  68. botrun_flow_lang/tests/test_mermaid_util.py +103 -103
  69. botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
  70. botrun_flow_lang/tests/test_plotly_util.py +151 -151
  71. botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
  72. botrun_flow_lang/tools/generate_docs.py +133 -133
  73. botrun_flow_lang/tools/templates/tools.html +153 -153
  74. botrun_flow_lang/utils/__init__.py +7 -7
  75. botrun_flow_lang/utils/botrun_logger.py +344 -344
  76. botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
  77. botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
  78. botrun_flow_lang/utils/google_drive_utils.py +654 -654
  79. botrun_flow_lang/utils/langchain_utils.py +324 -324
  80. botrun_flow_lang/utils/yaml_utils.py +9 -9
  81. {botrun_flow_lang-5.10.32.dist-info → botrun_flow_lang-5.10.82.dist-info}/METADATA +2 -2
  82. botrun_flow_lang-5.10.82.dist-info/RECORD +99 -0
  83. botrun_flow_lang-5.10.32.dist-info/RECORD +0 -99
  84. {botrun_flow_lang-5.10.32.dist-info → botrun_flow_lang-5.10.82.dist-info}/WHEEL +0 -0
@@ -1,40 +1,40 @@
1
- from fastapi import APIRouter, HTTPException, Form
2
- from typing import Dict, Any
3
-
4
- from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
5
-
6
- router = APIRouter(prefix="/auth")
7
-
8
-
9
- @router.post("/token_verify")
10
- async def verify_token(access_token: str = Form(...)) -> Dict[Any, Any]:
11
- """
12
- 驗證 access token 的有效性。
13
-
14
- Args:
15
- access_token: 要驗證的 access token (from form data)
16
-
17
- Returns:
18
- 包含驗證結果的字典:
19
- {
20
- "is_success": true,
21
- "username": "user@example.com"
22
- }
23
-
24
- Raises:
25
- HTTPException: 當 token 無效 (401) 或後端 API 無法訪問時 (500)
26
- """
27
- try:
28
- client = TokenVerifyClient()
29
- result = await client.verify_token(access_token)
30
- return result
31
- except ValueError as e:
32
- error_msg = str(e).lower()
33
- # Token 無效時回傳 401
34
- if "invalid" in error_msg and "token" in error_msg:
35
- raise HTTPException(status_code=401, detail="Invalid access token")
36
- # 請求格式錯誤時回傳 400
37
- elif "bad request" in error_msg:
38
- raise HTTPException(status_code=400, detail="Bad request: missing or invalid token format")
39
- # 其他錯誤回傳 500
1
+ from fastapi import APIRouter, HTTPException, Form
2
+ from typing import Dict, Any
3
+
4
+ from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
5
+
6
+ router = APIRouter(prefix="/auth")
7
+
8
+
9
+ @router.post("/token_verify")
10
+ async def verify_token(access_token: str = Form(...)) -> Dict[Any, Any]:
11
+ """
12
+ 驗證 access token 的有效性。
13
+
14
+ Args:
15
+ access_token: 要驗證的 access token (from form data)
16
+
17
+ Returns:
18
+ 包含驗證結果的字典:
19
+ {
20
+ "is_success": true,
21
+ "username": "user@example.com"
22
+ }
23
+
24
+ Raises:
25
+ HTTPException: 當 token 無效 (401) 或後端 API 無法訪問時 (500)
26
+ """
27
+ try:
28
+ client = TokenVerifyClient()
29
+ result = await client.verify_token(access_token)
30
+ return result
31
+ except ValueError as e:
32
+ error_msg = str(e).lower()
33
+ # Token 無效時回傳 401
34
+ if "invalid" in error_msg and "token" in error_msg:
35
+ raise HTTPException(status_code=401, detail="Invalid access token")
36
+ # 請求格式錯誤時回傳 400
37
+ elif "bad request" in error_msg:
38
+ raise HTTPException(status_code=400, detail="Bad request: missing or invalid token format")
39
+ # 其他錯誤回傳 500
40
40
  raise HTTPException(status_code=500, detail=str(e))
@@ -1,183 +1,183 @@
1
- """Utility functions for authentication shared across API modules."""
2
-
3
- import os
4
- import logging
5
- from fastapi import HTTPException, Depends
6
- from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
7
- from pydantic import BaseModel
8
- from typing import TYPE_CHECKING
9
-
10
- from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
11
-
12
- if TYPE_CHECKING:
13
- from botrun_flow_lang.services.hatch.hatch_fs_store import HatchFsStore
14
-
15
- # Reusable HTTPBearer security scheme
16
- security = HTTPBearer()
17
-
18
-
19
- class CurrentUser(BaseModel):
20
- """Current authenticated user information."""
21
- user_id: str # From botrun_back API's username field or admin for universal tokens
22
- is_admin: bool = False # Universal token users are marked as admin
23
-
24
- def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
25
- """Verify that the provided Bearer token is in the allowed JWT_TOKENS list.
26
-
27
- Args:
28
- credentials: Parsed `Authorization` header credentials supplied by FastAPI's
29
- dependency injection using `HTTPBearer`.
30
-
31
- Raises:
32
- HTTPException: If the token is missing or not present in the allowed list.
33
- """
34
- jwt_tokens_env = os.getenv("JWT_TOKENS", "")
35
- tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
36
- if credentials.credentials not in tokens:
37
- raise HTTPException(
38
- status_code=401,
39
- detail="Invalid authentication credentials",
40
- headers={"WWW-Authenticate": "Bearer"},
41
- )
42
- return True
43
-
44
-
45
- async def verify_jwt_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> CurrentUser:
46
- """Verify JWT token using dual authentication mechanism.
47
-
48
- First checks if token is in JWT_TOKENS (universal tokens for testing),
49
- then calls botrun_back API for user authentication using TokenVerifyClient.
50
-
51
- Args:
52
- credentials: Parsed Authorization header credentials
53
-
54
- Returns:
55
- CurrentUser: Authenticated user information
56
-
57
- Raises:
58
- HTTPException: If authentication fails
59
- """
60
- # 1. Check universal tokens (existing logic)
61
- jwt_tokens_env = os.getenv("JWT_TOKENS", "")
62
- tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
63
-
64
- if credentials.credentials in tokens:
65
- return CurrentUser(user_id="admin", is_admin=True)
66
-
67
- # 2. Use TokenVerifyClient for authentication (with IAP support)
68
- try:
69
- client = TokenVerifyClient()
70
- result = await client.verify_token(credentials.credentials)
71
-
72
- if result.get('is_success', False):
73
- username = result.get('username', '')
74
- if username:
75
- return CurrentUser(user_id=username, is_admin=False)
76
- else:
77
- raise HTTPException(status_code=401, detail="Invalid response from auth service")
78
- else:
79
- raise HTTPException(status_code=401, detail="Invalid authentication credentials")
80
-
81
- except ValueError as e:
82
- error_msg = str(e).lower()
83
- if "invalid" in error_msg and "token" in error_msg:
84
- raise HTTPException(status_code=401, detail="Invalid authentication credentials")
85
- elif "not configured" in error_msg:
86
- raise HTTPException(status_code=500, detail="Authentication service not configured")
87
- else:
88
- raise HTTPException(status_code=500, detail="Authentication service unavailable")
89
- except Exception as e:
90
- logging.error(f"Unexpected error during authentication: {e}")
91
- raise HTTPException(status_code=500, detail="Authentication error")
92
-
93
-
94
- def verify_user_permission(current_user: CurrentUser, target_user_id: str):
95
- """Verify user has permission to access resources for the specified user_id.
96
-
97
- Args:
98
- current_user: Current authenticated user
99
- target_user_id: Target user ID to check permission for
100
-
101
- Raises:
102
- HTTPException: If user lacks permission
103
- """
104
- if current_user.is_admin:
105
- return # Admin can access all resources
106
-
107
- if current_user.user_id != target_user_id:
108
- raise HTTPException(
109
- status_code=403,
110
- detail="Insufficient permissions to access this resource"
111
- )
112
-
113
-
114
- async def verify_hatch_owner(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
115
- """Verify user is the owner of the specified hatch.
116
-
117
- Args:
118
- current_user: Current authenticated user
119
- hatch_id: Hatch ID to check ownership for
120
- store: Hatch store instance
121
-
122
- Raises:
123
- HTTPException: If user is not the owner or hatch not found
124
- """
125
- if current_user.is_admin:
126
- return # Admin can access all hatches
127
-
128
- hatch = await store.get_hatch(hatch_id)
129
- if not hatch:
130
- raise HTTPException(status_code=404, detail="Hatch not found")
131
-
132
- if hatch.user_id != current_user.user_id:
133
- raise HTTPException(
134
- status_code=403,
135
- detail="Insufficient permissions to access this hatch"
136
- )
137
-
138
-
139
- async def verify_hatch_access(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
140
- """Verify user can read the hatch (owner or shared with user).
141
-
142
- Args:
143
- current_user: Current authenticated user
144
- hatch_id: Hatch ID to check access for
145
- store: Hatch store instance
146
-
147
- Raises:
148
- HTTPException: If user lacks access or hatch not found
149
- """
150
- if current_user.is_admin:
151
- return # Admin can access all hatches
152
-
153
- hatch = await store.get_hatch(hatch_id)
154
- if not hatch:
155
- raise HTTPException(status_code=404, detail="Hatch not found")
156
-
157
- # Check if user is owner
158
- if hatch.user_id == current_user.user_id:
159
- return
160
-
161
- # Check if hatch is shared with user
162
- is_shared, _ = await store.is_hatch_shared_with_user(hatch_id, current_user.user_id)
163
- if not is_shared:
164
- raise HTTPException(
165
- status_code=403,
166
- detail="Insufficient permissions to access this hatch"
167
- )
168
-
169
-
170
- def verify_admin_permission(current_user: CurrentUser):
171
- """Verify user has admin permissions.
172
-
173
- Args:
174
- current_user: Current authenticated user
175
-
176
- Raises:
177
- HTTPException: If user is not admin
178
- """
179
- if not current_user.is_admin:
180
- raise HTTPException(
181
- status_code=403,
182
- detail="Admin permissions required"
183
- )
1
+ """Utility functions for authentication shared across API modules."""
2
+
3
+ import os
4
+ import logging
5
+ from fastapi import HTTPException, Depends
6
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
7
+ from pydantic import BaseModel
8
+ from typing import TYPE_CHECKING
9
+
10
+ from botrun_flow_lang.utils.clients.token_verify_client import TokenVerifyClient
11
+
12
+ if TYPE_CHECKING:
13
+ from botrun_flow_lang.services.hatch.hatch_fs_store import HatchFsStore
14
+
15
+ # Reusable HTTPBearer security scheme
16
+ security = HTTPBearer()
17
+
18
+
19
+ class CurrentUser(BaseModel):
20
+ """Current authenticated user information."""
21
+ user_id: str # From botrun_back API's username field or admin for universal tokens
22
+ is_admin: bool = False # Universal token users are marked as admin
23
+
24
+ def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
25
+ """Verify that the provided Bearer token is in the allowed JWT_TOKENS list.
26
+
27
+ Args:
28
+ credentials: Parsed `Authorization` header credentials supplied by FastAPI's
29
+ dependency injection using `HTTPBearer`.
30
+
31
+ Raises:
32
+ HTTPException: If the token is missing or not present in the allowed list.
33
+ """
34
+ jwt_tokens_env = os.getenv("JWT_TOKENS", "")
35
+ tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
36
+ if credentials.credentials not in tokens:
37
+ raise HTTPException(
38
+ status_code=401,
39
+ detail="Invalid authentication credentials",
40
+ headers={"WWW-Authenticate": "Bearer"},
41
+ )
42
+ return True
43
+
44
+
45
+ async def verify_jwt_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> CurrentUser:
46
+ """Verify JWT token using dual authentication mechanism.
47
+
48
+ First checks if token is in JWT_TOKENS (universal tokens for testing),
49
+ then calls botrun_back API for user authentication using TokenVerifyClient.
50
+
51
+ Args:
52
+ credentials: Parsed Authorization header credentials
53
+
54
+ Returns:
55
+ CurrentUser: Authenticated user information
56
+
57
+ Raises:
58
+ HTTPException: If authentication fails
59
+ """
60
+ # 1. Check universal tokens (existing logic)
61
+ jwt_tokens_env = os.getenv("JWT_TOKENS", "")
62
+ tokens = [t.strip() for t in jwt_tokens_env.split("\n") if t.strip()]
63
+
64
+ if credentials.credentials in tokens:
65
+ return CurrentUser(user_id="admin", is_admin=True)
66
+
67
+ # 2. Use TokenVerifyClient for authentication (with IAP support)
68
+ try:
69
+ client = TokenVerifyClient()
70
+ result = await client.verify_token(credentials.credentials)
71
+
72
+ if result.get('is_success', False):
73
+ username = result.get('username', '')
74
+ if username:
75
+ return CurrentUser(user_id=username, is_admin=False)
76
+ else:
77
+ raise HTTPException(status_code=401, detail="Invalid response from auth service")
78
+ else:
79
+ raise HTTPException(status_code=401, detail="Invalid authentication credentials")
80
+
81
+ except ValueError as e:
82
+ error_msg = str(e).lower()
83
+ if "invalid" in error_msg and "token" in error_msg:
84
+ raise HTTPException(status_code=401, detail="Invalid authentication credentials")
85
+ elif "not configured" in error_msg:
86
+ raise HTTPException(status_code=500, detail="Authentication service not configured")
87
+ else:
88
+ raise HTTPException(status_code=500, detail="Authentication service unavailable")
89
+ except Exception as e:
90
+ logging.error(f"Unexpected error during authentication: {e}")
91
+ raise HTTPException(status_code=500, detail="Authentication error")
92
+
93
+
94
+ def verify_user_permission(current_user: CurrentUser, target_user_id: str):
95
+ """Verify user has permission to access resources for the specified user_id.
96
+
97
+ Args:
98
+ current_user: Current authenticated user
99
+ target_user_id: Target user ID to check permission for
100
+
101
+ Raises:
102
+ HTTPException: If user lacks permission
103
+ """
104
+ if current_user.is_admin:
105
+ return # Admin can access all resources
106
+
107
+ if current_user.user_id != target_user_id:
108
+ raise HTTPException(
109
+ status_code=403,
110
+ detail="Insufficient permissions to access this resource"
111
+ )
112
+
113
+
114
+ async def verify_hatch_owner(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
115
+ """Verify user is the owner of the specified hatch.
116
+
117
+ Args:
118
+ current_user: Current authenticated user
119
+ hatch_id: Hatch ID to check ownership for
120
+ store: Hatch store instance
121
+
122
+ Raises:
123
+ HTTPException: If user is not the owner or hatch not found
124
+ """
125
+ if current_user.is_admin:
126
+ return # Admin can access all hatches
127
+
128
+ hatch = await store.get_hatch(hatch_id)
129
+ if not hatch:
130
+ raise HTTPException(status_code=404, detail="Hatch not found")
131
+
132
+ if hatch.user_id != current_user.user_id:
133
+ raise HTTPException(
134
+ status_code=403,
135
+ detail="Insufficient permissions to access this hatch"
136
+ )
137
+
138
+
139
+ async def verify_hatch_access(current_user: CurrentUser, hatch_id: str, store: "HatchFsStore"):
140
+ """Verify user can read the hatch (owner or shared with user).
141
+
142
+ Args:
143
+ current_user: Current authenticated user
144
+ hatch_id: Hatch ID to check access for
145
+ store: Hatch store instance
146
+
147
+ Raises:
148
+ HTTPException: If user lacks access or hatch not found
149
+ """
150
+ if current_user.is_admin:
151
+ return # Admin can access all hatches
152
+
153
+ hatch = await store.get_hatch(hatch_id)
154
+ if not hatch:
155
+ raise HTTPException(status_code=404, detail="Hatch not found")
156
+
157
+ # Check if user is owner
158
+ if hatch.user_id == current_user.user_id:
159
+ return
160
+
161
+ # Check if hatch is shared with user
162
+ is_shared, _ = await store.is_hatch_shared_with_user(hatch_id, current_user.user_id)
163
+ if not is_shared:
164
+ raise HTTPException(
165
+ status_code=403,
166
+ detail="Insufficient permissions to access this hatch"
167
+ )
168
+
169
+
170
+ def verify_admin_permission(current_user: CurrentUser):
171
+ """Verify user has admin permissions.
172
+
173
+ Args:
174
+ current_user: Current authenticated user
175
+
176
+ Raises:
177
+ HTTPException: If user is not admin
178
+ """
179
+ if not current_user.is_admin:
180
+ raise HTTPException(
181
+ status_code=403,
182
+ detail="Admin permissions required"
183
+ )
@@ -1,65 +1,65 @@
1
- import os
2
- from fastapi import APIRouter, HTTPException
3
- import aiohttp
4
- from urllib.parse import urljoin
5
- import json
6
- from google.auth.transport.requests import Request
7
- from google.oauth2 import service_account
8
-
9
- router = APIRouter()
10
-
11
- router = APIRouter(prefix="/botrun_back")
12
-
13
-
14
- def normalize_url(base_url, path):
15
- f_base_url = base_url
16
- if not f_base_url.endswith("/"):
17
- f_base_url = f_base_url + "/"
18
- return urljoin(f_base_url, path)
19
-
20
-
21
- @router.get("/info")
22
- async def get_botrun_back_info():
23
- """Get information from the botrun backend service."""
24
- try:
25
- botrun_base_url = os.environ.get("BOTRUN_BACK_API_BASE")
26
- if not botrun_base_url:
27
- raise HTTPException(
28
- status_code=500, detail="BOTRUN_BACK_API_BASE not configured"
29
- )
30
-
31
- info_url = normalize_url(botrun_base_url, "botrun/info")
32
- iap_client_id = os.getenv("IAP_CLIENT_ID")
33
- iap_service_account_key_file = os.getenv("IAP_SERVICE_ACCOUNT_KEY_FILE")
34
- headers = {}
35
- if iap_client_id and iap_service_account_key_file:
36
- try:
37
- credentials = (
38
- service_account.IDTokenCredentials.from_service_account_file(
39
- iap_service_account_key_file,
40
- target_audience=iap_client_id,
41
- )
42
- )
43
- credentials.refresh(Request())
44
- token = credentials.token
45
- headers = {"Authorization": f"Bearer {token}"}
46
- except Exception as e:
47
- raise ValueError(f"Error generating IAP JWT token: {str(e)}")
48
-
49
- async with aiohttp.ClientSession() as session:
50
- async with session.get(info_url, headers=headers) as response:
51
- if response.status != 200:
52
- error_text = await response.text()
53
- raise HTTPException(
54
- status_code=response.status,
55
- detail=f"Error calling botrun backend: {error_text}",
56
- )
57
-
58
- text = await response.text()
59
- return json.loads(text)
60
- except aiohttp.ClientError as e:
61
- raise HTTPException(
62
- status_code=500, detail=f"Error connecting to botrun backend: {str(e)}"
63
- )
64
- except Exception as e:
65
- raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
1
+ import os
2
+ from fastapi import APIRouter, HTTPException
3
+ import aiohttp
4
+ from urllib.parse import urljoin
5
+ import json
6
+ from google.auth.transport.requests import Request
7
+ from google.oauth2 import service_account
8
+
9
+ router = APIRouter()
10
+
11
+ router = APIRouter(prefix="/botrun_back")
12
+
13
+
14
+ def normalize_url(base_url, path):
15
+ f_base_url = base_url
16
+ if not f_base_url.endswith("/"):
17
+ f_base_url = f_base_url + "/"
18
+ return urljoin(f_base_url, path)
19
+
20
+
21
+ @router.get("/info")
22
+ async def get_botrun_back_info():
23
+ """Get information from the botrun backend service."""
24
+ try:
25
+ botrun_base_url = os.environ.get("BOTRUN_BACK_API_BASE")
26
+ if not botrun_base_url:
27
+ raise HTTPException(
28
+ status_code=500, detail="BOTRUN_BACK_API_BASE not configured"
29
+ )
30
+
31
+ info_url = normalize_url(botrun_base_url, "botrun/info")
32
+ iap_client_id = os.getenv("IAP_CLIENT_ID")
33
+ iap_service_account_key_file = os.getenv("IAP_SERVICE_ACCOUNT_KEY_FILE")
34
+ headers = {}
35
+ if iap_client_id and iap_service_account_key_file:
36
+ try:
37
+ credentials = (
38
+ service_account.IDTokenCredentials.from_service_account_file(
39
+ iap_service_account_key_file,
40
+ target_audience=iap_client_id,
41
+ )
42
+ )
43
+ credentials.refresh(Request())
44
+ token = credentials.token
45
+ headers = {"Authorization": f"Bearer {token}"}
46
+ except Exception as e:
47
+ raise ValueError(f"Error generating IAP JWT token: {str(e)}")
48
+
49
+ async with aiohttp.ClientSession() as session:
50
+ async with session.get(info_url, headers=headers) as response:
51
+ if response.status != 200:
52
+ error_text = await response.text()
53
+ raise HTTPException(
54
+ status_code=response.status,
55
+ detail=f"Error calling botrun backend: {error_text}",
56
+ )
57
+
58
+ text = await response.text()
59
+ return json.loads(text)
60
+ except aiohttp.ClientError as e:
61
+ raise HTTPException(
62
+ status_code=500, detail=f"Error connecting to botrun backend: {str(e)}"
63
+ )
64
+ except Exception as e:
65
+ raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
@@ -1,3 +1,3 @@
1
- from fastapi import FastAPI, HTTPException, Query, APIRouter, Body, Depends
2
-
3
- crawl_api_router = APIRouter()
1
+ from fastapi import FastAPI, HTTPException, Query, APIRouter, Body, Depends
2
+
3
+ crawl_api_router = APIRouter()