botrun-flow-lang 5.12.262__py3-none-any.whl → 5.12.264__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 (87) 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 +508 -508
  6. botrun_flow_lang/api/langgraph_api.py +811 -811
  7. botrun_flow_lang/api/line_bot_api.py +1484 -1484
  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 +395 -395
  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 +178 -178
  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/gemini_subsidy_graph.py +460 -460
  24. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
  25. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
  26. botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +723 -723
  27. botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
  28. botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
  29. botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
  30. botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
  31. botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
  32. botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
  33. botrun_flow_lang/langgraph_agents/agents/util/local_files.py +419 -419
  34. botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
  35. botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
  36. botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +486 -486
  37. botrun_flow_lang/langgraph_agents/agents/util/pdf_cache.py +250 -250
  38. botrun_flow_lang/langgraph_agents/agents/util/pdf_processor.py +204 -204
  39. botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
  40. botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
  41. botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
  42. botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
  43. botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
  44. botrun_flow_lang/llm_agent/llm_agent.py +19 -19
  45. botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
  46. botrun_flow_lang/log/.gitignore +2 -2
  47. botrun_flow_lang/main.py +61 -61
  48. botrun_flow_lang/main_fast.py +51 -51
  49. botrun_flow_lang/mcp_server/__init__.py +10 -10
  50. botrun_flow_lang/mcp_server/default_mcp.py +744 -744
  51. botrun_flow_lang/models/nodes/utils.py +205 -205
  52. botrun_flow_lang/models/token_usage.py +34 -34
  53. botrun_flow_lang/requirements.txt +21 -21
  54. botrun_flow_lang/services/base/firestore_base.py +30 -30
  55. botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
  56. botrun_flow_lang/services/hatch/hatch_fs_store.py +419 -387
  57. botrun_flow_lang/services/storage/storage_cs_store.py +206 -206
  58. botrun_flow_lang/services/storage/storage_factory.py +12 -12
  59. botrun_flow_lang/services/storage/storage_store.py +65 -65
  60. botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
  61. botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
  62. botrun_flow_lang/static/docs/tools/index.html +926 -926
  63. botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
  64. botrun_flow_lang/tests/api_stress_test.py +357 -357
  65. botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
  66. botrun_flow_lang/tests/test_botrun_app.py +46 -46
  67. botrun_flow_lang/tests/test_html_util.py +31 -31
  68. botrun_flow_lang/tests/test_img_analyzer.py +190 -190
  69. botrun_flow_lang/tests/test_img_util.py +39 -39
  70. botrun_flow_lang/tests/test_local_files.py +114 -114
  71. botrun_flow_lang/tests/test_mermaid_util.py +103 -103
  72. botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
  73. botrun_flow_lang/tests/test_plotly_util.py +151 -151
  74. botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
  75. botrun_flow_lang/tools/generate_docs.py +133 -133
  76. botrun_flow_lang/tools/templates/tools.html +153 -153
  77. botrun_flow_lang/utils/__init__.py +7 -7
  78. botrun_flow_lang/utils/botrun_logger.py +344 -344
  79. botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
  80. botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
  81. botrun_flow_lang/utils/google_drive_utils.py +654 -654
  82. botrun_flow_lang/utils/langchain_utils.py +324 -324
  83. botrun_flow_lang/utils/yaml_utils.py +9 -9
  84. {botrun_flow_lang-5.12.262.dist-info → botrun_flow_lang-5.12.264.dist-info}/METADATA +1 -1
  85. botrun_flow_lang-5.12.264.dist-info/RECORD +102 -0
  86. botrun_flow_lang-5.12.262.dist-info/RECORD +0 -102
  87. {botrun_flow_lang-5.12.262.dist-info → botrun_flow_lang-5.12.264.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()