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