vibesurf 0.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 vibesurf might be problematic. Click here for more details.
- vibe_surf/__init__.py +12 -0
- vibe_surf/_version.py +34 -0
- vibe_surf/agents/__init__.py +0 -0
- vibe_surf/agents/browser_use_agent.py +1106 -0
- vibe_surf/agents/prompts/__init__.py +1 -0
- vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
- vibe_surf/agents/report_writer_agent.py +360 -0
- vibe_surf/agents/vibe_surf_agent.py +1632 -0
- vibe_surf/backend/__init__.py +0 -0
- vibe_surf/backend/api/__init__.py +3 -0
- vibe_surf/backend/api/activity.py +243 -0
- vibe_surf/backend/api/config.py +740 -0
- vibe_surf/backend/api/files.py +322 -0
- vibe_surf/backend/api/models.py +257 -0
- vibe_surf/backend/api/task.py +300 -0
- vibe_surf/backend/database/__init__.py +13 -0
- vibe_surf/backend/database/manager.py +129 -0
- vibe_surf/backend/database/models.py +164 -0
- vibe_surf/backend/database/queries.py +922 -0
- vibe_surf/backend/database/schemas.py +100 -0
- vibe_surf/backend/llm_config.py +182 -0
- vibe_surf/backend/main.py +137 -0
- vibe_surf/backend/migrations/__init__.py +16 -0
- vibe_surf/backend/migrations/init_db.py +303 -0
- vibe_surf/backend/migrations/seed_data.py +236 -0
- vibe_surf/backend/shared_state.py +601 -0
- vibe_surf/backend/utils/__init__.py +7 -0
- vibe_surf/backend/utils/encryption.py +164 -0
- vibe_surf/backend/utils/llm_factory.py +225 -0
- vibe_surf/browser/__init__.py +8 -0
- vibe_surf/browser/agen_browser_profile.py +130 -0
- vibe_surf/browser/agent_browser_session.py +416 -0
- vibe_surf/browser/browser_manager.py +296 -0
- vibe_surf/browser/utils.py +790 -0
- vibe_surf/browser/watchdogs/__init__.py +0 -0
- vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
- vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
- vibe_surf/chrome_extension/background.js +558 -0
- vibe_surf/chrome_extension/config.js +48 -0
- vibe_surf/chrome_extension/content.js +284 -0
- vibe_surf/chrome_extension/dev-reload.js +47 -0
- vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
- vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
- vibe_surf/chrome_extension/icons/logo.png +0 -0
- vibe_surf/chrome_extension/manifest.json +53 -0
- vibe_surf/chrome_extension/popup.html +134 -0
- vibe_surf/chrome_extension/scripts/api-client.js +473 -0
- vibe_surf/chrome_extension/scripts/main.js +491 -0
- vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
- vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
- vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
- vibe_surf/chrome_extension/sidepanel.html +347 -0
- vibe_surf/chrome_extension/styles/animations.css +471 -0
- vibe_surf/chrome_extension/styles/components.css +670 -0
- vibe_surf/chrome_extension/styles/main.css +2307 -0
- vibe_surf/chrome_extension/styles/settings.css +1100 -0
- vibe_surf/cli.py +357 -0
- vibe_surf/controller/__init__.py +0 -0
- vibe_surf/controller/file_system.py +53 -0
- vibe_surf/controller/mcp_client.py +68 -0
- vibe_surf/controller/vibesurf_controller.py +616 -0
- vibe_surf/controller/views.py +37 -0
- vibe_surf/llm/__init__.py +21 -0
- vibe_surf/llm/openai_compatible.py +237 -0
- vibesurf-0.1.0.dist-info/METADATA +97 -0
- vibesurf-0.1.0.dist-info/RECORD +70 -0
- vibesurf-0.1.0.dist-info/WHEEL +5 -0
- vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
- vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
- vibesurf-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Upload and Management Router
|
|
3
|
+
|
|
4
|
+
Handles file uploads to workspace directories, file retrieval, and listing
|
|
5
|
+
of uploaded files for VibeSurf sessions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
|
|
9
|
+
from fastapi.responses import FileResponse
|
|
10
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
11
|
+
from typing import List, Optional, Dict, Any
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import logging
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from uuid_extensions import uuid7str
|
|
17
|
+
import mimetypes
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from ..database import get_db_session
|
|
21
|
+
from ..database.queries import UploadedFileQueries
|
|
22
|
+
from .models import FileListQueryRequest, SessionFilesQueryRequest
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
router = APIRouter(prefix="/files", tags=["files"])
|
|
27
|
+
|
|
28
|
+
def get_upload_directory(session_id: Optional[str] = None) -> str:
|
|
29
|
+
from ..shared_state import workspace_dir
|
|
30
|
+
"""Get the upload directory path for a session or global uploads"""
|
|
31
|
+
if session_id:
|
|
32
|
+
upload_dir = os.path.join(workspace_dir, session_id, "upload_files")
|
|
33
|
+
else:
|
|
34
|
+
upload_dir = os.path.join(workspace_dir, "upload_files")
|
|
35
|
+
|
|
36
|
+
# Create directory if it doesn't exist
|
|
37
|
+
os.makedirs(upload_dir, exist_ok=True)
|
|
38
|
+
return upload_dir
|
|
39
|
+
|
|
40
|
+
def is_safe_path(basedir: str, path: str) -> bool:
|
|
41
|
+
"""Check if the path is safe (within basedir)"""
|
|
42
|
+
try:
|
|
43
|
+
# Resolve both paths to absolute paths
|
|
44
|
+
basedir = os.path.abspath(basedir)
|
|
45
|
+
path = os.path.abspath(path)
|
|
46
|
+
|
|
47
|
+
# Check if path starts with basedir
|
|
48
|
+
return path.startswith(basedir)
|
|
49
|
+
except:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
@router.post("/upload")
|
|
53
|
+
async def upload_files(
|
|
54
|
+
files: List[UploadFile] = File(...),
|
|
55
|
+
session_id: Optional[str] = Form(None),
|
|
56
|
+
db: AsyncSession = Depends(get_db_session)
|
|
57
|
+
):
|
|
58
|
+
"""Upload files to workspace/upload_files folder or session-specific folder"""
|
|
59
|
+
try:
|
|
60
|
+
from ..shared_state import workspace_dir
|
|
61
|
+
|
|
62
|
+
upload_dir = get_upload_directory(session_id)
|
|
63
|
+
uploaded_file_info = []
|
|
64
|
+
|
|
65
|
+
for file in files:
|
|
66
|
+
if not file.filename:
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
# Generate unique file ID
|
|
70
|
+
file_id = uuid7str()
|
|
71
|
+
|
|
72
|
+
# Create safe filename
|
|
73
|
+
filename = file.filename
|
|
74
|
+
file_path = os.path.join(upload_dir, filename)
|
|
75
|
+
|
|
76
|
+
# Handle duplicate filenames by adding suffix
|
|
77
|
+
counter = 1
|
|
78
|
+
base_name, ext = os.path.splitext(filename)
|
|
79
|
+
while os.path.exists(file_path):
|
|
80
|
+
new_filename = f"{base_name}_{counter}{ext}"
|
|
81
|
+
file_path = os.path.join(upload_dir, new_filename)
|
|
82
|
+
filename = new_filename
|
|
83
|
+
counter += 1
|
|
84
|
+
|
|
85
|
+
# Ensure path is safe
|
|
86
|
+
if not is_safe_path(upload_dir, file_path):
|
|
87
|
+
raise HTTPException(status_code=400, detail=f"Invalid file path: {filename}")
|
|
88
|
+
|
|
89
|
+
# Save file
|
|
90
|
+
try:
|
|
91
|
+
with open(file_path, "wb") as buffer:
|
|
92
|
+
shutil.copyfileobj(file.file, buffer)
|
|
93
|
+
|
|
94
|
+
# Get file info
|
|
95
|
+
file_size = os.path.getsize(file_path)
|
|
96
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
97
|
+
relative_path = os.path.relpath(file_path, workspace_dir)
|
|
98
|
+
|
|
99
|
+
# Store file metadata in database
|
|
100
|
+
uploaded_file = await UploadedFileQueries.create_file_record(
|
|
101
|
+
db=db,
|
|
102
|
+
file_id=file_id,
|
|
103
|
+
original_filename=file.filename,
|
|
104
|
+
stored_filename=filename,
|
|
105
|
+
file_path=file_path,
|
|
106
|
+
session_id=session_id,
|
|
107
|
+
file_size=file_size,
|
|
108
|
+
mime_type=mime_type or "application/octet-stream",
|
|
109
|
+
relative_path=relative_path
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Create response metadata
|
|
113
|
+
file_metadata = {
|
|
114
|
+
"file_id": uploaded_file.file_id,
|
|
115
|
+
"original_filename": uploaded_file.original_filename,
|
|
116
|
+
"stored_filename": uploaded_file.stored_filename,
|
|
117
|
+
"session_id": uploaded_file.session_id,
|
|
118
|
+
"file_size": uploaded_file.file_size,
|
|
119
|
+
"mime_type": uploaded_file.mime_type,
|
|
120
|
+
"upload_time": uploaded_file.upload_time.isoformat(),
|
|
121
|
+
"file_path": file_path
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
uploaded_file_info.append(file_metadata)
|
|
125
|
+
|
|
126
|
+
logger.info(f"File uploaded: {filename} (ID: {file_id}) to {upload_dir}")
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Failed to save file {filename}: {e}")
|
|
130
|
+
# If database record was created but file save failed, clean up
|
|
131
|
+
try:
|
|
132
|
+
await UploadedFileQueries.hard_delete_file(db, file_id)
|
|
133
|
+
except:
|
|
134
|
+
pass
|
|
135
|
+
raise HTTPException(status_code=500, detail=f"Failed to save file {filename}: {str(e)}")
|
|
136
|
+
|
|
137
|
+
# Commit all database changes
|
|
138
|
+
await db.commit()
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"message": f"Successfully uploaded {len(uploaded_file_info)} files",
|
|
142
|
+
"files": uploaded_file_info,
|
|
143
|
+
"upload_directory": upload_dir
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"File upload failed: {e}")
|
|
148
|
+
raise HTTPException(status_code=500, detail=f"File upload failed: {str(e)}")
|
|
149
|
+
|
|
150
|
+
@router.get("/{file_id}")
|
|
151
|
+
async def download_file(file_id: str, db: AsyncSession = Depends(get_db_session)):
|
|
152
|
+
"""Download file by file ID"""
|
|
153
|
+
from ..shared_state import workspace_dir
|
|
154
|
+
|
|
155
|
+
uploaded_file = await UploadedFileQueries.get_file(db, file_id)
|
|
156
|
+
if not uploaded_file:
|
|
157
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
158
|
+
|
|
159
|
+
file_path = uploaded_file.file_path
|
|
160
|
+
|
|
161
|
+
if not os.path.exists(file_path):
|
|
162
|
+
raise HTTPException(status_code=404, detail="File not found on disk")
|
|
163
|
+
|
|
164
|
+
# Ensure path is safe
|
|
165
|
+
if not is_safe_path(workspace_dir, file_path):
|
|
166
|
+
raise HTTPException(status_code=403, detail="Access denied")
|
|
167
|
+
|
|
168
|
+
return FileResponse(
|
|
169
|
+
path=file_path,
|
|
170
|
+
filename=uploaded_file.original_filename,
|
|
171
|
+
media_type=uploaded_file.mime_type
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@router.get("")
|
|
175
|
+
async def list_uploaded_files(
|
|
176
|
+
query: FileListQueryRequest = Depends(),
|
|
177
|
+
db: AsyncSession = Depends(get_db_session)
|
|
178
|
+
):
|
|
179
|
+
"""List uploaded files, optionally filtered by session"""
|
|
180
|
+
try:
|
|
181
|
+
# Get files from database
|
|
182
|
+
uploaded_files = await UploadedFileQueries.list_files(
|
|
183
|
+
db=db,
|
|
184
|
+
session_id=query.session_id,
|
|
185
|
+
limit=query.limit,
|
|
186
|
+
offset=query.offset,
|
|
187
|
+
active_only=True
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Get total count
|
|
191
|
+
total_count = await UploadedFileQueries.count_files(
|
|
192
|
+
db=db,
|
|
193
|
+
session_id=query.session_id,
|
|
194
|
+
active_only=True
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Convert to response format (exclude file_path for security)
|
|
198
|
+
files_response = []
|
|
199
|
+
for file_record in uploaded_files:
|
|
200
|
+
files_response.append({
|
|
201
|
+
"file_id": file_record.file_id,
|
|
202
|
+
"original_filename": file_record.original_filename,
|
|
203
|
+
"stored_filename": file_record.stored_filename,
|
|
204
|
+
"session_id": file_record.session_id,
|
|
205
|
+
"file_size": file_record.file_size,
|
|
206
|
+
"mime_type": file_record.mime_type,
|
|
207
|
+
"upload_time": file_record.upload_time.isoformat(),
|
|
208
|
+
"file_path": file_record.file_path
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
"files": files_response,
|
|
213
|
+
"total_count": total_count,
|
|
214
|
+
"limit": query.limit,
|
|
215
|
+
"offset": query.offset,
|
|
216
|
+
"has_more": query.limit != -1 and (query.offset + query.limit < total_count),
|
|
217
|
+
"session_id": query.session_id
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Failed to list files: {e}")
|
|
222
|
+
raise HTTPException(status_code=500, detail=f"Failed to list files: {str(e)}")
|
|
223
|
+
|
|
224
|
+
@router.delete("/{file_id}")
|
|
225
|
+
async def delete_file(file_id: str, db: AsyncSession = Depends(get_db_session)):
|
|
226
|
+
"""Delete uploaded file by file ID"""
|
|
227
|
+
# Get file record
|
|
228
|
+
uploaded_file = await UploadedFileQueries.get_file(db, file_id)
|
|
229
|
+
if not uploaded_file:
|
|
230
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
# Remove file from disk
|
|
234
|
+
if os.path.exists(uploaded_file.file_path):
|
|
235
|
+
os.remove(uploaded_file.file_path)
|
|
236
|
+
|
|
237
|
+
# Soft delete from database
|
|
238
|
+
success = await UploadedFileQueries.delete_file(db, file_id)
|
|
239
|
+
if not success:
|
|
240
|
+
raise HTTPException(status_code=500, detail="Failed to delete file record")
|
|
241
|
+
|
|
242
|
+
await db.commit()
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
"message": f"File {uploaded_file.original_filename} deleted successfully",
|
|
246
|
+
"file_id": file_id
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
except HTTPException:
|
|
250
|
+
raise
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.error(f"Failed to delete file {file_id}: {e}")
|
|
253
|
+
raise HTTPException(status_code=500, detail=f"Failed to delete file: {str(e)}")
|
|
254
|
+
|
|
255
|
+
@router.get("/session/{session_id}")
|
|
256
|
+
async def list_session_files(
|
|
257
|
+
session_id: str,
|
|
258
|
+
query: SessionFilesQueryRequest = Depends()
|
|
259
|
+
):
|
|
260
|
+
"""List all files in a session directory"""
|
|
261
|
+
try:
|
|
262
|
+
from ..shared_state import workspace_dir
|
|
263
|
+
session_dir = os.path.join(workspace_dir, session_id)
|
|
264
|
+
|
|
265
|
+
if not os.path.exists(session_dir):
|
|
266
|
+
return {
|
|
267
|
+
"session_id": session_id,
|
|
268
|
+
"files": [],
|
|
269
|
+
"directories": [],
|
|
270
|
+
"message": "Session directory not found"
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
files = []
|
|
274
|
+
directories = []
|
|
275
|
+
|
|
276
|
+
for root, dirs, filenames in os.walk(session_dir):
|
|
277
|
+
# Calculate relative path from session directory
|
|
278
|
+
rel_root = os.path.relpath(root, session_dir)
|
|
279
|
+
if rel_root == ".":
|
|
280
|
+
rel_root = ""
|
|
281
|
+
|
|
282
|
+
# Add directories if requested
|
|
283
|
+
if query.include_directories:
|
|
284
|
+
for dirname in dirs:
|
|
285
|
+
dir_path = os.path.join(rel_root, dirname) if rel_root else dirname
|
|
286
|
+
directories.append({
|
|
287
|
+
"name": dirname,
|
|
288
|
+
"path": dir_path,
|
|
289
|
+
"type": "directory"
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
# Add files
|
|
293
|
+
for filename in filenames:
|
|
294
|
+
file_path = os.path.join(root, filename)
|
|
295
|
+
rel_path = os.path.join(rel_root, filename) if rel_root else filename
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
stat = os.stat(file_path)
|
|
299
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
300
|
+
|
|
301
|
+
files.append({
|
|
302
|
+
"name": filename,
|
|
303
|
+
"path": rel_path,
|
|
304
|
+
"size": stat.st_size,
|
|
305
|
+
"mime_type": mime_type or "application/octet-stream",
|
|
306
|
+
"modified_time": datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
|
307
|
+
"type": "file"
|
|
308
|
+
})
|
|
309
|
+
except Exception as e:
|
|
310
|
+
logger.warning(f"Could not get stats for file {file_path}: {e}")
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
"session_id": session_id,
|
|
314
|
+
"files": files,
|
|
315
|
+
"directories": directories if query.include_directories else [],
|
|
316
|
+
"total_files": len(files),
|
|
317
|
+
"total_directories": len(directories) if query.include_directories else 0
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
except Exception as e:
|
|
321
|
+
logger.error(f"Failed to list session files for {session_id}: {e}")
|
|
322
|
+
raise HTTPException(status_code=500, detail=f"Failed to list session files: {str(e)}")
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Request/Response Models for VibeSurf Backend
|
|
3
|
+
|
|
4
|
+
Pydantic models for API serialization and validation.
|
|
5
|
+
With LLM Profile management support.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, validator
|
|
9
|
+
from typing import List, Optional, Dict, Any
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
# LLM Profile Models
|
|
14
|
+
class LLMProfileCreateRequest(BaseModel):
|
|
15
|
+
"""Request model for creating a new LLM profile"""
|
|
16
|
+
profile_name: str = Field(description="Unique profile name", min_length=1, max_length=100)
|
|
17
|
+
provider: str = Field(description="LLM provider (openai, anthropic, google, azure_openai)")
|
|
18
|
+
model: str = Field(description="Model name")
|
|
19
|
+
api_key: Optional[str] = Field(default=None, description="API key (will be encrypted)")
|
|
20
|
+
base_url: Optional[str] = Field(default=None, description="Custom base URL")
|
|
21
|
+
temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0)
|
|
22
|
+
max_tokens: Optional[int] = Field(default=None, gt=0)
|
|
23
|
+
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
|
24
|
+
frequency_penalty: Optional[float] = Field(default=None, ge=-2.0, le=2.0)
|
|
25
|
+
seed: Optional[int] = Field(default=None)
|
|
26
|
+
provider_config: Optional[Dict[str, Any]] = Field(default=None, description="Provider-specific config")
|
|
27
|
+
description: Optional[str] = Field(default=None, description="Profile description")
|
|
28
|
+
is_default: bool = Field(default=False, description="Set as default profile")
|
|
29
|
+
|
|
30
|
+
class LLMProfileUpdateRequest(BaseModel):
|
|
31
|
+
"""Request model for updating an LLM profile"""
|
|
32
|
+
provider: Optional[str] = None
|
|
33
|
+
model: Optional[str] = None
|
|
34
|
+
api_key: Optional[str] = None
|
|
35
|
+
base_url: Optional[str] = None
|
|
36
|
+
temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0)
|
|
37
|
+
max_tokens: Optional[int] = Field(default=None, gt=0)
|
|
38
|
+
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
|
39
|
+
frequency_penalty: Optional[float] = Field(default=None, ge=-2.0, le=2.0)
|
|
40
|
+
seed: Optional[int] = None
|
|
41
|
+
provider_config: Optional[Dict[str, Any]] = None
|
|
42
|
+
description: Optional[str] = None
|
|
43
|
+
is_active: Optional[bool] = None
|
|
44
|
+
is_default: Optional[bool] = None
|
|
45
|
+
|
|
46
|
+
class LLMProfileResponse(BaseModel):
|
|
47
|
+
"""Response model for LLM profile data (without API key)"""
|
|
48
|
+
profile_id: str
|
|
49
|
+
profile_name: str
|
|
50
|
+
provider: str
|
|
51
|
+
model: str
|
|
52
|
+
base_url: Optional[str] = None
|
|
53
|
+
# Note: API key is intentionally excluded from response
|
|
54
|
+
temperature: Optional[float] = None
|
|
55
|
+
max_tokens: Optional[int] = None
|
|
56
|
+
top_p: Optional[float] = None
|
|
57
|
+
frequency_penalty: Optional[float] = None
|
|
58
|
+
seed: Optional[int] = None
|
|
59
|
+
provider_config: Optional[Dict[str, Any]] = None
|
|
60
|
+
description: Optional[str] = None
|
|
61
|
+
is_active: bool
|
|
62
|
+
is_default: bool
|
|
63
|
+
created_at: datetime
|
|
64
|
+
updated_at: datetime
|
|
65
|
+
last_used_at: Optional[datetime] = None
|
|
66
|
+
|
|
67
|
+
class Config:
|
|
68
|
+
from_attributes = True
|
|
69
|
+
|
|
70
|
+
# MCP Profile Models
|
|
71
|
+
class McpProfileCreateRequest(BaseModel):
|
|
72
|
+
"""Request model for creating a new MCP profile"""
|
|
73
|
+
display_name: str = Field(description="Display name for MCP profile", min_length=1, max_length=100)
|
|
74
|
+
mcp_server_name: str = Field(description="MCP server name/identifier", min_length=1, max_length=100)
|
|
75
|
+
mcp_server_params: Dict[str, Any] = Field(description="MCP server parameters (command, args, etc.)")
|
|
76
|
+
description: Optional[str] = Field(default=None, description="Profile description")
|
|
77
|
+
|
|
78
|
+
class McpProfileUpdateRequest(BaseModel):
|
|
79
|
+
"""Request model for updating an MCP profile"""
|
|
80
|
+
display_name: Optional[str] = Field(default=None, min_length=1, max_length=100)
|
|
81
|
+
mcp_server_name: Optional[str] = Field(default=None, min_length=1, max_length=100)
|
|
82
|
+
mcp_server_params: Optional[Dict[str, Any]] = None
|
|
83
|
+
description: Optional[str] = None
|
|
84
|
+
is_active: Optional[bool] = None
|
|
85
|
+
|
|
86
|
+
class McpProfileResponse(BaseModel):
|
|
87
|
+
"""Response model for MCP profile data"""
|
|
88
|
+
mcp_id: str
|
|
89
|
+
display_name: str
|
|
90
|
+
mcp_server_name: str
|
|
91
|
+
mcp_server_params: Dict[str, Any]
|
|
92
|
+
description: Optional[str] = None
|
|
93
|
+
is_active: bool
|
|
94
|
+
created_at: datetime
|
|
95
|
+
updated_at: datetime
|
|
96
|
+
last_used_at: Optional[datetime] = None
|
|
97
|
+
|
|
98
|
+
class Config:
|
|
99
|
+
from_attributes = True
|
|
100
|
+
|
|
101
|
+
# Task Models
|
|
102
|
+
class TaskCreateRequest(BaseModel):
|
|
103
|
+
"""Request model for creating a new task"""
|
|
104
|
+
session_id: str = Field(description="Session identifier")
|
|
105
|
+
task_description: str = Field(description="The task description")
|
|
106
|
+
llm_profile_name: str = Field(description="LLM profile name to use")
|
|
107
|
+
upload_files_path: Optional[str] = Field(default=None, description="Path to uploaded files")
|
|
108
|
+
mcp_server_config: Optional[Dict[str, Any]] = Field(default=None, description="MCP server configuration")
|
|
109
|
+
|
|
110
|
+
class TaskControlRequest(BaseModel):
|
|
111
|
+
"""Request model for task control operations (pause/resume/stop)"""
|
|
112
|
+
reason: Optional[str] = Field(default=None, description="Reason for the operation")
|
|
113
|
+
|
|
114
|
+
class TaskResponse(BaseModel):
|
|
115
|
+
"""Response model for task data"""
|
|
116
|
+
task_id: str
|
|
117
|
+
session_id: str
|
|
118
|
+
task_description: str
|
|
119
|
+
status: str
|
|
120
|
+
llm_profile_name: str
|
|
121
|
+
upload_files_path: Optional[str] = None
|
|
122
|
+
workspace_dir: Optional[str] = None
|
|
123
|
+
mcp_server_config: Optional[Dict[str, Any]] = None
|
|
124
|
+
task_result: Optional[str] = None
|
|
125
|
+
error_message: Optional[str] = None
|
|
126
|
+
report_path: Optional[str] = None
|
|
127
|
+
created_at: datetime
|
|
128
|
+
updated_at: datetime
|
|
129
|
+
started_at: Optional[datetime] = None
|
|
130
|
+
completed_at: Optional[datetime] = None
|
|
131
|
+
task_metadata: Optional[Dict[str, Any]] = None
|
|
132
|
+
|
|
133
|
+
class Config:
|
|
134
|
+
from_attributes = True
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def from_orm(cls, task):
|
|
138
|
+
"""Create response from SQLAlchemy Task model"""
|
|
139
|
+
return cls(
|
|
140
|
+
task_id=task.task_id,
|
|
141
|
+
session_id=task.session_id,
|
|
142
|
+
task_description=task.task_description,
|
|
143
|
+
status=task.status.value,
|
|
144
|
+
llm_profile_name=task.llm_profile_name,
|
|
145
|
+
upload_files_path=task.upload_files_path,
|
|
146
|
+
workspace_dir=task.workspace_dir,
|
|
147
|
+
mcp_server_config=task.mcp_server_config,
|
|
148
|
+
task_result=task.task_result,
|
|
149
|
+
error_message=task.error_message,
|
|
150
|
+
report_path=task.report_path,
|
|
151
|
+
created_at=task.created_at,
|
|
152
|
+
updated_at=task.updated_at,
|
|
153
|
+
started_at=task.started_at,
|
|
154
|
+
completed_at=task.completed_at,
|
|
155
|
+
task_metadata=task.task_metadata
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
class TaskStatusResponse(BaseModel):
|
|
159
|
+
"""Response model for task status information"""
|
|
160
|
+
task_id: Optional[str] = None
|
|
161
|
+
session_id: Optional[str] = None
|
|
162
|
+
status: Optional[str] = None
|
|
163
|
+
task_description: Optional[str] = None
|
|
164
|
+
created_at: Optional[datetime] = None
|
|
165
|
+
started_at: Optional[datetime] = None
|
|
166
|
+
error_message: Optional[str] = None
|
|
167
|
+
is_running: bool = False
|
|
168
|
+
|
|
169
|
+
class TaskListResponse(BaseModel):
|
|
170
|
+
"""Response model for task list"""
|
|
171
|
+
tasks: List[TaskResponse]
|
|
172
|
+
total_count: int
|
|
173
|
+
session_id: Optional[str] = None
|
|
174
|
+
|
|
175
|
+
class ErrorResponse(BaseModel):
|
|
176
|
+
"""Standard error response model"""
|
|
177
|
+
error: str
|
|
178
|
+
detail: Optional[str] = None
|
|
179
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
|
180
|
+
|
|
181
|
+
class ControlOperationResponse(BaseModel):
|
|
182
|
+
"""Response model for control operations"""
|
|
183
|
+
success: bool
|
|
184
|
+
message: str
|
|
185
|
+
operation: str
|
|
186
|
+
timestamp: datetime
|
|
187
|
+
details: Optional[Dict[str, Any]] = None
|
|
188
|
+
|
|
189
|
+
# Activity Log Models (for VibeSurf agent activity)
|
|
190
|
+
class ActivityLogEntry(BaseModel):
|
|
191
|
+
"""Model for VibeSurf agent activity log entry"""
|
|
192
|
+
timestamp: datetime
|
|
193
|
+
level: str
|
|
194
|
+
message: str
|
|
195
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
196
|
+
|
|
197
|
+
class ActivityLogResponse(BaseModel):
|
|
198
|
+
"""Response model for activity logs"""
|
|
199
|
+
logs: List[ActivityLogEntry]
|
|
200
|
+
total_count: int
|
|
201
|
+
session_id: Optional[str] = None
|
|
202
|
+
task_id: Optional[str] = None
|
|
203
|
+
|
|
204
|
+
# Activity API Request Models
|
|
205
|
+
class ActivityQueryRequest(BaseModel):
|
|
206
|
+
"""Request model for getting recent tasks"""
|
|
207
|
+
limit: int = Field(default=-1, ge=-1, le=1000, description="Number of recent tasks to retrieve (-1 for all)")
|
|
208
|
+
|
|
209
|
+
class SessionActivityQueryRequest(BaseModel):
|
|
210
|
+
"""Request model for getting session activity logs"""
|
|
211
|
+
limit: int = Field(default=-1, ge=-1, le=1000, description="Number of activity logs to retrieve (-1 for all)")
|
|
212
|
+
message_index: Optional[int] = Field(default=None, ge=0, description="Specific message index to retrieve")
|
|
213
|
+
|
|
214
|
+
# File API Request Models
|
|
215
|
+
class FileUploadRequest(BaseModel):
|
|
216
|
+
"""Request model for file upload (for form validation)"""
|
|
217
|
+
session_id: Optional[str] = Field(default=None, description="Session ID for file association")
|
|
218
|
+
|
|
219
|
+
class FileListQueryRequest(BaseModel):
|
|
220
|
+
"""Request model for listing uploaded files"""
|
|
221
|
+
session_id: Optional[str] = Field(default=None, description="Filter by session ID")
|
|
222
|
+
limit: int = Field(default=-1, ge=-1, le=1000, description="Number of files to retrieve (-1 for all)")
|
|
223
|
+
offset: int = Field(default=0, ge=0, description="Number of files to skip")
|
|
224
|
+
|
|
225
|
+
class SessionFilesQueryRequest(BaseModel):
|
|
226
|
+
"""Request model for listing session files"""
|
|
227
|
+
include_directories: bool = Field(default=False, description="Whether to include directories in the response")
|
|
228
|
+
|
|
229
|
+
# File Upload Models
|
|
230
|
+
class UploadedFileResponse(BaseModel):
|
|
231
|
+
"""Response model for uploaded file information"""
|
|
232
|
+
filename: str
|
|
233
|
+
file_path: str
|
|
234
|
+
file_size: Optional[int] = None
|
|
235
|
+
mime_type: Optional[str] = None
|
|
236
|
+
uploaded_at: datetime
|
|
237
|
+
|
|
238
|
+
# Configuration Models (for config endpoints)
|
|
239
|
+
class LLMConfigResponse(BaseModel):
|
|
240
|
+
"""Response model for LLM configuration"""
|
|
241
|
+
provider: str
|
|
242
|
+
model: str
|
|
243
|
+
temperature: Optional[float] = None
|
|
244
|
+
max_tokens: Optional[int] = None
|
|
245
|
+
available_providers: List[str] = []
|
|
246
|
+
|
|
247
|
+
class ControllerConfigRequest(BaseModel):
|
|
248
|
+
"""Request model for updating controller configuration"""
|
|
249
|
+
exclude_actions: Optional[List[str]] = Field(default=None, description="Actions to exclude from execution")
|
|
250
|
+
max_actions_per_task: Optional[int] = Field(default=None, gt=0, description="Maximum actions per task")
|
|
251
|
+
display_files_in_done_text: Optional[bool] = Field(default=None, description="Whether to display files in done text")
|
|
252
|
+
|
|
253
|
+
class ControllerConfigResponse(BaseModel):
|
|
254
|
+
"""Response model for controller configuration"""
|
|
255
|
+
exclude_actions: List[str] = []
|
|
256
|
+
max_actions_per_task: int = 100
|
|
257
|
+
display_files_in_done_text: bool = True
|