MindsDB 25.8.2.0__py3-none-any.whl → 25.9.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (101) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +5 -45
  3. mindsdb/api/a2a/__init__.py +52 -0
  4. mindsdb/api/a2a/agent.py +17 -28
  5. mindsdb/api/a2a/common/server/server.py +17 -36
  6. mindsdb/api/a2a/common/server/task_manager.py +14 -28
  7. mindsdb/api/a2a/common/types.py +3 -4
  8. mindsdb/api/a2a/task_manager.py +43 -55
  9. mindsdb/api/a2a/utils.py +63 -0
  10. mindsdb/api/common/middleware.py +106 -0
  11. mindsdb/api/http/initialize.py +13 -15
  12. mindsdb/api/http/namespaces/agents.py +6 -7
  13. mindsdb/api/http/namespaces/auth.py +6 -14
  14. mindsdb/api/http/namespaces/config.py +0 -2
  15. mindsdb/api/http/namespaces/default.py +74 -106
  16. mindsdb/api/http/start.py +25 -44
  17. mindsdb/api/litellm/start.py +11 -10
  18. mindsdb/api/mcp/__init__.py +165 -0
  19. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +33 -64
  20. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +86 -85
  21. mindsdb/integrations/handlers/crate_handler/crate_handler.py +3 -7
  22. mindsdb/integrations/handlers/derby_handler/derby_handler.py +32 -34
  23. mindsdb/integrations/handlers/documentdb_handler/requirements.txt +1 -0
  24. mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +12 -13
  25. mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +45 -44
  26. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +101 -95
  27. mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +129 -129
  28. mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +59 -43
  29. mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +38 -39
  30. mindsdb/integrations/handlers/informix_handler/informix_handler.py +5 -18
  31. mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +22 -28
  32. mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +3 -7
  33. mindsdb/integrations/handlers/mongodb_handler/mongodb_handler.py +53 -67
  34. mindsdb/integrations/handlers/mongodb_handler/requirements.txt +1 -0
  35. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_ast.py +43 -68
  36. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py +17 -25
  37. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py +10 -16
  38. mindsdb/integrations/handlers/mongodb_handler/utils/mongodb_render.py +43 -69
  39. mindsdb/integrations/libs/base.py +1 -1
  40. mindsdb/interfaces/agents/constants.py +17 -2
  41. mindsdb/interfaces/agents/langchain_agent.py +83 -18
  42. mindsdb/interfaces/knowledge_base/controller.py +3 -1
  43. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +7 -1
  44. mindsdb/interfaces/skills/skill_tool.py +7 -1
  45. mindsdb/interfaces/skills/sql_agent.py +6 -2
  46. mindsdb/utilities/config.py +3 -155
  47. mindsdb/utilities/fs.py +10 -4
  48. mindsdb/utilities/log.py +0 -25
  49. mindsdb/utilities/starters.py +0 -39
  50. {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/METADATA +265 -263
  51. {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/RECORD +54 -98
  52. mindsdb/api/a2a/__main__.py +0 -144
  53. mindsdb/api/a2a/run_a2a.py +0 -86
  54. mindsdb/api/common/check_auth.py +0 -42
  55. mindsdb/api/http/gunicorn_wrapper.py +0 -17
  56. mindsdb/api/mcp/start.py +0 -205
  57. mindsdb/api/mongo/__init__.py +0 -0
  58. mindsdb/api/mongo/classes/__init__.py +0 -5
  59. mindsdb/api/mongo/classes/query_sql.py +0 -19
  60. mindsdb/api/mongo/classes/responder.py +0 -45
  61. mindsdb/api/mongo/classes/responder_collection.py +0 -34
  62. mindsdb/api/mongo/classes/scram.py +0 -86
  63. mindsdb/api/mongo/classes/session.py +0 -23
  64. mindsdb/api/mongo/functions/__init__.py +0 -19
  65. mindsdb/api/mongo/responders/__init__.py +0 -73
  66. mindsdb/api/mongo/responders/add_shard.py +0 -13
  67. mindsdb/api/mongo/responders/aggregate.py +0 -90
  68. mindsdb/api/mongo/responders/buildinfo.py +0 -17
  69. mindsdb/api/mongo/responders/coll_stats.py +0 -63
  70. mindsdb/api/mongo/responders/company_id.py +0 -25
  71. mindsdb/api/mongo/responders/connection_status.py +0 -22
  72. mindsdb/api/mongo/responders/count.py +0 -21
  73. mindsdb/api/mongo/responders/db_stats.py +0 -32
  74. mindsdb/api/mongo/responders/delete.py +0 -105
  75. mindsdb/api/mongo/responders/describe.py +0 -23
  76. mindsdb/api/mongo/responders/end_sessions.py +0 -13
  77. mindsdb/api/mongo/responders/find.py +0 -175
  78. mindsdb/api/mongo/responders/get_cmd_line_opts.py +0 -18
  79. mindsdb/api/mongo/responders/get_free_monitoring_status.py +0 -14
  80. mindsdb/api/mongo/responders/get_parameter.py +0 -23
  81. mindsdb/api/mongo/responders/getlog.py +0 -14
  82. mindsdb/api/mongo/responders/host_info.py +0 -28
  83. mindsdb/api/mongo/responders/insert.py +0 -270
  84. mindsdb/api/mongo/responders/is_master.py +0 -20
  85. mindsdb/api/mongo/responders/is_master_lower.py +0 -13
  86. mindsdb/api/mongo/responders/list_collections.py +0 -55
  87. mindsdb/api/mongo/responders/list_databases.py +0 -37
  88. mindsdb/api/mongo/responders/list_indexes.py +0 -22
  89. mindsdb/api/mongo/responders/ping.py +0 -13
  90. mindsdb/api/mongo/responders/recv_chunk_start.py +0 -13
  91. mindsdb/api/mongo/responders/replsetgetstatus.py +0 -13
  92. mindsdb/api/mongo/responders/sasl_continue.py +0 -34
  93. mindsdb/api/mongo/responders/sasl_start.py +0 -33
  94. mindsdb/api/mongo/responders/update_range_deletions.py +0 -12
  95. mindsdb/api/mongo/responders/whatsmyuri.py +0 -18
  96. mindsdb/api/mongo/server.py +0 -388
  97. mindsdb/api/mongo/start.py +0 -15
  98. mindsdb/api/mongo/utilities/__init__.py +0 -0
  99. {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/WHEEL +0 -0
  100. {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/licenses/LICENSE +0 -0
  101. {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- from typing import AsyncIterable
1
+ from typing import AsyncIterable, Dict
2
2
  from mindsdb.api.a2a.common.types import (
3
3
  SendTaskRequest,
4
4
  TaskSendParams,
@@ -18,12 +18,13 @@ from mindsdb.api.a2a.common.types import (
18
18
  )
19
19
  from mindsdb.api.a2a.common.server.task_manager import InMemoryTaskManager
20
20
  from mindsdb.api.a2a.agent import MindsDBAgent
21
- from mindsdb.api.a2a.utils import to_serializable
21
+ from mindsdb.api.a2a.utils import to_serializable, convert_a2a_message_to_qa_format
22
22
 
23
23
  from typing import Union
24
24
  import logging
25
25
  import asyncio
26
26
  import time
27
+ import traceback
27
28
 
28
29
  logger = logging.getLogger(__name__)
29
30
 
@@ -46,19 +47,15 @@ class AgentTaskManager(InMemoryTaskManager):
46
47
  def __init__(
47
48
  self,
48
49
  project_name: str,
49
- mindsdb_host: str,
50
- mindsdb_port: int,
51
50
  agent_name: str = None,
52
51
  ):
53
52
  super().__init__()
54
53
  self.project_name = project_name
55
- self.mindsdb_host = mindsdb_host
56
- self.mindsdb_port = mindsdb_port
57
54
  self.agent_name = agent_name
58
55
  self.tasks = {} # Task storage
59
56
  self.lock = asyncio.Lock() # Lock for task operations
60
57
 
61
- def _create_agent(self, agent_name: str = None) -> MindsDBAgent:
58
+ def _create_agent(self, user_info: Dict, agent_name: str = None) -> MindsDBAgent:
62
59
  """Create a new MindsDBAgent instance for the given agent name."""
63
60
  if not agent_name:
64
61
  raise ValueError("Agent name is required but was not provided in the request")
@@ -66,11 +63,12 @@ class AgentTaskManager(InMemoryTaskManager):
66
63
  return MindsDBAgent(
67
64
  agent_name=agent_name,
68
65
  project_name=self.project_name,
69
- host=self.mindsdb_host,
70
- port=self.mindsdb_port,
66
+ user_info=user_info,
71
67
  )
72
68
 
73
- async def _stream_generator(self, request: SendTaskStreamingRequest) -> AsyncIterable[SendTaskStreamingResponse]:
69
+ async def _stream_generator(
70
+ self, request: SendTaskStreamingRequest, user_info: Dict
71
+ ) -> AsyncIterable[SendTaskStreamingResponse]:
74
72
  task_send_params: TaskSendParams = request.params
75
73
  query = self._get_user_query(task_send_params)
76
74
  params = self._get_task_params(task_send_params)
@@ -92,24 +90,10 @@ class AgentTaskManager(InMemoryTaskManager):
92
90
  yield error_result
93
91
  return # Early return from generator
94
92
 
95
- agent = self._create_agent(agent_name)
93
+ agent = self._create_agent(user_info, agent_name)
96
94
 
97
- # Get the history from the task
95
+ # Get the history from the task object (where it was properly extracted and stored)
98
96
  history = task.history if task and task.history else []
99
- logger.info(f"Using history with length {len(history)} for request")
100
-
101
- # Log the history for debugging
102
- logger.info(f"Conversation history for task {task_send_params.id}:")
103
- for idx, msg in enumerate(history):
104
- # Convert Message object to dict if needed
105
- msg_dict = msg.dict() if hasattr(msg, "dict") else msg
106
- role = msg_dict.get("role", "unknown")
107
- text = ""
108
- for part in msg_dict.get("parts", []):
109
- if part.get("type") == "text":
110
- text = part.get("text", "")
111
- break
112
- logger.info(f"Message {idx + 1} ({role}): {text[:100]}...")
113
97
 
114
98
  if not streaming:
115
99
  # If streaming is disabled, use invoke and return a single response
@@ -182,24 +166,24 @@ class AgentTaskManager(InMemoryTaskManager):
182
166
 
183
167
  # If streaming is enabled (default), use the streaming implementation
184
168
  try:
185
- logger.debug(f"[TaskManager] Entering agent.stream() at {time.time()}")
186
- # Transform to agent-compatible format
187
- agent_messages = to_question_format(
188
- [
189
- {
190
- "role": task_send_params.message.role,
191
- "parts": task_send_params.message.parts,
192
- "metadata": task_send_params.message.metadata,
193
- }
194
- ]
195
- )
196
- async for item in agent.streaming_invoke(agent_messages, timeout=60):
169
+ logger.debug(f"Entering agent.stream() at {time.time()}")
170
+ # Create A2A message structure and convert using centralized utility
171
+ a2a_message = task_send_params.message.model_dump()
172
+ logger.debug(f"History: {history}")
173
+ if history:
174
+ a2a_message["history"] = [msg.model_dump() if hasattr(msg, "model_dump") else msg for msg in history]
175
+
176
+ # Convert to Q&A format using centralized utility function
177
+ all_messages = convert_a2a_message_to_qa_format(a2a_message)
178
+
179
+ async for item in agent.streaming_invoke(all_messages, timeout=60):
197
180
  # Clean up: Remove verbose debug logs, keep only errors and essential info
198
181
  if isinstance(item, dict) and "artifact" in item and "parts" in item["artifact"]:
199
182
  item["artifact"]["parts"] = [to_serializable(p) for p in item["artifact"]["parts"]]
200
183
  yield to_serializable(item)
201
184
  except Exception as e:
202
185
  logger.error(f"An error occurred while streaming the response: {e}")
186
+ logger.error(traceback.format_exc())
203
187
  error_text = f"An error occurred while streaming the response: {str(e)}"
204
188
  # Ensure all parts are plain dicts
205
189
  parts = [{"type": "text", "text": error_text}]
@@ -235,19 +219,23 @@ class AgentTaskManager(InMemoryTaskManager):
235
219
  message = task_send_params.message
236
220
  message_dict = message.dict() if hasattr(message, "dict") else message
237
221
 
238
- # Get history from request if available
222
+ # Get history from request if available - check both locations
239
223
  history = []
224
+
225
+ # First check if history is at top level (task_send_params.history)
240
226
  if hasattr(task_send_params, "history") and task_send_params.history:
241
- # Convert each history item to dict if needed and ensure proper role
227
+ # Convert each history item to dict if needed
242
228
  for item in task_send_params.history:
243
- item_dict = item.dict() if hasattr(item, "dict") else item
244
- # Ensure the role is properly set
245
- if "role" not in item_dict:
246
- item_dict["role"] = "assistant" if "answer" in item_dict else "user"
229
+ item_dict = item.model_dump() if hasattr(item, "model_dump") else item
230
+ history.append(item_dict)
231
+ # Also check if history is nested under message (message.history)
232
+ elif hasattr(task_send_params.message, "history") and task_send_params.message.history:
233
+ for item in task_send_params.message.history:
234
+ item_dict = item.model_dump() if hasattr(item, "model_dump") else item
247
235
  history.append(item_dict)
248
236
 
249
- # Add current message to history
250
- history.append(message_dict)
237
+ # DO NOT add current message to history - it should be processed separately
238
+ # The current message will be extracted during streaming from task_send_params.message
251
239
 
252
240
  # Create a new task
253
241
  task = Task(
@@ -321,27 +309,27 @@ class AgentTaskManager(InMemoryTaskManager):
321
309
 
322
310
  return None
323
311
 
324
- async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:
312
+ async def on_send_task(self, request: SendTaskRequest, user_info: Dict) -> SendTaskResponse:
325
313
  error = self._validate_request(request)
326
314
  if error:
327
315
  return error
328
316
 
329
- return await self._invoke(request)
317
+ return await self._invoke(request, user_info=user_info)
330
318
 
331
319
  async def on_send_task_subscribe(
332
- self, request: SendTaskStreamingRequest
320
+ self, request: SendTaskStreamingRequest, user_info: Dict
333
321
  ) -> AsyncIterable[SendTaskStreamingResponse]:
334
322
  error = self._validate_request(request)
335
323
  if error:
336
- logger.info(f"[TaskManager] Yielding error at {time.time()} for invalid request: {error}")
324
+ logger.info(f"Yielding error at {time.time()} for invalid request: {error}")
337
325
  yield to_serializable(SendTaskStreamingResponse(id=request.id, error=to_serializable(error.error)))
338
326
  return
339
327
 
340
328
  # We can't await an async generator directly, so we need to use it as is
341
329
  try:
342
- logger.debug(f"[TaskManager] Entering streaming path at {time.time()}")
343
- async for response in self._stream_generator(request):
344
- logger.debug(f"[TaskManager] Yielding streaming response at {time.time()} with: {str(response)[:120]}")
330
+ logger.debug(f"Entering streaming path at {time.time()}")
331
+ async for response in self._stream_generator(request, user_info):
332
+ logger.debug(f"Yielding streaming response at {time.time()} with: {str(response)[:120]}")
345
333
  yield response
346
334
  except Exception as e:
347
335
  # If an error occurs, yield an error response
@@ -420,13 +408,13 @@ class AgentTaskManager(InMemoryTaskManager):
420
408
  "session_id": task_send_params.sessionId,
421
409
  }
422
410
 
423
- async def _invoke(self, request: SendTaskRequest) -> SendTaskResponse:
411
+ async def _invoke(self, request: SendTaskRequest, user_info: Dict) -> SendTaskResponse:
424
412
  task_send_params: TaskSendParams = request.params
425
413
  query = self._get_user_query(task_send_params)
426
414
  params = self._get_task_params(task_send_params)
427
415
  agent_name = params["agent_name"]
428
416
  streaming = params["streaming"]
429
- agent = self._create_agent(agent_name)
417
+ agent = self._create_agent(user_info, agent_name)
430
418
 
431
419
  try:
432
420
  # Get the history from the task
mindsdb/api/a2a/utils.py CHANGED
@@ -1,3 +1,9 @@
1
+ from typing import Dict, List
2
+ from mindsdb.utilities.log import getLogger
3
+
4
+ logger = getLogger(__name__)
5
+
6
+
1
7
  def to_serializable(obj):
2
8
  # Primitives
3
9
  if isinstance(obj, (str, int, float, bool, type(None))):
@@ -19,3 +25,60 @@ def to_serializable(obj):
19
25
  return [to_serializable(v) for v in obj]
20
26
  # Fallback: string
21
27
  return str(obj)
28
+
29
+
30
+ def convert_a2a_message_to_qa_format(a2a_message: Dict) -> List[Dict[str, str]]:
31
+ """
32
+ Convert A2A message format to question/answer format.
33
+
34
+ This is the format that the langchain agent expects and ensure effective multi-turn conversation
35
+
36
+ Args:
37
+ a2a_message: A2A message containing history and current message parts
38
+
39
+ Returns:
40
+ List of messages in question/answer format
41
+ """
42
+ converted_messages = []
43
+
44
+ # Process conversation history first
45
+ if "history" in a2a_message and a2a_message["history"] is not None:
46
+ for hist_msg in a2a_message["history"]:
47
+ if hist_msg.get("role") == "user":
48
+ # Extract text from parts
49
+ text = ""
50
+ for part in hist_msg.get("parts", []):
51
+ if part.get("type") == "text":
52
+ text = part.get("text", "")
53
+ break
54
+ # Create question with empty answer initially
55
+ converted_messages.append({"question": text, "answer": ""})
56
+ elif hist_msg.get("role") in ["agent", "assistant"]:
57
+ # Extract text from parts
58
+ text = ""
59
+ for part in hist_msg.get("parts", []):
60
+ if part.get("type") == "text":
61
+ text = part.get("text", "")
62
+ break
63
+ # Pair with the most recent question that has empty answer
64
+ paired = False
65
+ for i in range(len(converted_messages) - 1, -1, -1):
66
+ if converted_messages[i].get("answer") == "":
67
+ converted_messages[i]["answer"] = text
68
+ paired = True
69
+ break
70
+
71
+ if not paired:
72
+ logger.warning("Could not pair agent response with question (no empty answer found)")
73
+
74
+ logger.debug(f"Converted {len(a2a_message['history'])} A2A history messages to Q&A format")
75
+
76
+ # Add current message as final question with empty answer
77
+ current_text = ""
78
+ for part in a2a_message.get("parts", []):
79
+ if part.get("type") == "text":
80
+ current_text = part.get("text", "")
81
+ break
82
+ converted_messages.append({"question": current_text, "answer": ""})
83
+
84
+ return converted_messages
@@ -0,0 +1,106 @@
1
+ from starlette.middleware.base import BaseHTTPMiddleware
2
+ from starlette.responses import JSONResponse
3
+ from starlette.requests import Request
4
+ from http import HTTPStatus
5
+ from typing import Optional
6
+ import secrets
7
+ import hmac
8
+ import hashlib
9
+ import os
10
+ import traceback
11
+
12
+ from mindsdb.utilities import log
13
+ from mindsdb.utilities.config import config
14
+
15
+ logger = log.getLogger(__name__)
16
+
17
+ SECRET_KEY = os.environ.get("AUTH_SECRET_KEY") or secrets.token_urlsafe(32)
18
+ # We store token (fingerprints) in memory, which means everyone is logged out if the process restarts
19
+ TOKENS = []
20
+
21
+
22
+ def get_pat_fingerprint(token: str) -> str:
23
+ """Hash the token with HMAC-SHA256 using secret_key as pepper."""
24
+ return hmac.new(SECRET_KEY.encode(), token.encode(), hashlib.sha256).hexdigest()
25
+
26
+
27
+ def generate_pat() -> str:
28
+ logger.debug("Generating new auth token")
29
+ token = "pat_" + secrets.token_urlsafe(32)
30
+ TOKENS.append(get_pat_fingerprint(token))
31
+ return token
32
+
33
+
34
+ def verify_pat(raw_token: str) -> bool:
35
+ """Verify if the raw_token matches a stored fingerprint.
36
+ Returns token_id if valid, None if not.
37
+ """
38
+ if not raw_token:
39
+ return False
40
+ fp = get_pat_fingerprint(raw_token)
41
+ for stored_fp in TOKENS:
42
+ if hmac.compare_digest(fp, stored_fp):
43
+ return True
44
+ return False
45
+
46
+
47
+ def revoke_pat(raw_token: str) -> bool:
48
+ """Revoke raw_token from active tokens"""
49
+ if not raw_token:
50
+ return False
51
+ fp = get_pat_fingerprint(raw_token)
52
+ for stored_fp in TOKENS:
53
+ if hmac.compare_digest(fp, stored_fp):
54
+ TOKENS.remove(stored_fp)
55
+ return True
56
+ return False
57
+
58
+
59
+ class PATAuthMiddleware(BaseHTTPMiddleware):
60
+ def _extract_bearer(self, request: Request) -> Optional[str]:
61
+ h = request.headers.get("Authorization")
62
+ if not h or not h.startswith("Bearer "):
63
+ return None
64
+ return h.split(" ", 1)[1].strip() or None
65
+
66
+ async def dispatch(self, request: Request, call_next):
67
+ if config.get("auth", {}).get("http_auth_enabled", False) is False:
68
+ return await call_next(request)
69
+
70
+ token = self._extract_bearer(request)
71
+ if not token or not verify_pat(token):
72
+ return JSONResponse({"detail": "Unauthorized"}, status_code=HTTPStatus.UNAUTHORIZED)
73
+
74
+ request.state.user = config["auth"].get("username")
75
+ return await call_next(request)
76
+
77
+
78
+ # Used by mysql and postgres protocols
79
+ def check_auth(username, password, scramble_func, salt, company_id, config):
80
+ try:
81
+ hardcoded_user = config["auth"].get("username")
82
+ hardcoded_password = config["auth"].get("password")
83
+ if hardcoded_password is None:
84
+ hardcoded_password = ""
85
+ hardcoded_password_hash = scramble_func(hardcoded_password, salt)
86
+ hardcoded_password = hardcoded_password.encode()
87
+
88
+ if password is None:
89
+ password = ""
90
+ if isinstance(password, str):
91
+ password = password.encode()
92
+
93
+ if username != hardcoded_user:
94
+ logger.warning(f"Check auth, user={username}: user mismatch")
95
+ return {"success": False}
96
+
97
+ if password != hardcoded_password and password != hardcoded_password_hash:
98
+ logger.warning(f"check auth, user={username}: password mismatch")
99
+ return {"success": False}
100
+
101
+ logger.info(f"Check auth, user={username}: Ok")
102
+ return {"success": True, "username": username}
103
+ except Exception as e:
104
+ logger.error(f"Check auth, user={username}: ERROR")
105
+ logger.error(e)
106
+ logger.error(traceback.format_exc())
@@ -1,5 +1,4 @@
1
1
  import os
2
- import secrets
3
2
  import mimetypes
4
3
  import threading
5
4
  import traceback
@@ -27,7 +26,7 @@ from mindsdb.api.http.namespaces.chatbots import ns_conf as chatbots_ns
27
26
  from mindsdb.api.http.namespaces.jobs import ns_conf as jobs_ns
28
27
  from mindsdb.api.http.namespaces.config import ns_conf as conf_ns
29
28
  from mindsdb.api.http.namespaces.databases import ns_conf as databases_ns
30
- from mindsdb.api.http.namespaces.default import ns_conf as default_ns, check_auth
29
+ from mindsdb.api.http.namespaces.default import ns_conf as default_ns
31
30
  from mindsdb.api.http.namespaces.file import ns_conf as file_ns
32
31
  from mindsdb.api.http.namespaces.handlers import ns_conf as handlers_ns
33
32
  from mindsdb.api.http.namespaces.knowledge_bases import ns_conf as knowledge_bases_ns
@@ -53,6 +52,7 @@ from mindsdb.utilities.json_encoder import CustomJSONProvider
53
52
  from mindsdb.utilities.ps import is_pid_listen_port, wait_func_is_true
54
53
  from mindsdb.utilities.sentry import sentry_sdk # noqa: F401
55
54
  from mindsdb.utilities.otel import trace # noqa: F401
55
+ from mindsdb.api.common.middleware import verify_pat
56
56
 
57
57
  logger = log.getLogger(__name__)
58
58
 
@@ -314,12 +314,19 @@ def initialize_app(config, no_studio):
314
314
  ctx.set_default()
315
315
  config = Config()
316
316
 
317
+ h = request.headers.get("Authorization")
318
+ if not h or not h.startswith("Bearer "):
319
+ bearer = None
320
+ else:
321
+ bearer = h.split(" ", 1)[1].strip() or None
322
+
317
323
  # region routes where auth is required
318
324
  if (
319
325
  config["auth"]["http_auth_enabled"] is True
320
326
  and any(request.path.startswith(f"/api{ns.path}") for ns in protected_namespaces)
321
- and check_auth() is False
327
+ and verify_pat(bearer) is False
322
328
  ):
329
+ logger.debug(f"Auth failed for path {request.path}")
323
330
  return http_error(
324
331
  HTTPStatus.UNAUTHORIZED,
325
332
  "Unauthorized",
@@ -340,29 +347,23 @@ def initialize_app(config, no_studio):
340
347
  except Exception:
341
348
  user_id = 0
342
349
 
343
- try:
344
- session_id = request.cookies.get("session")
345
- except Exception:
346
- session_id = "unknown"
347
-
348
350
  if company_id is not None:
349
351
  try:
350
352
  company_id = int(company_id)
351
353
  except Exception as e:
352
- logger.error(f"Cloud not parse company id: {company_id} | exception: {e}")
354
+ logger.error(f"Could not parse company id: {company_id} | exception: {e}")
353
355
  company_id = None
354
356
 
355
357
  if user_class is not None:
356
358
  try:
357
359
  user_class = int(user_class)
358
360
  except Exception as e:
359
- logger.error(f"Cloud not parse user_class: {user_class} | exception: {e}")
361
+ logger.error(f"Could not parse user_class: {user_class} | exception: {e}")
360
362
  user_class = 0
361
363
  else:
362
364
  user_class = 0
363
365
 
364
366
  ctx.user_id = user_id
365
- ctx.session_id = session_id
366
367
  ctx.company_id = company_id
367
368
  ctx.user_class = user_class
368
369
  ctx.email_confirmed = email_confirmed
@@ -394,14 +395,11 @@ def initialize_flask(config, init_static_thread, no_studio):
394
395
  FlaskInstrumentor().instrument_app(app)
395
396
  RequestsInstrumentor().instrument()
396
397
 
397
- app.config["SECRET_KEY"] = os.environ.get("FLASK_SECRET_KEY", secrets.token_hex(32))
398
- app.config["SESSION_COOKIE_NAME"] = "session"
399
- app.config["PERMANENT_SESSION_LIFETIME"] = config["auth"]["http_permanent_session_lifetime"]
400
398
  app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 60
401
399
  app.config["SWAGGER_HOST"] = "http://localhost:8000/mindsdb"
402
400
  app.json = CustomJSONProvider()
403
401
 
404
- authorizations = {"apikey": {"type": "session", "in": "query", "name": "session"}}
402
+ authorizations = {"apikey": {"type": "apiKey", "in": "header", "name": "Authorization"}}
405
403
 
406
404
  logger.debug("Creating swagger API..")
407
405
  api = Swagger_Api(
@@ -323,15 +323,16 @@ class AgentCompletionsStream(Resource):
323
323
  @ns_conf.doc("agent_completions_stream")
324
324
  @api_endpoint_metrics("POST", "/agents/agent/completions/stream")
325
325
  def post(self, project_name, agent_name):
326
- logger.info(f"Received streaming request for agent {agent_name} in project {project_name}")
327
-
328
- # Check for required parameters.
326
+ # Extract messages from request (HTTP format only)
329
327
  if "messages" not in request.json:
330
- logger.error("Missing 'messages' parameter in request body")
331
328
  return http_error(
332
- HTTPStatus.BAD_REQUEST, "Missing parameter", 'Must provide "messages" parameter in POST body'
329
+ HTTPStatus.BAD_REQUEST,
330
+ "Missing parameter",
331
+ 'Must provide "messages" parameter in POST body',
333
332
  )
334
333
 
334
+ messages = request.json["messages"]
335
+
335
336
  session = SessionController()
336
337
  try:
337
338
  existing_agent = session.agents_controller.get_agent(agent_name, project_name=project_name)
@@ -346,8 +347,6 @@ class AgentCompletionsStream(Resource):
346
347
  HTTPStatus.NOT_FOUND, "Project not found", f"Project with name {project_name} does not exist"
347
348
  )
348
349
 
349
- messages = request.json["messages"]
350
-
351
350
  try:
352
351
  gen = _completion_event_generator(agent_name, messages, project_name)
353
352
  logger.info(f"Starting streaming response for agent {agent_name}")
@@ -4,7 +4,7 @@ import time
4
4
  import urllib
5
5
 
6
6
  import requests
7
- from flask import redirect, request, session, url_for
7
+ from flask import redirect, request, url_for
8
8
  from flask_restx import Resource
9
9
 
10
10
  from mindsdb.api.http.namespaces.configs.auth import ns_conf
@@ -21,9 +21,7 @@ def get_access_token() -> str:
21
21
  Returns:
22
22
  str: token
23
23
  """
24
- return (
25
- Config().get("auth", {}).get("oauth", {}).get("tokens", {}).get("access_token")
26
- )
24
+ return Config().get("auth", {}).get("oauth", {}).get("tokens", {}).get("access_token")
27
25
 
28
26
 
29
27
  def request_user_info(access_token: str = None) -> dict:
@@ -58,7 +56,7 @@ def request_user_info(access_token: str = None) -> dict:
58
56
  @ns_conf.hide
59
57
  class Auth(Resource):
60
58
  @ns_conf.doc(params={"code": "authentification code"})
61
- @api_endpoint_metrics('GET', '/auth/code')
59
+ @api_endpoint_metrics("GET", "/auth/code")
62
60
  def get(self):
63
61
  """callback from auth server if authentification is successful"""
64
62
  config = Config()
@@ -72,9 +70,7 @@ class Auth(Resource):
72
70
  client_id = oauth_meta["client_id"]
73
71
  client_secret = oauth_meta["client_secret"]
74
72
  auth_server = oauth_meta["server_host"]
75
- client_basic = base64.b64encode(
76
- f"{client_id}:{client_secret}".encode()
77
- ).decode()
73
+ client_basic = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
78
74
 
79
75
  redirect_uri = f"https://{public_hostname}{request.path}"
80
76
  response = requests.post(
@@ -115,7 +111,7 @@ class Auth(Resource):
115
111
  "public_hostname": public_hostname,
116
112
  "ami_id": aws_meta_data.get("ami-id"),
117
113
  },
118
- headers={"Authorization": f'Bearer {tokens["access_token"]}'},
114
+ headers={"Authorization": f"Bearer {tokens['access_token']}"},
119
115
  timeout=5,
120
116
  )
121
117
  if resp.status_code != 200:
@@ -123,10 +119,6 @@ class Auth(Resource):
123
119
  except Exception as e:
124
120
  logger.warning(f"Cant't send request to cloud server: {e}")
125
121
 
126
- session["username"] = user_data["name"]
127
- session["auth_provider"] = "cloud"
128
- session.permanent = True
129
-
130
122
  if request.path.endswith("/auth/callback/cloud_home"):
131
123
  return redirect(f"https://{auth_server}")
132
124
  else:
@@ -140,7 +132,7 @@ class CloudLoginRoute(Resource):
140
132
  responses={302: "Redirect to auth server"},
141
133
  params={"location": "final redirection should lead to that location"},
142
134
  )
143
- @api_endpoint_metrics('GET', '/auth/cloud_login')
135
+ @api_endpoint_metrics("GET", "/auth/cloud_login")
144
136
  def get(self):
145
137
  """redirect to cloud login form"""
146
138
  location = request.args.get("location")
@@ -32,8 +32,6 @@ class GetConfig(Resource):
32
32
  value = config.get(key)
33
33
  if value is not None:
34
34
  resp[key] = value
35
- if "a2a" in config["api"]:
36
- resp["a2a"] = config["api"]["a2a"]
37
35
  return resp
38
36
 
39
37
  @ns_conf.doc("put_config")