appkit-assistant 0.17.3__py3-none-any.whl → 1.0.1__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 (57) hide show
  1. appkit_assistant/backend/{models.py → database/models.py} +32 -132
  2. appkit_assistant/backend/{repositories.py → database/repositories.py} +93 -1
  3. appkit_assistant/backend/model_manager.py +5 -5
  4. appkit_assistant/backend/models/__init__.py +28 -0
  5. appkit_assistant/backend/models/anthropic.py +31 -0
  6. appkit_assistant/backend/models/google.py +27 -0
  7. appkit_assistant/backend/models/openai.py +50 -0
  8. appkit_assistant/backend/models/perplexity.py +56 -0
  9. appkit_assistant/backend/processors/__init__.py +29 -0
  10. appkit_assistant/backend/processors/claude_responses_processor.py +205 -387
  11. appkit_assistant/backend/processors/gemini_responses_processor.py +290 -352
  12. appkit_assistant/backend/processors/lorem_ipsum_processor.py +6 -4
  13. appkit_assistant/backend/processors/mcp_mixin.py +297 -0
  14. appkit_assistant/backend/processors/openai_base.py +11 -125
  15. appkit_assistant/backend/processors/openai_chat_completion_processor.py +5 -3
  16. appkit_assistant/backend/processors/openai_responses_processor.py +480 -402
  17. appkit_assistant/backend/processors/perplexity_processor.py +156 -79
  18. appkit_assistant/backend/{processor.py → processors/processor_base.py} +7 -2
  19. appkit_assistant/backend/processors/streaming_base.py +188 -0
  20. appkit_assistant/backend/schemas.py +138 -0
  21. appkit_assistant/backend/services/auth_error_detector.py +99 -0
  22. appkit_assistant/backend/services/chunk_factory.py +273 -0
  23. appkit_assistant/backend/services/citation_handler.py +292 -0
  24. appkit_assistant/backend/services/file_cleanup_service.py +316 -0
  25. appkit_assistant/backend/services/file_upload_service.py +903 -0
  26. appkit_assistant/backend/services/file_validation.py +138 -0
  27. appkit_assistant/backend/{mcp_auth_service.py → services/mcp_auth_service.py} +4 -2
  28. appkit_assistant/backend/services/mcp_token_service.py +61 -0
  29. appkit_assistant/backend/services/message_converter.py +289 -0
  30. appkit_assistant/backend/services/openai_client_service.py +120 -0
  31. appkit_assistant/backend/{response_accumulator.py → services/response_accumulator.py} +163 -1
  32. appkit_assistant/backend/services/system_prompt_builder.py +89 -0
  33. appkit_assistant/backend/services/thread_service.py +5 -3
  34. appkit_assistant/backend/system_prompt_cache.py +3 -3
  35. appkit_assistant/components/__init__.py +8 -4
  36. appkit_assistant/components/composer.py +59 -24
  37. appkit_assistant/components/file_manager.py +623 -0
  38. appkit_assistant/components/mcp_server_dialogs.py +12 -20
  39. appkit_assistant/components/mcp_server_table.py +12 -2
  40. appkit_assistant/components/message.py +119 -2
  41. appkit_assistant/components/thread.py +1 -1
  42. appkit_assistant/components/threadlist.py +4 -2
  43. appkit_assistant/components/tools_modal.py +37 -20
  44. appkit_assistant/configuration.py +12 -0
  45. appkit_assistant/state/file_manager_state.py +697 -0
  46. appkit_assistant/state/mcp_oauth_state.py +3 -3
  47. appkit_assistant/state/mcp_server_state.py +47 -2
  48. appkit_assistant/state/system_prompt_state.py +1 -1
  49. appkit_assistant/state/thread_list_state.py +99 -5
  50. appkit_assistant/state/thread_state.py +88 -9
  51. {appkit_assistant-0.17.3.dist-info → appkit_assistant-1.0.1.dist-info}/METADATA +8 -6
  52. appkit_assistant-1.0.1.dist-info/RECORD +58 -0
  53. appkit_assistant/backend/processors/claude_base.py +0 -178
  54. appkit_assistant/backend/processors/gemini_base.py +0 -84
  55. appkit_assistant-0.17.3.dist-info/RECORD +0 -39
  56. /appkit_assistant/backend/{file_manager.py → services/file_manager.py} +0 -0
  57. {appkit_assistant-0.17.3.dist-info → appkit_assistant-1.0.1.dist-info}/WHEEL +0 -0
