tech-envision commited on
Commit
4f46c78
Β·
1 Parent(s): c4aea9f

Add VM file management API endpoints

Browse files
Files changed (2) hide show
  1. README.md +4 -0
  2. api_app/__init__.py +68 -2
README.md CHANGED
@@ -91,6 +91,10 @@ uvicorn api_app:app --host 0.0.0.0 --port 8000
91
  - `POST /chat/stream` – stream the assistant's response as plain text.
92
  - `POST /upload` – upload a document that can be referenced in chats.
93
  - `GET /sessions/{user}` – list available session names for a user.
 
 
 
 
94
 
95
  Example request:
96
 
 
91
  - `POST /chat/stream` – stream the assistant's response as plain text.
92
  - `POST /upload` – upload a document that can be referenced in chats.
93
  - `GET /sessions/{user}` – list available session names for a user.
94
+ - `GET /vm/{user}/list` – list files in a directory under `/data`.
95
+ - `GET /vm/{user}/file` – read a file from the VM.
96
+ - `POST /vm/{user}/file` – create or overwrite a file in the VM.
97
+ - `DELETE /vm/{user}/file` – delete a file or directory from the VM.
98
 
99
  Example request:
100
 
api_app/__init__.py CHANGED
@@ -1,14 +1,17 @@
1
  from __future__ import annotations
2
 
3
- from fastapi import FastAPI, UploadFile, File, Form
4
  from fastapi.responses import StreamingResponse
5
- from fastapi import HTTPException
6
  from fastapi.middleware.cors import CORSMiddleware
7
  from pydantic import BaseModel
8
  import asyncio
9
  import os
10
  import tempfile
11
  from pathlib import Path
 
 
 
 
12
 
13
  from src.team import TeamChatSession
14
  from src.log import get_logger
@@ -24,6 +27,26 @@ class ChatRequest(BaseModel):
24
  prompt: str
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def create_app() -> FastAPI:
28
  app = FastAPI(title="LLM Backend API")
29
 
@@ -84,6 +107,49 @@ def create_app() -> FastAPI:
84
  async def health():
85
  return {"status": "ok"}
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  return app
88
 
89
 
 
1
  from __future__ import annotations
2
 
3
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
4
  from fastapi.responses import StreamingResponse
 
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from pydantic import BaseModel
7
  import asyncio
8
  import os
9
  import tempfile
10
  from pathlib import Path
11
+ from typing import List
12
+ import shutil
13
+
14
+ from src.config import UPLOAD_DIR
15
 
16
  from src.team import TeamChatSession
17
  from src.log import get_logger
 
27
  prompt: str
28
 
29
 
30
+ class FileWriteRequest(BaseModel):
31
+ path: str
32
+ content: str
33
+
34
+
35
+ def _vm_host_path(user: str, vm_path: str) -> Path:
36
+ """Return the host path for a given ``vm_path`` inside ``/data``."""
37
+
38
+ try:
39
+ rel = Path(vm_path).relative_to("/data")
40
+ except ValueError as exc: # pragma: no cover - invalid path
41
+ raise HTTPException(status_code=400, detail="Path must start with /data") from exc
42
+
43
+ base = (Path(UPLOAD_DIR) / user).resolve()
44
+ target = (base / rel).resolve()
45
+ if not target.is_relative_to(base):
46
+ raise HTTPException(status_code=400, detail="Invalid path")
47
+ return target
48
+
49
+
50
  def create_app() -> FastAPI:
51
  app = FastAPI(title="LLM Backend API")
52
 
 
107
  async def health():
108
  return {"status": "ok"}
109
 
110
+ @app.get("/vm/{user}/list")
111
+ async def list_vm_dir(user: str, path: str = "/data"):
112
+ target = _vm_host_path(user, path)
113
+ if not target.exists():
114
+ raise HTTPException(status_code=404, detail="Directory not found")
115
+ if not target.is_dir():
116
+ raise HTTPException(status_code=400, detail="Not a directory")
117
+ entries: List[dict[str, str | bool]] = []
118
+ for entry in sorted(target.iterdir()):
119
+ entries.append({"name": entry.name, "is_dir": entry.is_dir()})
120
+ return {"entries": entries}
121
+
122
+ @app.get("/vm/{user}/file")
123
+ async def read_vm_file(user: str, path: str):
124
+ target = _vm_host_path(user, path)
125
+ if not target.exists():
126
+ raise HTTPException(status_code=404, detail="File not found")
127
+ if target.is_dir():
128
+ raise HTTPException(status_code=400, detail="Path is a directory")
129
+ try:
130
+ content = target.read_text()
131
+ except UnicodeDecodeError:
132
+ raise HTTPException(status_code=400, detail="Binary file not supported")
133
+ return {"content": content}
134
+
135
+ @app.post("/vm/{user}/file")
136
+ async def write_vm_file(user: str, req: FileWriteRequest):
137
+ target = _vm_host_path(user, req.path)
138
+ target.parent.mkdir(parents=True, exist_ok=True)
139
+ target.write_text(req.content)
140
+ return {"status": "ok"}
141
+
142
+ @app.delete("/vm/{user}/file")
143
+ async def delete_vm_file(user: str, path: str):
144
+ target = _vm_host_path(user, path)
145
+ if target.is_dir():
146
+ shutil.rmtree(target)
147
+ elif target.exists():
148
+ target.unlink()
149
+ else:
150
+ raise HTTPException(status_code=404, detail="File not found")
151
+ return {"status": "deleted"}
152
+
153
  return app
154
 
155