391 lines
8.8 KiB
Markdown
391 lines
8.8 KiB
Markdown
# codespace 可运行后端骨架设计
|
||
|
||
日期:2026-07-02
|
||
|
||
## 目标
|
||
|
||
在当前目录 `/Users/taochen/llm/codespace` 生成一个 Go 后端项目骨架。第一版目标是可运行、可测试、边界清晰的 Workspace Service,而不是完整 Web IDE。
|
||
|
||
核心能力:
|
||
|
||
- 每个 workspace 对应本地文件系统中的一个目录。
|
||
- 文件读写统一通过 `FileSystem` 接口,第一版实现 `LocalFS`。
|
||
- OpenCode 以本机进程方式启动,工作目录 `cwd` 指向 workspace root。
|
||
- HTTP handler 不直接操作文件系统或进程,必须经过 service 层。
|
||
- 预留 watcher、Git、LSP、AI Agent 的后续扩展点。
|
||
- 不依赖 Docker。
|
||
|
||
## 非目标
|
||
|
||
第一版不实现:
|
||
|
||
- 用户认证和权限系统。
|
||
- Docker / 容器隔离。
|
||
- Git 集成。
|
||
- LSP。
|
||
- AI Agent 编排。
|
||
- Web 前端。
|
||
- 完整 watcher / WebSocket 文件事件推送。
|
||
|
||
这些能力以后接入,但不能污染第一版的核心边界。
|
||
|
||
## 目录结构
|
||
|
||
```text
|
||
.
|
||
├── cmd/
|
||
│ └── server/
|
||
│ └── main.go
|
||
├── configs/
|
||
│ └── config.yaml
|
||
├── internal/
|
||
│ ├── api/
|
||
│ │ ├── router.go
|
||
│ │ ├── workspace_handler.go
|
||
│ │ ├── file_handler.go
|
||
│ │ └── process_handler.go
|
||
│ ├── workspace/
|
||
│ │ ├── manager.go
|
||
│ │ ├── workspace.go
|
||
│ │ └── path.go
|
||
│ ├── fs/
|
||
│ │ ├── filesystem.go
|
||
│ │ ├── localfs.go
|
||
│ │ └── fileinfo.go
|
||
│ ├── process/
|
||
│ │ ├── manager.go
|
||
│ │ ├── session.go
|
||
│ │ └── opencode.go
|
||
│ ├── watcher/
|
||
│ │ └── watcher.go
|
||
│ ├── service/
|
||
│ │ ├── workspace_service.go
|
||
│ │ ├── file_service.go
|
||
│ │ └── process_service.go
|
||
│ ├── model/
|
||
│ │ ├── workspace.go
|
||
│ │ ├── file.go
|
||
│ │ └── process.go
|
||
│ └── util/
|
||
│ ├── path.go
|
||
│ └── errors.go
|
||
├── pkg/
|
||
│ ├── config/
|
||
│ ├── logger/
|
||
│ └── response/
|
||
├── web/
|
||
├── docs/
|
||
├── Makefile
|
||
├── go.mod
|
||
└── README.md
|
||
```
|
||
|
||
## 架构
|
||
|
||
```text
|
||
HTTP request
|
||
↓
|
||
internal/api handler
|
||
↓
|
||
internal/service
|
||
↓
|
||
workspace.Manager / fs.FileSystem / process.Manager
|
||
↓
|
||
local workspace directory + opencode process
|
||
```
|
||
|
||
边界规则:
|
||
|
||
1. `api` 只负责 HTTP 编解码、路由参数、状态码。
|
||
2. `service` 负责业务编排和校验。
|
||
3. `workspace` 负责 workspace 元数据和 root 目录管理。
|
||
4. `fs` 负责 workspace 内文件操作。
|
||
5. `process` 负责进程生命周期。
|
||
6. `watcher` 第一版只保留接口/占位,不接入运行链路。
|
||
|
||
## 核心数据结构
|
||
|
||
### Workspace
|
||
|
||
```go
|
||
type Workspace struct {
|
||
ID string `json:"id"`
|
||
Root string `json:"root"`
|
||
}
|
||
```
|
||
|
||
含义:
|
||
|
||
- `ID` 是外部 API 使用的 workspace 标识。
|
||
- `Root` 是服务端本地绝对路径。
|
||
- 第一版不引入数据库,workspace 是否存在由目录和 manager 判断。
|
||
|
||
### FileInfo
|
||
|
||
```go
|
||
type FileInfo struct {
|
||
Name string `json:"name"`
|
||
Path string `json:"path"`
|
||
IsDir bool `json:"isDir"`
|
||
Size int64 `json:"size"`
|
||
ModTime time.Time `json:"modTime"`
|
||
}
|
||
```
|
||
|
||
`Path` 始终是 workspace 内相对路径,不向 API 暴露宿主机绝对路径。
|
||
|
||
### ProcessStatus
|
||
|
||
```go
|
||
type ProcessStatus struct {
|
||
WorkspaceID string `json:"workspaceId"`
|
||
Running bool `json:"running"`
|
||
PID int `json:"pid,omitempty"`
|
||
}
|
||
```
|
||
|
||
## Workspace Manager
|
||
|
||
职责:
|
||
|
||
- 创建 workspace 目录。
|
||
- 查询 workspace。
|
||
- 删除 workspace 目录。
|
||
- 生成 workspace root 路径。
|
||
|
||
接口:
|
||
|
||
```go
|
||
type Manager interface {
|
||
Create(id string) (*Workspace, error)
|
||
Get(id string) (*Workspace, error)
|
||
Delete(id string) error
|
||
}
|
||
```
|
||
|
||
路径策略:
|
||
|
||
- 配置项 `workspaceRoot` 默认使用 `./workspaces`。
|
||
- workspace root 为 `<workspaceRoot>/<workspaceID>`。
|
||
- workspace ID 第一版限制为安全路径片段,只允许字母、数字、`-`、`_`、`.`。
|
||
|
||
## FileSystem
|
||
|
||
所有文件操作必须走接口:
|
||
|
||
```go
|
||
type FileSystem interface {
|
||
List(path string) ([]FileInfo, error)
|
||
Read(path string) ([]byte, error)
|
||
Write(path string, data []byte) error
|
||
Mkdir(path string) error
|
||
Remove(path string) error
|
||
Rename(oldPath, newPath string) error
|
||
Stat(path string) (*FileInfo, error)
|
||
}
|
||
```
|
||
|
||
第一版实现 `LocalFS`:
|
||
|
||
- `LocalFS` 绑定一个 workspace root。
|
||
- 所有传入路径都是 workspace 内相对路径。
|
||
- 解析路径时必须阻止 `../` 逃逸。
|
||
- API 不返回宿主机绝对路径。
|
||
|
||
## Process Manager
|
||
|
||
职责:按 workspace 管理 OpenCode 进程。
|
||
|
||
接口:
|
||
|
||
```go
|
||
type Manager interface {
|
||
Start(workspaceID string, workspaceRoot string) error
|
||
Stop(workspaceID string) error
|
||
Restart(workspaceID string, workspaceRoot string) error
|
||
Status(workspaceID string) Status
|
||
}
|
||
```
|
||
|
||
第一版行为:
|
||
|
||
- 命令默认是 `opencode`,通过配置项 `opencodeCommand` 可改。
|
||
- `cmd.Dir = workspace.Root`。
|
||
- `HOME` 默认设置为 workspace root,避免不同 workspace 共享 CLI 状态。
|
||
- 同一个 workspace 已有运行进程时,再次 start 返回已运行错误,不重复启动。
|
||
- stop 使用进程 kill;优雅退出可以后续补。
|
||
|
||
## Service 层
|
||
|
||
### WorkspaceService
|
||
|
||
- `CreateWorkspace(id string)`
|
||
- `GetWorkspace(id string)`
|
||
- `DeleteWorkspace(id string)`
|
||
|
||
删除 workspace 前,先停止对应进程,避免目录被运行进程占用。
|
||
|
||
### FileService
|
||
|
||
- `List(workspaceID, path string)`
|
||
- `Read(workspaceID, path string)`
|
||
- `Write(workspaceID, path string, data []byte)`
|
||
- `Mkdir(workspaceID, path string)`
|
||
- `Remove(workspaceID, path string)`
|
||
- `Rename(workspaceID, oldPath, newPath string)`
|
||
- `Stat(workspaceID, path string)`
|
||
|
||
每次通过 workspace manager 取 workspace,再构造绑定该 root 的 `LocalFS`。
|
||
|
||
### ProcessService
|
||
|
||
- `Start(workspaceID string)`
|
||
- `Stop(workspaceID string)`
|
||
- `Restart(workspaceID string)`
|
||
- `Status(workspaceID string)`
|
||
|
||
ProcessService 不自己拼路径,只通过 workspace manager 取 root。
|
||
|
||
## HTTP API
|
||
|
||
基础路径:`/api`
|
||
|
||
### 健康检查
|
||
|
||
```text
|
||
GET /healthz
|
||
```
|
||
|
||
返回:
|
||
|
||
```json
|
||
{"status":"ok"}
|
||
```
|
||
|
||
### Workspace
|
||
|
||
```text
|
||
POST /api/workspaces
|
||
GET /api/workspaces/{id}
|
||
DELETE /api/workspaces/{id}
|
||
```
|
||
|
||
创建请求:
|
||
|
||
```json
|
||
{"id":"user1"}
|
||
```
|
||
|
||
### Files
|
||
|
||
```text
|
||
GET /api/workspaces/{id}/files?path=.
|
||
GET /api/workspaces/{id}/files/read?path=main.go
|
||
PUT /api/workspaces/{id}/files/write
|
||
POST /api/workspaces/{id}/files/mkdir
|
||
DELETE /api/workspaces/{id}/files?path=main.go
|
||
POST /api/workspaces/{id}/files/rename
|
||
```
|
||
|
||
写文件请求:
|
||
|
||
```json
|
||
{"path":"main.go","content":"package main\n"}
|
||
```
|
||
|
||
重命名请求:
|
||
|
||
```json
|
||
{"oldPath":"old.go","newPath":"new.go"}
|
||
```
|
||
|
||
### Process
|
||
|
||
```text
|
||
POST /api/workspaces/{id}/process/start
|
||
POST /api/workspaces/{id}/process/stop
|
||
POST /api/workspaces/{id}/process/restart
|
||
GET /api/workspaces/{id}/process/status
|
||
```
|
||
|
||
## 配置
|
||
|
||
`configs/config.yaml`:
|
||
|
||
```yaml
|
||
server:
|
||
addr: ":8080"
|
||
workspace:
|
||
root: "./workspaces"
|
||
process:
|
||
opencodeCommand: "opencode"
|
||
```
|
||
|
||
配置加载规则:
|
||
|
||
1. 先加载默认值。
|
||
2. 如果配置文件存在,覆盖默认值。
|
||
3. 环境变量可覆盖:
|
||
- `CODESPACE_ADDR`
|
||
- `CODESPACE_WORKSPACE_ROOT`
|
||
- `CODESPACE_OPENCODE_COMMAND`
|
||
|
||
## 错误处理
|
||
|
||
统一 JSON 响应:
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "bad_request",
|
||
"message": "invalid workspace id"
|
||
}
|
||
}
|
||
```
|
||
|
||
常见映射:
|
||
|
||
- 参数错误:400
|
||
- workspace 不存在:404
|
||
- 文件不存在:404
|
||
- 进程已运行:409
|
||
- 内部错误:500
|
||
|
||
第一版只做简单错误类型,不引入复杂错误框架。
|
||
|
||
## 测试策略
|
||
|
||
最小测试:
|
||
|
||
- workspace ID 校验和路径生成。
|
||
- LocalFS 路径逃逸防护。
|
||
- LocalFS 基础读写/列表。
|
||
- Process manager 状态逻辑使用可配置命令测试,不依赖真实 OpenCode。
|
||
- HTTP handler 可做轻量测试,至少覆盖 health 和 workspace 创建。
|
||
|
||
验收命令:
|
||
|
||
```sh
|
||
go test ./...
|
||
go run ./cmd/server
|
||
```
|
||
|
||
## 兼容性与风险
|
||
|
||
- 当前目录是项目根目录,不再额外创建 `codespace/` 子目录。
|
||
- 初始化会创建标准 Go 项目文件;如果目标文件已存在,实施阶段必须先检查,避免覆盖用户已有内容。
|
||
- 不改变公共 API,因为这是初始版本。
|
||
- 最大安全风险是路径逃逸,必须在 `LocalFS` 和 workspace ID 校验中处理。
|
||
- 最大复杂度风险是过早引入 watcher/Git/LSP/Agent;第一版明确不实现。
|
||
|
||
## 骨架完成标准
|
||
|
||
完成后应满足:
|
||
|
||
- 当前目录包含完整 Go module。
|
||
- `go test ./...` 通过。
|
||
- `go run ./cmd/server` 能启动 HTTP 服务。
|
||
- 可以创建 workspace、读写 workspace 内文件、查询/启动/停止进程。
|
||
- Handler 不直接调用 `os.*` 或 `exec.*`。
|
||
- 文件路径不能逃逸 workspace root。
|