@@ -1,76 +1,25 @@
1
1
  import asyncio
2
- import enum
3
2
  import logging
4
3
  import os
5
4
  from collections.abc import AsyncGenerator
6
5
  from typing import Any
7
6
 
8
- from appkit_assistant.backend.models import AIModel, Chunk, MCPServer, Message
9
- from appkit_assistant.backend.processors.openai_chat_completion_processor import (
10
- OpenAIChatCompletionsProcessor,
11
- )
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- class ContextSize(enum.StrEnum):
17
- """Enum for context size options."""
18
-
19
- LOW = "low"
20
- MEDIUM = "medium"
21
- HIGH = "high"
22
-
23
-
24
- class PerplexityAIModel(AIModel):
25
- """AI model for Perplexity API."""
7
+ from openai import AsyncStream
26
8
 
27
- search_context_size: ContextSize = ContextSize.MEDIUM
28
- search_domain_filter: list[str] = []
29
-
30
-
31
- SONAR = PerplexityAIModel(
32
- id="sonar",
33
- text="Perplexity Sonar",
34
- icon="perplexity",
35
- model="sonar",
36
- stream=True,
9
+ from appkit_assistant.backend.database.models import (
10
+ MCPServer,
37
11
  )
38
-
39
- SONAR_PRO = PerplexityAIModel(
40
- id="sonar-pro",
41
- text="Perplexity Sonar Pro",
42
- icon="perplexity",
43
- model="sonar-pro",
44
- stream=True,
45
- keywords=["sonar", "perplexity"],
46
- )
47
-
48
- SONAR_DEEP_RESEARCH = PerplexityAIModel(
49
- id="sonar-deep-research",
50
- text="Perplexity Deep Research",
51
- icon="perplexity",
52
- model="sonar-deep-research",
53
- search_context_size=ContextSize.HIGH,
54
- stream=True,
55
- keywords=["reasoning", "deep", "research", "perplexity"],
12
+ from appkit_assistant.backend.models.perplexity import PerplexityAIModel
13
+ from appkit_assistant.backend.processors.openai_chat_completion_processor import (
14
+ OpenAIChatCompletionsProcessor,
56
15
  )
57
-
58
- SONAR_REASONING = PerplexityAIModel(
59
- id="sonar-reasoning",
60
- text="Perplexity Reasoning",
61
- icon="perplexity",
62
- model="sonar-reasoning",
63
- search_context_size=ContextSize.HIGH,
64
- stream=True,
65
- keywords=["reasoning", "perplexity"],
16
+ from appkit_assistant.backend.schemas import (
17
+ Chunk,
18
+ Message,
66
19
  )
20
+ from appkit_assistant.backend.services.chunk_factory import ChunkFactory
67
21
 
68
- ALL_MODELS = {
69
- SONAR.id: SONAR,
70
- SONAR_PRO.id: SONAR_PRO,
71
- SONAR_DEEP_RESEARCH.id: SONAR_DEEP_RESEARCH,
72
- SONAR_REASONING.id: SONAR_REASONING,
73
- }
22
+ logger = logging.getLogger(__name__)
74
23
 
75
24
 
76
25
  class PerplexityProcessor(OpenAIChatCompletionsProcessor):
@@ -83,24 +32,79 @@ class PerplexityProcessor(OpenAIChatCompletionsProcessor):
83
32
  ) -> None:
84
33
  self.base_url = "https://api.perplexity.ai"
85
34
  super().__init__(api_key=api_key, base_url=self.base_url, models=models)
35
+ self._chunk_factory = ChunkFactory(processor_name="perplexity")
86
36
 
87
37
  async def process(
88
38
  self,
89
39
  messages: list[Message],
90
40
  model_id: str,
91
- files: list[str] | None = None,
92
- mcp_servers: list[MCPServer] | None = None, # noqa: ARG002
41
+ files: list[str] | None = None, # noqa: ARG002
42
+ mcp_servers: list[MCPServer] | None = None,
93
43
  payload: dict[str, Any] | None = None,
94
44
  cancellation_token: asyncio.Event | None = None,
95
- **kwargs: Any,
45
+ **kwargs: Any, # noqa: ARG002
96
46
  ) -> AsyncGenerator[Chunk, None]:
47
+ """Process messages using Perplexity API with citation support.
48
+
49
+ Args:
50
+ messages: List of messages to process.
51
+ model_id: ID of the model to use.
52
+ files: File attachments (not used in Perplexity).
53
+ mcp_servers: MCP servers (not supported, will log warning).
54
+ payload: Additional payload parameters.
55
+ cancellation_token: Optional event to signal cancellation.
56
+ **kwargs: Additional arguments.
57
+ """
58
+ if not self.client:
59
+ raise ValueError("Perplexity Client not initialized.")
60
+
97
61
  if model_id not in self.models:
98
62
  logger.error("Model %s not supported by Perplexity processor", model_id)
99
63
  raise ValueError(f"Model {model_id} not supported by Perplexity processor")
100
64
 
101
- model = self.models[model_id]
65
+ if mcp_servers:
66
+ logger.warning(
67
+ "MCP servers provided to PerplexityProcessor but not supported."
68
+ )
102
69
 
103
- # Create Perplexity-specific payload
70
+ model = self.models[model_id]
71
+ perplexity_payload = self._build_perplexity_payload(model, payload)
72
+
73
+ try:
74
+ chat_messages = self._convert_messages_to_openai_format(messages)
75
+ session = await self.client.chat.completions.create(
76
+ model=model.model,
77
+ messages=chat_messages[:-1],
78
+ stream=model.stream,
79
+ temperature=model.temperature,
80
+ extra_body=perplexity_payload,
81
+ )
82
+
83
+ if isinstance(session, AsyncStream):
84
+ async for chunk in self._process_streaming_response(
85
+ session, model, cancellation_token
86
+ ):
87
+ yield chunk
88
+ else:
89
+ async for chunk in self._process_non_streaming_response(session, model):
90
+ yield chunk
91
+
92
+ except Exception as e:
93
+ logger.error("Error in Perplexity processor: %s", e)
94
+ raise
95
+
96
+ def _build_perplexity_payload(
97
+ self, model: PerplexityAIModel, payload: dict[str, Any] | None
98
+ ) -> dict[str, Any]:
99
+ """Build the Perplexity-specific payload.
100
+
101
+ Args:
102
+ model: The Perplexity AI model configuration.
103
+ payload: Additional payload parameters to merge.
104
+
105
+ Returns:
106
+ Combined payload dictionary for the API request.
107
+ """
104
108
  perplexity_payload = {
105
109
  "search_domain_filter": model.search_domain_filter,
106
110
  "return_images": True,
@@ -109,18 +113,91 @@ class PerplexityProcessor(OpenAIChatCompletionsProcessor):
109
113
  "search_context_size": model.search_context_size,
110
114
  },
111
115
  }
112
-
113
- # Merge with any additional payload
114
116
  if payload:
115
117
  perplexity_payload.update(payload)
118
+ return perplexity_payload
119
+
120
+ async def _process_streaming_response(
121
+ self,
122
+ session: AsyncStream[Any],
123
+ model: PerplexityAIModel,
124
+ cancellation_token: asyncio.Event | None,
125
+ ) -> AsyncGenerator[Chunk, None]:
126
+ """Process a streaming response from Perplexity API.
127
+
128
+ Args:
129
+ session: The async stream from the API.
130
+ model: The model configuration.
131
+ cancellation_token: Optional cancellation event.
132
+
133
+ Yields:
134
+ Chunk objects with text content and citations.
135
+ """
136
+ citations: list[str] = []
137
+
138
+ async for event in session:
139
+ if cancellation_token and cancellation_token.is_set():
140
+ logger.info("Processing cancelled by user")
141
+ break
142
+
143
+ # Extract citations from streaming response if available
144
+ if hasattr(event, "citations") and event.citations:
145
+ citations = event.citations
146
+
147
+ if event.choices and event.choices[0].delta:
148
+ content = event.choices[0].delta.content
149
+ if content:
150
+ yield self._create_chunk(
151
+ content, model.model, stream=True, message_id=event.id
152
+ )
153
+
154
+ # After streaming completes, yield citation annotations
155
+ async for chunk in self._yield_citations(citations):
156
+ yield chunk
157
+
158
+ async def _process_non_streaming_response(
159
+ self, session: Any, model: PerplexityAIModel
160
+ ) -> AsyncGenerator[Chunk, None]:
161
+ """Process a non-streaming response from Perplexity API.
162
+
163
+ Args:
164
+ session: The response object from the API.
165
+ model: The model configuration.
166
+
167
+ Yields:
168
+ Chunk objects with text content and citations.
169
+ """
170
+ content = session.choices[0].message.content
171
+ citations: list[str] = []
172
+
173
+ if hasattr(session, "citations") and session.citations:
174
+ citations = session.citations
175
+
176
+ if content:
177
+ yield self._create_chunk(content, model.model, message_id=session.id)
178
+
179
+ async for chunk in self._yield_citations(citations):
180
+ yield chunk
181
+
182
+ async def _yield_citations(
183
+ self, citations: list[str]
184
+ ) -> AsyncGenerator[Chunk, None]:
185
+ """Yield annotation chunks for citations.
186
+
187
+ Args:
188
+ citations: List of citation URLs from Perplexity API.
189
+
190
+ Yields:
191
+ Chunk objects with citation annotations.
192
+ """
193
+ if not citations:
194
+ return
195
+
196
+ logger.debug("Processing %d citations from Perplexity", len(citations))
116
197
 
117
- async for response in super().process(
118
- messages=messages,
119
- model_id=model_id,
120
- files=files,
121
- mcp_servers=None,
122
- payload=perplexity_payload,
123
- cancellation_token=cancellation_token,
124
- **kwargs,
125
- ):
126
- yield response
198
+ # Yield individual ANNOTATION chunks for citations display
199
+ for url in citations:
200
+ yield self._chunk_factory.annotation(
201
+ text=url,
202
+ annotation_data={"url": url, "source": "perplexity"},
203
+ )
@@ -7,7 +7,12 @@ import asyncio
7
7
  import logging
8
8
  from collections.abc import AsyncGenerator
9
9
 
10
- from appkit_assistant.backend.models import AIModel, Chunk, MCPServer, Message
10
+ from appkit_assistant.backend.database.models import MCPServer
11
+ from appkit_assistant.backend.schemas import (
12
+ AIModel,
13
+ Chunk,
14
+ Message,
15
+ )
11
16
  from appkit_commons.configuration.configuration import ReflexConfig
12
17
  from appkit_commons.registry import service_registry
13
18
 
@@ -31,7 +36,7 @@ def mcp_oauth_redirect_uri() -> str:
31
36
  return f"http://localhost:8080{MCP_OAUTH_CALLBACK_PATH}"
32
37
 
33
38
 
34
- class Processor(abc.ABC):
39
+ class ProcessorBase(abc.ABC):
35
40
  """Base processor interface for AI processing services."""
36
41
 
37
42
  @abc.abstractmethod
@@ -0,0 +1,188 @@
1
+ """Streaming Processor Base class.
2
+
3
+ Provides a unified base class for all streaming AI processors with:
4
+ - Standardized dict-based event dispatch
5
+ - Cancellation token handling
6
+ - Common state management
7
+ - Composed service dependencies
8
+ """
9
+
10
+ import asyncio
11
+ import logging
12
+ from abc import ABC, abstractmethod
13
+ from collections.abc import AsyncGenerator
14
+ from typing import Any
15
+
16
+ from appkit_assistant.backend.database.models import (
17
+ MCPServer,
18
+ )
19
+ from appkit_assistant.backend.processors.processor_base import ProcessorBase
20
+ from appkit_assistant.backend.schemas import (
21
+ AIModel,
22
+ Chunk,
23
+ ChunkType,
24
+ Message,
25
+ )
26
+ from appkit_assistant.backend.services.auth_error_detector import AuthErrorDetector
27
+ from appkit_assistant.backend.services.chunk_factory import ChunkFactory
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class StreamingProcessorBase(ProcessorBase, ABC):
33
+ """Base class for streaming AI processors.
34
+
35
+ Provides common functionality for:
36
+ - Event dispatch via dict-based handlers
37
+ - Cancellation token checking
38
+ - Common state tracking (reasoning sessions)
39
+ - Chunk creation via ChunkFactory
40
+ - Error detection via AuthErrorDetector
41
+
42
+ Note: For user ID tracking and MCP capabilities, combine with MCPCapabilities mixin.
43
+
44
+ Subclasses must implement:
45
+ - _get_event_handlers(): Return dict mapping event types to handlers
46
+ - get_supported_models(): Return supported models
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ models: dict[str, AIModel],
52
+ processor_name: str,
53
+ ) -> None:
54
+ """Initialize the streaming processor base.
55
+
56
+ Args:
57
+ models: Dictionary of supported AI models
58
+ processor_name: Name for chunk metadata (e.g., "claude_responses")
59
+ """
60
+ self.models = models
61
+ self._chunk_factory = ChunkFactory(processor_name)
62
+ self._auth_detector = AuthErrorDetector()
63
+
64
+ # Common streaming state
65
+ self._current_reasoning_session: str | None = None
66
+
67
+ @property
68
+ def chunk_factory(self) -> ChunkFactory:
69
+ """Get the chunk factory instance."""
70
+ return self._chunk_factory
71
+
72
+ @property
73
+ def auth_detector(self) -> AuthErrorDetector:
74
+ """Get the auth error detector instance."""
75
+ return self._auth_detector
76
+
77
+ @property
78
+ def current_reasoning_session(self) -> str | None:
79
+ """Get the current reasoning session ID."""
80
+ return self._current_reasoning_session
81
+
82
+ @current_reasoning_session.setter
83
+ def current_reasoning_session(self, value: str | None) -> None:
84
+ """Set the current reasoning session ID."""
85
+ self._current_reasoning_session = value
86
+
87
+ def get_supported_models(self) -> dict[str, AIModel]:
88
+ """Return supported models."""
89
+ return self.models
90
+
91
+ @abstractmethod
92
+ def _get_event_handlers(self) -> dict[str, Any]:
93
+ """Get the event handler mapping.
94
+
95
+ Returns:
96
+ Dict mapping event type strings to handler methods.
97
+ Handler methods should accept the event and return Chunk | None.
98
+ """
99
+
100
+ def _handle_event(self, event: Any) -> Chunk | None:
101
+ """Handle a streaming event using dict-based dispatch.
102
+
103
+ Args:
104
+ event: The event object from the API stream
105
+
106
+ Returns:
107
+ A Chunk if the event produced content, None otherwise
108
+ """
109
+ event_type = getattr(event, "type", None)
110
+ if not event_type:
111
+ return None
112
+
113
+ handlers = self._get_event_handlers()
114
+ handler = handlers.get(event_type)
115
+
116
+ if handler:
117
+ return handler(event)
118
+
119
+ logger.debug("Unhandled event type: %s", event_type)
120
+ return None
121
+
122
+ async def _process_stream_with_cancellation(
123
+ self,
124
+ stream: Any,
125
+ cancellation_token: asyncio.Event | None = None,
126
+ ) -> AsyncGenerator[Chunk, None]:
127
+ """Process a stream with cancellation support.
128
+
129
+ Args:
130
+ stream: Async iterable stream of events
131
+ cancellation_token: Optional event to signal cancellation
132
+
133
+ Yields:
134
+ Chunk objects from handled events
135
+ """
136
+ try:
137
+ async for event in stream:
138
+ if cancellation_token and cancellation_token.is_set():
139
+ logger.info("Processing cancelled by user")
140
+ break
141
+
142
+ chunk = self._handle_event(event)
143
+ if chunk:
144
+ yield chunk
145
+ except Exception as e:
146
+ error_msg = str(e)
147
+ logger.error("Error during stream processing: %s", error_msg)
148
+
149
+ # Only yield error chunk if NOT an auth error
150
+ if not self._auth_detector.is_auth_error(error_msg):
151
+ yield self._chunk_factory.error(
152
+ f"Ein Fehler ist aufgetreten: {error_msg}",
153
+ error_type=type(e).__name__,
154
+ )
155
+
156
+ def _create_chunk(
157
+ self,
158
+ chunk_type: ChunkType,
159
+ content: str,
160
+ extra_metadata: dict[str, Any] | None = None,
161
+ ) -> Chunk:
162
+ """Create a chunk using the factory (convenience method).
163
+
164
+ Args:
165
+ chunk_type: The type of chunk
166
+ content: The text content
167
+ extra_metadata: Additional metadata
168
+
169
+ Returns:
170
+ A Chunk instance
171
+ """
172
+ return self._chunk_factory.create(chunk_type, content, extra_metadata)
173
+
174
+ @abstractmethod
175
+ async def process(
176
+ self,
177
+ messages: list[Message],
178
+ model_id: str,
179
+ files: list[str] | None = None,
180
+ mcp_servers: list[MCPServer] | None = None,
181
+ payload: dict[str, Any] | None = None,
182
+ user_id: int | None = None,
183
+ cancellation_token: asyncio.Event | None = None,
184
+ ) -> AsyncGenerator[Chunk, None]:
185
+ """Process messages and generate response chunks.
186
+
187
+ Must be implemented by subclasses to handle vendor-specific API calls.
188
+ """
@@ -0,0 +1,138 @@
1
+ import uuid
2
+ from enum import StrEnum
3
+
4
+ from pydantic import BaseModel
5
+ from sqlmodel import Field
6
+
7
+
8
+ class ChunkType(StrEnum):
9
+ """Enum for chunk types."""
10
+
11
+ TEXT = "text" # default
12
+ ANNOTATION = "annotation" # for text annotations
13
+ IMAGE = "image"
14
+ IMAGE_PARTIAL = "image_partial" # for streaming image generation
15
+ THINKING = "thinking" # when the model is "thinking" / reasoning
16
+ THINKING_RESULT = "thinking_result" # when the "thinking" is done
17
+ ACTION = "action" # when the user needs to take action
18
+ TOOL_RESULT = "tool_result" # result from a tool
19
+ TOOL_CALL = "tool_call" # calling a tool
20
+ PROCESSING = "processing" # file processing status
21
+ COMPLETION = "completion" # when response generation is complete
22
+ AUTH_REQUIRED = "auth_required" # user needs to authenticate (MCP)
23
+ ERROR = "error" # when an error occurs
24
+ LIFECYCLE = "lifecycle"
25
+
26
+
27
+ class Chunk(BaseModel):
28
+ """Model for text chunks."""
29
+
30
+ type: ChunkType
31
+ text: str
32
+ chunk_metadata: dict[str, str | None] = {}
33
+
34
+
35
+ class ThreadStatus(StrEnum):
36
+ """Enum for thread status."""
37
+
38
+ NEW = "new"
39
+ ACTIVE = "active"
40
+ IDLE = "idle"
41
+ WAITING = "waiting"
42
+ ERROR = "error"
43
+ DELETED = "deleted"
44
+ ARCHIVED = "archived"
45
+
46
+
47
+ class MessageType(StrEnum):
48
+ """Enum for message types."""
49
+
50
+ HUMAN = "human"
51
+ SYSTEM = "system"
52
+ ASSISTANT = "assistant"
53
+ TOOL_USE = "tool_use"
54
+ ERROR = "error"
55
+ INFO = "info"
56
+ WARNING = "warning"
57
+
58
+
59
+ class Message(BaseModel):
60
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
61
+ text: str
62
+ original_text: str | None = None # To store original text if edited
63
+ editable: bool = False
64
+ type: MessageType
65
+ done: bool = False
66
+ attachments: list[str] = [] # List of filenames for display
67
+ annotations: list[str] = [] # List of file citations/annotations
68
+
69
+
70
+ class ThinkingType(StrEnum):
71
+ REASONING = "reasoning"
72
+ TOOL_CALL = "tool_call"
73
+ PROCESSING = "processing"
74
+
75
+
76
+ class ThinkingStatus(StrEnum):
77
+ IN_PROGRESS = "in_progress"
78
+ COMPLETED = "completed"
79
+ ERROR = "error"
80
+
81
+
82
+ class Thinking(BaseModel):
83
+ type: ThinkingType
84
+ id: str # reasoning_session_id or tool_id
85
+ text: str
86
+ status: ThinkingStatus = ThinkingStatus.IN_PROGRESS
87
+ tool_name: str | None = None
88
+ parameters: str | None = None
89
+ result: str | None = None
90
+ error: str | None = None
91
+
92
+
93
+ class AIModel(BaseModel):
94
+ id: str
95
+ text: str
96
+ icon: str = "codesandbox"
97
+ stream: bool = False
98
+ tenant_key: str = ""
99
+ project_id: int = 0
100
+ model: str = "default"
101
+ temperature: float = 0.05
102
+ supports_tools: bool = False
103
+ supports_attachments: bool = False
104
+ supports_search: bool = False
105
+ keywords: list[str] = []
106
+ disabled: bool = False
107
+ requires_role: str | None = None
108
+
109
+
110
+ class Suggestion(BaseModel):
111
+ prompt: str
112
+ icon: str = ""
113
+
114
+
115
+ class UploadedFile(BaseModel):
116
+ """Model for tracking uploaded files in the composer."""
117
+
118
+ filename: str
119
+ file_path: str
120
+ size: int = 0
121
+
122
+
123
+ class ThreadModel(BaseModel):
124
+ thread_id: str
125
+ title: str = ""
126
+ active: bool = False
127
+ state: ThreadStatus = ThreadStatus.NEW
128
+ prompt: str | None = ""
129
+ messages: list[Message] = []
130
+ ai_model: str = ""
131
+
132
+
133
+ class MCPAuthType(StrEnum):
134
+ """Enum for MCP server authentication types."""
135
+
136
+ NONE = "none"
137
+ API_KEY = "api_key"
138
+ OAUTH_DISCOVERY = "oauth_discovery"