opencode-cursor-agent/docs/MIGRATION_PLAN.md

34 KiB
Raw Permalink Blame History

重構計劃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 專案

# 安裝 goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest

# 建立 API 定義目錄
mkdir -p api etc

Step 1.2: 建立 api/chat.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: 生成代碼骨架

goctl api go -api api/chat.api -dir . --style go_zero

Step 1.4: 建立 etc/chat.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: 移動生成的檔案

# goctl 生成 chat.go 到根目錄,需移動到 cmd/
mkdir -p cmd/chat
mv chat.go cmd/chat/chat.go

Phase 2: Domain 層建立

目標:建立 pkg/domain/ 目錄結構

Step 2.1: 建立實體

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: 建立倉儲介面

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: 建立用例介面

mkdir -p pkg/domain/usecase

檔案:

新檔案 說明
pkg/domain/usecase/chat.go ChatUsecase interface
pkg/domain/usecase/agent.go AgentRunner interface

Step 2.4: 建立常數

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: 建立目錄結構

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: 建立目錄

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: 建立目錄

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: 建立目錄

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: 建立目錄

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

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

mkdir -p internal/svc

建立 internal/svc/servicecontext.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 層

mkdir -p internal/logic

SSE 自訂 Handler 說明:

由於 go-zero 標準 handler 回傳 JSON但專案需要 SSE streaming所以 Logic 層需要自訂處理:

// 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 層

mkdir -p internal/handler

自訂 Handler 處理 SSE

由於需要 SSE streamingHandler 需要:

  1. 不使用 goctl 的標準模板
  2. 自訂 http.HandlerFunc 處理 SSE
  3. routes.go 中註冊自訂 handler

Step 8.5: 建立 Middleware

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: 建立目錄

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: 移除舊目錄

確認所有遷移完成後:

# 移除舊的 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 路徑

# 批量更新 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: 測試

# 單元測試
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. 功能測試:確保核心功能正常
# 每日測試腳本
#!/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 相關命令:

# 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

八、參考資源


九、附錄:檔案遷移對照表

完整檔案對照表

原始 目標 說明
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