feat: add remote workspaces and verified deliverables

This commit is contained in:
wuyang
2026-07-26 12:58:04 +08:00
parent 37f83eaac7
commit 0b4d799216
30 changed files with 1595 additions and 55 deletions
+55 -1
View File
@@ -1,9 +1,11 @@
import asyncio
from contextlib import asynccontextmanager
from typing import Annotated
from urllib.parse import quote
import uvicorn
from fastapi import Depends, FastAPI, Header, HTTPException
from fastapi import Depends, FastAPI, Header, HTTPException, Query
from fastapi.responses import Response, StreamingResponse
from agent_platform.auth import UserIdentity, decode_openwebui_identity, verify_service_bearer
from agent_platform.config import Settings, get_settings
@@ -19,6 +21,7 @@ from agent_platform.gateway.schemas import (
SearchFilesRequest,
StartProcessRequest,
ToolResult,
WorkspaceListing,
WorkspaceStatus,
WriteFileRequest,
)
@@ -70,6 +73,11 @@ def create_app(
async with mutation_locks_guard:
return mutation_locks.setdefault(user_id, asyncio.Lock())
def attachment_header(filename: str) -> str:
fallback = "".join(character for character in filename if character.isascii() and character.isalnum())
fallback = fallback[:80] or "download"
return f"attachment; filename={fallback}; filename*=UTF-8''{quote(filename)}"
@app.get("/health", include_in_schema=False)
async def health() -> dict:
return {"status": "ok", "provider": settings.execution_provider}
@@ -85,6 +93,52 @@ def create_app(
except ValueError as exc:
raise translate_value_error(exc) from exc
@app.get("/v1/files", response_model=WorkspaceListing, operation_id="browse_workspace_files")
async def browse_workspace_files(
identity: Identity,
path: Annotated[str, Query(max_length=4096)] = ".",
) -> WorkspaceListing:
try:
return await executor().browse_files(identity.user_id, path)
except ValueError as exc:
raise translate_value_error(exc) from exc
except FileNotFoundError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.get("/v1/files/download", operation_id="download_workspace_file")
async def download_workspace_file(
identity: Identity,
path: Annotated[str, Query(max_length=4096)],
) -> Response:
try:
download = await executor().download_file(identity.user_id, path)
except ValueError as exc:
raise translate_value_error(exc) from exc
except (FileNotFoundError, IsADirectoryError) as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return Response(
content=download.content or b"",
media_type=download.media_type,
headers={"Content-Disposition": attachment_header(download.filename)},
)
@app.get("/v1/files/archive", operation_id="archive_workspace_files")
async def archive_workspace_files(
identity: Identity,
path: Annotated[str, Query(max_length=4096)] = ".",
) -> StreamingResponse:
try:
download = await executor().archive_files(identity.user_id, path)
except ValueError as exc:
raise translate_value_error(exc) from exc
except FileNotFoundError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return StreamingResponse(
download.chunks or iter(()),
media_type=download.media_type,
headers={"Content-Disposition": attachment_header(download.filename)},
)
@app.post("/v1/tools/read_file", response_model=ToolResult, operation_id="read_file")
async def read_file(body: ReadFileRequest, identity: Identity) -> ToolResult:
try: