opencode-cursor-agent/docs/MIGRATION_PLAN.md

1069 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 重構計劃cursor-api-proxy → go-zero + DDD Architecture
## 一、概述
### 目標
將 cursor-api-proxy 從標準 Go HTTP 架構重構為 go-zero + DDDClean Architecture混合架構。
### 核心原則
- **internal/**go-zero 框架特定代碼(強依賴框架)
- **pkg/**框架無關的核心業務邏輯DIP 反轉依賴)
- **api/**:契約優先的 API 定義goctl 生成基礎代碼)
### 架構分層
```
┌─────────────────────────────────────────────────────────────┐
│ api/chat.api │
│ (API 契約定義) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ internal/handler/ │
│ (HTTP Handler 層) │
│ goctl 生成 + 自訂 SSE Handler │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ internal/logic/ │
│ (業務邏輯調度層) │
│ Adapter Pattern: Request → Usecase │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ pkg/usecase/ │
│ (Application Layer) │
│ 業務流程協調、Repository 調用 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ pkg/domain/repository/ │
│ (Repository Interface) │
│ DIP 反轉依賴 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ pkg/repository/ │
│ (Infrastructure Layer) │
│ Repository Interface 實作 │
└─────────────────────────────────────────────────────────────┘
```
---
## 二、目錄結構(最終狀態)
```
.
├── api/ # [Contract] go-zero API 定義
│ └── chat.api # OpenAI 相容 API 規格
├── etc/ # [Config] 環境配置
│ ├── chat.yaml # 本地開發
│ └── chat-prod.yaml # 生產環境
├── cmd/ # [Entry] 入口點
│ ├── chat/ # HTTP proxy 服務
│ │ └── chat.go # go-zero 入口
│ └── cli/ # CLI 工具
│ ├── login.go
│ ├── accounts.go
│ ├── resethwid.go
│ └── usage.go
├── internal/ # [Framework Layer] go-zero 特定
│ ├── config/
│ │ └── config.go # 配置結構體(使用 rest.RestConf
│ ├── handler/ # HTTP handlers (goctl 生成)
│ │ ├── routes.go # 路由註冊
│ │ ├── chat_handler.go # Chat API handler
│ │ ├── health_handler.go # Health check handler
│ │ └── models_handler.go # Models list handler
│ ├── logic/ # 業務邏輯調度層
│ │ ├── chatcompletionlogic.go # Chat completion 邏輯
│ │ ├── geminichatlogic.go # Gemini chat 邏輯
│ │ ├── anthropiclogic.go # Anthropic messages 邏輯
│ │ ├── healthlogic.go # Health 邏輯
│ │ └── modelslogic.go # Models 邏輯
│ ├── middleware/ # 中間件
│ │ ├── auth.go # API Key 驗證
│ │ └── recovery.go # Panic 恢復
│ ├── svc/ # ServiceContext (DI 容器)
│ │ └── servicecontext.go
│ └── types/ # API types (goctl 生成)
│ └── types.go
├── pkg/ # [Domain] 框架無關的核心邏輯
│ ├── domain/ # <Domain Layer>
│ │ ├── entity/ # 純粹業務物件(無框架依賴)
│ │ │ ├── message.go # Message, Tool, ToolCall
│ │ │ ├── chunk.go # StreamChunk
│ │ │ ├── account.go # Account, AccountStat
│ │ │ └── config.go # ProviderConfig
│ │ ├── repository/ # Repository Interface (DIP)
│ │ │ ├── account.go # AccountPool interface
│ │ │ └── provider.go # Provider interface
│ │ ├── usecase/ # Usecase Interface
│ │ │ ├── chat.go # ChatUsecase interface
│ │ │ └── agent.go # AgentRunner interface
│ │ └── const/ # 常數
│ │ ├── models.go # Model ID 常數
│ │ └── errors.go # 錯誤定義
│ │
│ ├── repository/ # <Infrastructure Layer>
│ │ ├── account.go # AccountPool 實作
│ │ └── provider.go # Provider 工廠
│ │
│ ├── usecase/ # <Application Layer>
│ │ ├── chat.go # ChatUsecase 實作
│ │ ├── agent.go # Agent 執行邏輯
│ │ ├── sanitizer.go # 消息清理
│ │ └── toolcall.go # Tool call 處理
│ │
│ ├── provider/ # Provider 實作
│ │ ├── cursor/ # Cursor CLI provider
│ │ │ └── provider.go
│ │ └── geminiweb/ # Gemini Web provider
│ │ ├── provider.go
│ │ ├── browser.go
│ │ └── pool.go
│ │
│ ├── infrastructure/ # 基礎設施
│ │ ├── process/ # 進程管理
│ │ │ ├── runner.go
│ │ │ ├── kill_unix.go
│ │ │ └── kill_windows.go
│ │ ├── parser/ # SSE parsing
│ │ │ └── stream.go
│ │ ├── httputil/ # HTTP 工具
│ │ │ └── httputil.go
│ │ ├── logger/ # 日誌
│ │ │ └── logger.go
│ │ ├── env/ # 環境變數
│ │ │ └── env.go
│ │ ├── workspace/ # 工作區管理
│ │ │ └── workspace.go
│ │ └── winlimit/ # Windows 命令行限制
│ │ └── winlimit.go
│ │
│ └── adapter/ # API 格式適配器
│ ├── openai/ # OpenAI 格式
│ │ └── openai.go
│ └── anthropic/ # Anthropic 格式
│ └── anthropic.go
├── build/ # [Infrastructure] 建置相關
│ ├── Dockerfile
│ └── docker-compose.yml
├── scripts/ # 腳本工具
├── docs/ # 文檔
│ └── MIGRATION_PLAN.md # 本計劃文件
├── Makefile
├── go.mod
└── go.sum
```
---
## 三、執行步驟
### Phase 1: API 定義與骨架生成
**目標**:建立 go-zero 架構骨架
#### Step 1.1: 初始化 go-zero 專案
```bash
# 安裝 goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
# 建立 API 定義目錄
mkdir -p api etc
```
#### Step 1.2: 建立 `api/chat.api`
```api
syntax = "v1"
info (
title: "Cursor API Proxy"
desc: "OpenAI-compatible API proxy for Cursor/Gemini"
author: "cursor-api-proxy"
version: "1.0"
)
// ============ Types ============
type (
// Health
HealthRequest {}
HealthResponse {
Status string `json:"status"`
Version string `json:"version"`
}
// Models
ModelsRequest {}
ModelsResponse {
Object string `json:"object"`
Data []ModelData `json:"data"`
}
ModelData {
Id string `json:"id"`
Object string `json:"object"`
OwnedBy string `json:"owned_by"`
}
// Chat Completions
ChatCompletionRequest {
Model string `json:"model"`
Messages []Message `json:"messages"`
Stream bool `json:"stream,optional"`
Tools []Tool `json:"tools,optional"`
Functions []Function `json:"functions,optional"`
MaxTokens int `json:"max_tokens,optional"`
Temperature float64 `json:"temperature,optional"`
}
Message {
Role string `json:"role"`
Content interface{} `json:"content"`
}
Tool {
Type string `json:"type"`
Function ToolFunction `json:"function"`
}
ToolFunction {
Name string `json:"name"`
Description string `json:"description"`
Parameters interface{} `json:"parameters"`
}
Function {
Name string `json:"name"`
Description string `json:"description,optional"`
Parameters interface{} `json:"parameters,optional"`
}
ChatCompletionResponse {
Id string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []Choice `json:"choices"`
Usage Usage `json:"usage"`
}
Choice {
Index int `json:"index"`
Message *RespMessage `json:"message,optional"`
Delta *Delta `json:"delta,optional"`
FinishReason string `json:"finish_reason"`
}
RespMessage {
Role string `json:"role"`
Content string `json:"content,optional"`
ToolCalls []ToolCall `json:"tool_calls,optional"`
}
Delta struct {
Role string `json:"role,optional"`
Content string `json:"content,optional"`
ReasoningContent string `json:"reasoning_content,optional"`
ToolCalls []ToolCall `json:"tool_calls,optional"`
}
ToolCall struct {
Index int `json:"index"`
Id string `json:"id"`
Type string `json:"type"`
Function FunctionCall `json:"function"`
}
FunctionCall struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
}
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
// Anthropic Messages
AnthropicRequest {
Model string `json:"model"`
Messages []Message `json:"messages"`
MaxTokens int `json:"max_tokens"`
Stream bool `json:"stream,optional"`
System string `json:"system,optional"`
}
AnthropicResponse {
Id string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Content []ContentBlock `json:"content"`
Model string `json:"model"`
Usage AnthropicUsage `json:"usage"`
}
ContentBlock {
Type string `json:"type"`
Text string `json:"text,optional"`
}
AnthropicUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
}
)
// ============ Routes ============
@server(
prefix: /v1
group: chat
)
service chat-api {
@doc("Health check")
@handler Health
get /health returns (HealthResponse)
@doc("List available models")
@handler Models
get /v1/models returns (ModelsResponse)
@doc("Chat completions (OpenAI compatible)")
@handler ChatCompletions
post /v1/chat/completions (ChatCompletionRequest)
@doc("Anthropic messages")
@handler AnthropicMessages
post /v1/messages (AnthropicRequest)
}
```
#### Step 1.3: 生成代碼骨架
```bash
goctl api go -api api/chat.api -dir . --style go_zero
```
#### Step 1.4: 建立 `etc/chat.yaml`
```yaml
Name: chat-api
Host: ${CURSOR_BRIDGE_HOST:0.0.0.0}
Port: ${CURSOR_BRIDGE_PORT:8080}
# API Key 驗證(可選)
Auth:
AccessSecret: ${CURSOR_API_KEY:}
AccessExpire: 86400
# Cursor 配置
AgentBin: ${CURSOR_AGENT_BIN:cursor-agent}
DefaultModel: ${CURSOR_DEFAULT_MODEL:claude-3.5-sonnet}
Provider: ${CURSOR_PROVIDER:cursor}
# 超時設定
TimeoutMs: ${CURSOR_TIMEOUT_MS:300000}
# 多帳號池
ConfigDirs:
- ${HOME}/.cursor-api-proxy/accounts/default
MultiPort: false
# TLS
TLSCertPath: ${CURSOR_TLS_CERT_PATH:}
TLSKeyPath: ${CURSOR_TLS_KEY_PATH:}
# 日誌
SessionsLogPath: ${CURSOR_SESSIONS_LOG_PATH:}
Verbose: ${CURSOR_VERBOSE:false}
# Gemini 設定
GeminiAccountDir: ${GEMINI_ACCOUNT_DIR:}
GeminiBrowserVisible: ${GEMINI_BROWSER_VISIBLE:false}
GeminiMaxSessions: ${GEMINI_MAX_SESSIONS:10}
# 工作區設定
Workspace: ${CURSOR_WORKSPACE:}
ChatOnlyWorkspace: ${CURSOR_CHAT_ONLY_WORKSPACE:true}
WinCmdlineMax: ${CURSOR_WIN_CMDLINE_MAX:32768}
# Agent 設定
Force: ${CURSOR_FORCE:false}
ApproveMcps: ${CURSOR_APPROVE_MCPS:false}
MaxMode: ${CURSOR_MAX_MODE:false}
StrictModel: ${CURSOR_STRICT_MODEL:true}
```
#### Step 1.5: 移動生成的檔案
```bash
# goctl 生成 chat.go 到根目錄,需移動到 cmd/
mkdir -p cmd/chat
mv chat.go cmd/chat/chat.go
```
---
### Phase 2: Domain 層建立
**目標**:建立 `pkg/domain/` 目錄結構
#### Step 2.1: 建立實體
```bash
mkdir -p pkg/domain/entity
```
**檔案對照表:**
| 新檔案 | 來源 |
|--------|------|
| `pkg/domain/entity/message.go` | `internal/apitypes/types.go` |
| `pkg/domain/entity/chunk.go` | `internal/apitypes/types.go` (StreamChunk) |
| `pkg/domain/entity/account.go` | `internal/pool/pool.go` (AccountStat) |
| `pkg/domain/entity/config.go` | `internal/config/config.go` (部分) |
#### Step 2.2: 建立倉儲介面
```bash
mkdir -p pkg/domain/repository
```
**檔案:**
| 新檔案 | 說明 |
|--------|------|
| `pkg/domain/repository/account.go` | AccountPool interface (從 pool.go 抽取) |
| `pkg/domain/repository/provider.go` | Provider interface (從 providers/factory.go 抽取) |
#### Step 2.3: 建立用例介面
```bash
mkdir -p pkg/domain/usecase
```
**檔案:**
| 新檔案 | 說明 |
|--------|------|
| `pkg/domain/usecase/chat.go` | ChatUsecase interface |
| `pkg/domain/usecase/agent.go` | AgentRunner interface |
#### Step 2.4: 建立常數
```bash
mkdir -p pkg/domain/const
```
**檔案:**
| 新檔案 | 來源 |
|--------|------|
| `pkg/domain/const/models.go` | `internal/models/cursormap.go` |
| `pkg/domain/const/errors.go` | 新增錯誤定義 |
---
### Phase 3: Infrastructure 層建立
**目標**:建立 `pkg/infrastructure/` 基礎設施層
#### Step 3.1: 建立目錄結構
```bash
mkdir -p pkg/infrastructure/{process,parser,httputil,logger,env,workspace,winlimit}
```
#### Step 3.2: 遷移基礎設施模組
| 原始 | 目標 |
|------|------|
| `internal/process/process.go` | `pkg/infrastructure/process/runner.go` |
| `internal/process/kill_unix.go` | `pkg/infrastructure/process/kill_unix.go` |
| `internal/process/kill_windows.go` | `pkg/infrastructure/process/kill_windows.go` |
| `internal/parser/stream.go` | `pkg/infrastructure/parser/stream.go` |
| `internal/httputil/httputil.go` | `pkg/infrastructure/httputil/httputil.go` |
| `internal/logger/logger.go` | `pkg/infrastructure/logger/logger.go` |
| `internal/env/env.go` | `pkg/infrastructure/env/env.go` |
| `internal/workspace/workspace.go` | `pkg/infrastructure/workspace/workspace.go` |
| `internal/winlimit/winlimit.go` | `pkg/infrastructure/winlimit/winlimit.go` |
---
### Phase 4: Repository 層實作
**目標**:實作 `pkg/repository/`
#### Step 4.1: 建立目錄
```bash
mkdir -p pkg/repository
```
#### Step 4.2: 遷移檔案
| 原始 | 目標 |
|------|------|
| `internal/pool/pool.go` | `pkg/repository/account.go` (AccountPool 實作) |
| `internal/providers/factory.go` | `pkg/repository/provider.go` (Provider 工廠) |
---
### Phase 5: Provider 層建立
**目標**:遷移 Provider 實作
#### Step 5.1: 建立目錄
```bash
mkdir -p pkg/provider/cursor pkg/provider/geminiweb
```
#### Step 5.2: 遷移檔案
| 原始 | 目標 |
|------|------|
| `internal/providers/cursor/provider.go` | `pkg/provider/cursor/provider.go` |
| `internal/providers/geminiweb/provider.go` | `pkg/provider/geminiweb/provider.go` |
| `internal/providers/geminiweb/browser.go` | `pkg/provider/geminiweb/browser.go` |
| `internal/providers/geminiweb/pool.go` | `pkg/provider/geminiweb/pool.go` |
| `internal/providers/geminiweb/browser_manager.go` | `pkg/provider/geminiweb/browser_manager.go` |
| `internal/providers/geminiweb/page.go` | `pkg/provider/geminiweb/page.go` |
| `internal/providers/geminiweb/playwright_provider.go` | `pkg/provider/geminiweb/playwright_provider.go` |
---
### Phase 6: Usecase 層建立
**目標**:實作業務邏輯層
#### Step 6.1: 建立目錄
```bash
mkdir -p pkg/usecase
```
#### Step 6.2: 建立檔案
| 新檔案 | 說明 | 來源 |
|--------|------|------|
| `pkg/usecase/chat.go` | 核心聊天邏輯 | 從 handlers/chat.go 抽取 |
| `pkg/usecase/agent.go` | Agent 執行邏輯 | `internal/agent/runner.go` |
| `pkg/usecase/sanitizer.go` | 消息清理 | `internal/sanitize/sanitize.go` |
| `pkg/usecase/toolcall.go` | Tool call 處理 | `internal/toolcall/toolcall.go` |
#### Step 6.3: Agent 相關檔案遷移
| 原始 | 目標 |
|------|------|
| `internal/agent/runner.go` | `pkg/usecase/runner.go` |
| `internal/agent/token.go` | `pkg/usecase/token.go` |
| `internal/agent/cmdargs.go` | `pkg/usecase/cmdargs.go` |
| `internal/agent/maxmode.go` | `pkg/usecase/maxmode.go` |
---
### Phase 7: Adapter 層建立
**目標**API 格式轉換器
#### Step 7.1: 建立目錄
```bash
mkdir -p pkg/adapter/openai pkg/adapter/anthropic
```
#### Step 7.2: 遷移檔案
| 原始 | 目標 |
|------|------|
| `internal/openai/*.go` | `pkg/adapter/openai/*.go` |
| `internal/anthropic/*.go` | `pkg/adapter/anthropic/*.go` |
---
### Phase 8: Internal 層重組
**目標**:建立 go-zero 框架層
#### Step 8.1: 更新 Config
修改 `internal/config/config.go` 使用 go-zero 的 `rest.RestConf`
```go
package config
import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
// Cursor 配置
AgentBin string
DefaultModel string
Provider string
TimeoutMs int
// 多帳號池
ConfigDirs []string
MultiPort bool
// TLS
TLSCertPath string
TLSKeyPath string
// 日誌
SessionsLogPath string
Verbose bool
// Gemini
GeminiAccountDir string
GeminiBrowserVisible bool
GeminiMaxSessions int
// 工作區
Workspace string
ChatOnlyWorkspace bool
WinCmdlineMax int
// Agent
Force bool
ApproveMcps bool
MaxMode bool
StrictModel bool
}
```
#### Step 8.2: 建立 ServiceContext
```bash
mkdir -p internal/svc
```
建立 `internal/svc/servicecontext.go`
```go
package svc
import (
"cursor-api-proxy/internal/config"
"cursor-api-proxy/pkg/domain/repository"
"cursor-api-proxy/pkg/provider"
"cursor-api-proxy/pkg/repository"
"cursor-api-proxy/pkg/usecase"
)
type ServiceContext struct {
Config config.Config
// Domain
AccountPool repository.AccountPool
ChatUsecase usecase.ChatUsecase
// Provider
Provider repository.Provider
}
func NewServiceContext(c config.Config) *ServiceContext {
accountPool := repository.NewAccountPool(c.ConfigDirs)
prov := provider.NewProvider(c)
chatUsecase := usecase.NewChatUsecase(accountPool, prov, c)
return &ServiceContext{
Config: c,
AccountPool: accountPool,
ChatUsecase: chatUsecase,
Provider: prov,
}
}
```
#### Step 8.3: 建立 Logic 層
```bash
mkdir -p internal/logic
```
**SSE 自訂 Handler 說明:**
由於 go-zero 標準 handler 回傳 JSON但專案需要 SSE streaming所以 Logic 層需要自訂處理:
```go
// internal/logic/chatcompletionlogic.go
func (l *ChatCompletionLogic) ChatCompletion(req *types.ChatCompletionRequest) error {
// 設定 SSE headers
l.w.Header().Set("Content-Type", "text/event-stream")
l.w.Header().Set("Cache-Control", "no-cache")
l.w.Header().Set("Connection", "keep-alive")
flusher, ok := l.w.(http.Flusher)
if !ok {
return errors.New("streaming not supported")
}
// 委託給 usecase 處理串流
err := l.svcCtx.ChatUsecase.Stream(l.ctx, usecase.ChatInput{
Model: req.Model,
Messages: l.convertMessages(req.Messages),
Tools: l.convertTools(req.Tools),
Stream: req.Stream,
}, func(chunk entity.StreamChunk) {
l.writeChunk(chunk)
flusher.Flush()
})
return err
}
```
#### Step 8.4: 建立 Handler 層
```bash
mkdir -p internal/handler
```
**自訂 Handler 處理 SSE**
由於需要 SSE streamingHandler 需要:
1. 不使用 goctl 的標準模板
2. 自訂 `http.HandlerFunc` 處理 SSE
3.`routes.go` 中註冊自訂 handler
#### Step 8.5: 建立 Middleware
```bash
mkdir -p internal/middleware
```
| 新檔案 | 說明 |
|--------|------|
| `internal/middleware/auth.go` | API Key 驗證 |
| `internal/middleware/recovery.go` | Panic 恢復(從 router.go 抽取) |
---
### Phase 9: CLI 工具遷移
**目標**:保留 CLI 工具在 `cmd/cli/`
#### Step 9.1: 建立目錄
```bash
mkdir -p cmd/cli
```
#### Step 9.2: 遷移檔案
| 原始 | 目標 |
|------|------|
| `cmd/args.go` | `cmd/cli/args.go` |
| `cmd/login.go` | `cmd/cli/login.go` |
| `cmd/accounts.go` | `cmd/cli/accounts.go` |
| `cmd/resethwid.go` | `cmd/cli/resethwid.go` |
| `cmd/usage.go` | `cmd/cli/usage.go` |
| `cmd/sqlite.go` | `cmd/cli/sqlite.go` |
| `cmd/gemini-login/main.go` | `cmd/cli/gemini_login.go` |
---
### Phase 10: 清理與測試
#### Step 10.1: 移除舊目錄
確認所有遷移完成後:
```bash
# 移除舊的 internal 目錄(保留已遷移的)
rm -rf internal/router
rm -rf internal/handlers
rm -rf internal/providers
rm -rf internal/apitypes
rm -rf internal/pool
rm -rf internal/agent
rm -rf internal/parser
rm -rf internal/toolcall
rm -rf internal/sanitize
rm -rf internal/openai
rm -rf internal/anthropic
rm -rf internal/process
rm -rf internal/models
rm -rf internal/workspace
rm -rf internal/winlimit
rm -rf internal/httputil
rm -rf internal/logger
rm -rf internal/env
rm -rf internal/server
# 移除舊的 main.go
rm main.go
# 移除舊的 cmd 目錄(已遷移到 cmd/cli
rm -rf cmd/gemini-login
```
#### Step 10.2: 更新 import 路徑
```bash
# 批量更新 import 路徑
# internal/ → pkg/ (domain 相關)
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/apitypes|cursor-api-proxy/pkg/domain/entity|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/pool|cursor-api-proxy/pkg/repository|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/agent|cursor-api-proxy/pkg/usecase|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/providers|cursor-api-proxy/pkg/provider|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/parser|cursor-api-proxy/pkg/infrastructure/parser|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/toolcall|cursor-api-proxy/pkg/usecase|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/sanitize|cursor-api-proxy/pkg/usecase|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/openai|cursor-api-proxy/pkg/adapter/openai|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/anthropic|cursor-api-proxy/pkg/adapter/anthropic|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/process|cursor-api-proxy/pkg/infrastructure/process|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/models|cursor-api-proxy/pkg/domain/const|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/workspace|cursor-api-proxy/pkg/infrastructure/workspace|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/winlimit|cursor-api-proxy/pkg/infrastructure/winlimit|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/httputil|cursor-api-proxy/pkg/infrastructure/httputil|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/logger|cursor-api-proxy/pkg/infrastructure/logger|g' {} \;
find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/env|cursor-api-proxy/pkg/infrastructure/env|g' {} \;
```
#### Step 10.3: 測試
```bash
# 單元測試
go test ./pkg/domain/... -v
go test ./pkg/usecase/... -v
go test ./pkg/repository/... -v
# 整合測試
go test ./internal/... -v
# 建置測試
go build ./cmd/chat
go build ./cmd/cli
# 功能測試
make env
./cursor-api-proxy -f etc/chat.yaml &
curl http://localhost:8080/health
curl http://localhost:8080/v1/models
```
---
## 四、漸進式遷移順序
### 每日執行計劃
| 階段 | 任務 | 預估時間 | 依賴 |
|------|------|---------|------|
| Day 1 | Phase 1: API 定義 | 2-3 小時 | 無 |
| Day 2 | Phase 2: Domain 層 | 2-3 小時 | Phase 1 |
| Day 3 | Phase 3: Infrastructure 層 | 2-3 小時 | Phase 2 |
| Day 4 | Phase 4: Repository 層 | 2-3 小時 | Phase 3 |
| Day 5 | Phase 5: Provider 層 | 2-3 小時 | Phase 4 |
| Day 6 | Phase 6: Usecase 層 | 3-4 小時 | Phase 5 |
| Day 7 | Phase 7: Adapter 層 | 2 小時 | Phase 6 |
| Day 8 | Phase 8: Internal 層 | 4-5 小時 | Phase 7 |
| Day 9 | Phase 9: CLI 工具 | 1-2 小時 | Phase 8 |
| Day 10 | Phase 10: 清理與測試 | 3-4 小時 | Phase 9 |
### 漸進式測試策略
每個 Phase 完成後執行:
1. **單元測試**:確保新模組測試通過
2. **編譯測試**:確保代碼可編譯
3. **功能測試**:確保核心功能正常
```bash
# 每日測試腳本
#!/bin/bash
set -e
echo "=== Running unit tests ==="
go test ./pkg/... -v
echo "=== Building ==="
go build ./cmd/chat
go build ./cmd/cli
echo "=== Starting server ==="
./cursor-api-proxy -f etc/chat.yaml &
PID=$!
sleep 2
echo "=== Running functional tests ==="
curl -s http://localhost:8080/health | jq .
curl -s http://localhost:8080/v1/models | jq .
echo "=== Cleaning up ==="
kill $PID
echo "=== All tests passed ==="
```
---
## 五、回歸測試清單
### 功能測試
- [ ] Health check: `GET /health`
- [ ] Models list: `GET /v1/models`
- [ ] Chat completions (非串流): `POST /v1/chat/completions` with `stream: false`
- [ ] Chat completions (串流): `POST /v1/chat/completions` with `stream: true`
- [ ] Anthropic messages: `POST /v1/messages`
- [ ] Gemini chat completions: `POST /v1/chat/completions` (Provider=gemini-web)
- [ ] Tool calls 處理
- [ ] Rate limit 處理
- [ ] Multi-account 輪替
- [ ] API Key 驗證
- [ ] TLS/HTTPS 支援
### CLI 工具測試
- [ ] `login` 命令
- [ ] `accounts` 命令
- [ ] `resethwid` 命令
- [ ] `usage` 命令
- [ ] `gemini-login` 命令
### 效能測試
- [ ] SSE streaming latency
- [ ] Concurrent requests (10, 50, 100)
- [ ] Memory usage baseline
- [ ] Long-running stability
---
## 六、風險與注意事項
### 高風險項目
| 風險 | 描述 | 緩解措施 |
|------|------|---------|
| SSE Streaming | go-zero 標準 handler 不支援 SSE | 自訂 handler在 Logic 層處理 SSE |
| Context 傳遞 | 需確保 context 正確傳遞到 usecase 層 | 使用 context.Context 作為參數傳遞 |
| 全局變數 | pool.go 的 global pool 需改為 DI | 使用 ServiceContext 注入 |
| 環境變數 | 需從 `CURSOR_*` 遷移到 go-zero 配置 | 保持環境變數支援,使用 `${VAR:default}` 語法 |
### 注意事項
1. **保持向後相容**API 端點和行為必須與現有版本一致
2. **環境變數相容**:保持現有環境變數名稱
3. **Makefile 更新**:更新建置命令
4. **日誌格式**:保持現有日誌格式以便分析工具相容
---
## 七、Makefile 更新
需要新增 go-zero 相關命令:
```makefile
# go-zero 代碼生成
.PHONY: api
api:
goctl api go -api api/chat.api -dir . --style go_zero
.PHONY: api-doc
api-doc:
goctl api doc -api api/chat.api -dir docs/
# 格式化
.PHONY: fmt
fmt:
go fmt ./...
# 測試
.PHONY: test
test:
go test ./... -v -race
# 建置
.PHONY: build
build:
go build -o bin/chat-api cmd/chat/chat.go
# 運行
.PHONY: run
run:
./bin/chat-api -f etc/chat.yaml
```
---
## 八、參考資源
- [go-zero 官方文檔](https://go-zero.dev/)
- [go-zero API 定義](https://go-zero.dev/docs/tutorial/api/)
- [go-zero 最佳實踐](https://go-zero.dev/docs/tutorials/)
- [zero-skills](https://github.com/zeromicro/zero-skills)
- [ai-context](https://github.com/zeromicro/ai-context)
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/33/the-clean-architecture.html)
---
## 九、附錄:檔案遷移對照表
### 完整檔案對照表
| 原始 | 目標 | 說明 |
|------|------|------|
| `main.go` | `cmd/chat/chat.go` | HTTP 服務入口 |
| `cmd/args.go` | `cmd/cli/args.go` | CLI 參數 |
| `cmd/login.go` | `cmd/cli/login.go` | CLI login |
| `cmd/accounts.go` | `cmd/cli/accounts.go` | CLI accounts |
| `cmd/resethwid.go` | `cmd/cli/resethwid.go` | CLI resethwid |
| `cmd/usage.go` | `cmd/cli/usage.go` | CLI usage |
| `cmd/sqlite.go` | `cmd/cli/sqlite.go` | SQLite 操作 |
| `cmd/gemini-login/main.go` | `cmd/cli/gemini_login.go` | Gemini CLI |
| `internal/apitypes/types.go` | `pkg/domain/entity/message.go` | Message 實體 |
| `internal/pool/pool.go` | `pkg/repository/account.go` | AccountPool 實作 |
| `internal/agent/runner.go` | `pkg/usecase/runner.go` | Agent 執行 |
| `internal/agent/token.go` | `pkg/usecase/token.go` | Token 處理 |
| `internal/agent/cmdargs.go` | `pkg/usecase/cmdargs.go` | 命令參數 |
| `internal/agent/maxmode.go` | `pkg/usecase/maxmode.go` | Max mode |
| `internal/providers/factory.go` | `pkg/repository/provider.go` | Provider 工廠 |
| `internal/providers/cursor/provider.go` | `pkg/provider/cursor/provider.go` | Cursor provider |
| `internal/providers/geminiweb/*.go` | `pkg/provider/geminiweb/*.go` | Gemini provider |
| `internal/parser/stream.go` | `pkg/infrastructure/parser/stream.go` | SSE parsing |
| `internal/toolcall/toolcall.go` | `pkg/usecase/toolcall.go` | Tool call |
| `internal/sanitize/sanitize.go` | `pkg/usecase/sanitize.go` | 消息清理 |
| `internal/openai/*.go` | `pkg/adapter/openai/*.go` | OpenAI 格式 |
| `internal/anthropic/*.go` | `pkg/adapter/anthropic/*.go` | Anthropic 格式 |
| `internal/process/process.go` | `pkg/infrastructure/process/runner.go` | 進程管理 |
| `internal/process/kill_*.go` | `pkg/infrastructure/process/kill_*.go` | Kill 實作 |
| `internal/models/cursormap.go` | `pkg/domain/const/models.go` | Model 映射 |
| `internal/workspace/workspace.go` | `pkg/infrastructure/workspace/workspace.go` | 工作區 |
| `internal/winlimit/winlimit.go` | `pkg/infrastructure/winlimit/winlimit.go` | Win limit |
| `internal/httputil/httputil.go` | `pkg/infrastructure/httputil/httputil.go` | HTTP 工具 |
| `internal/logger/logger.go` | `pkg/infrastructure/logger/logger.go` | 日誌 |
| `internal/env/env.go` | `pkg/infrastructure/env/env.go` | 環境變數 |
| `internal/config/config.go` | `internal/config/config.go` | 配置(使用 go-zero Config |
| `internal/router/router.go` | `internal/handler/routes.go` | 路由(重寫) |
| `internal/handlers/*.go` | `internal/logic/*.go` | 業務邏輯 |
---
**文件版本**v1.0
**建立日期**2026-04-03
**最後更新**2026-04-03