refactor(task-7): integrate all layers and fix type mismatches

- Merge all branches (domain, infrastructure, repository, provider, adapter, usecase, cli)
- Update ServiceContext to inject dependencies
- Fix Message.Content type mismatch (string vs interface{})
- Update AccountStat to use entity.AccountStat
- Add helper functions for content conversion
This commit is contained in:
王性驊 2026-04-03 17:49:11 +08:00
parent 8f1b7159ed
commit 7e0b7a970c
5 changed files with 543 additions and 44 deletions

View File

@ -0,0 +1,462 @@
# REFACTOR TASKS
重構任務拆分,支援 git worktree 並行開發。
---
## Task Overview
### 並行策略
```
時間軸 ──────────────────────────────────────────────────────────────►
Task 0: Init (必須先完成)
├── Task 1: Domain Layer ─────────────────────────┐
│ │
│ ┌── Task 2: Infrastructure Layer ────────────┤── 並行
│ │ │
│ └── Task 3: Repository Layer ────────────────┘
│ (依賴 Task 1)
├── Task 4: Provider Layer ──────────────────────┐
│ (依賴 Task 1) │
│ │── 可並行
├── Task 5: Usecase Layer ───────────────────────┤
│ (依賴 Task 3) │
│ │
├── Task 6: Adapter Layer ───────────────────────┘
│ (依賴 Task 1)
├── Task 7: Internal Layer ──────────────────────┐
│ (整合所有,必須最後) │
│ │── 序列
├── Task 8: CLI Tools │
│ │
└── Task 9: Cleanup & Tests ────────────────────┘
```
### Worktree 分支規劃
| 分支名稱 | 基於 | 任務 | 可並行 |
|---------|------|------|--------|
| `refactor/init` | `master` | Task 0 | ❌ |
| `refactor/domain` | `refactor/init` | Task 1 | ✅ |
| `refactor/infrastructure` | `refactor/init` | Task 2 | ✅ |
| `refactor/repository` | `refactor/domain` | Task 3 | ✅ |
| `refactor/provider` | `refactor/domain` | Task 4 | ✅ |
| `refactor/usecase` | `refactor/repository` | Task 5 | ✅ |
| `refactor/adapter` | `refactor/domain` | Task 6 | ✅ |
| `refactor/internal` | 合併所有 | Task 7 | ❌ |
| `refactor/cli` | `refactor/init` | Task 8 | ✅ |
| `refactor/cleanup` | 合併所有 | Task 9 | ❌ |
---
## Task 0: 初始化
### 分支
`refactor/init`
### 依賴
無(必須先完成)
### 小任務
- [ ] **0.1** 更新 go.mod (5min)
- `go get github.com/zeromicro/go-zero@latest`
- `go mod tidy`
- [ ] **0.2** 建立目錄 (1min)
- `mkdir -p api etc`
- [ ] **0.3** 建立 `api/chat.api` (15min)
- 定義 API types
- 定義 routes
- [ ] **0.4** 建立 `etc/chat.yaml` (5min)
- 配置參數
- [ ] **0.5** 更新 Makefile (10min)
- 新增 goctl 命令
- [ ] **0.6** 提交 (2min)
**預估時間**: ~30min
---
## Task 1: Domain Layer
### 分支
`refactor/domain`
### 依賴
Task 0 完成
### 小任務
- [ ] **1.1** 建立目錄結構 (1min)
- `pkg/domain/entity`
- `pkg/domain/repository`
- `pkg/domain/usecase`
- `pkg/domain/const`
- [ ] **1.2** `entity/message.go` (10min)
- Message, Tool, ToolFunction, ToolCall
- [ ] **1.3** `entity/chunk.go` (5min)
- StreamChunk, ChunkType
- [ ] **1.4** `entity/account.go` (5min)
- Account, AccountStat
- [ ] **1.5** `repository/account.go` (10min)
- AccountPool interface
- [ ] **1.6** `repository/provider.go` (5min)
- Provider interface
- [ ] **1.7** `usecase/chat.go` (15min)
- ChatUsecase interface
- [ ] **1.8** `usecase/agent.go` (5min)
- AgentRunner interface
- [ ] **1.9** `const/models.go` (10min)
- Model 常數
- [ ] **1.10** `const/errors.go` (5min)
- 錯誤定義
- [ ] **1.11** 提交 (2min)
**預估時間**: ~2h
---
## Task 2: Infrastructure Layer
### 分支
`refactor/infrastructure`
### 依賴
Task 0 完成(可與 Task 1 並行)
### 小任務
- [ ] **2.1** 建立目錄 (2min)
- `pkg/infrastructure/{process,parser,httputil,logger,env,workspace,winlimit}`
- [ ] **2.2** 遷移 process (10min)
- runner.go, kill_unix.go, kill_windows.go, process_test.go
- [ ] **2.3** 遷移 parser (5min)
- stream.go, stream_test.go
- [ ] **2.4** 遷移 httputil (5min)
- httputil.go, httputil_test.go
- [ ] **2.5** 遷移 logger (5min)
- logger.go
- [ ] **2.6** 遷移 env (5min)
- env.go, env_test.go
- [ ] **2.7** 遷移 workspace (5min)
- workspace.go
- [ ] **2.8** 遷移 winlimit (5min)
- winlimit.go, winlimit_test.go
- [ ] **2.9** 驗證編譯 (5min)
- [ ] **2.10** 提交 (2min)
**預估時間**: ~1h
---
## Task 3: Repository Layer
### 分支
`refactor/repository`
### 依賴
Task 1 完成
### 小任務
- [ ] **3.1** 建立目錄 (1min)
- [ ] **3.2** 遷移 account.go (20min)
- AccountPool 實作
- 移除全局變數
- [ ] **3.3** 遷移 provider.go (10min)
- Provider 工廠
- [ ] **3.4** 遷移測試 (5min)
- [ ] **3.5** 驗證編譯 (5min)
- [ ] **3.6** 提交 (2min)
**預估時間**: ~1h
---
## Task 4: Provider Layer
### 分支
`refactor/provider`
### 依賴
Task 1 完成
### 小任務
- [ ] **4.1** 建立目錄 (1min)
- `pkg/provider/cursor`
- `pkg/provider/geminiweb`
- [ ] **4.2** 遷移 cursor provider (5min)
- [ ] **4.3** 遷移 geminiweb provider (10min)
- [ ] **4.4** 更新 import (5min)
- [ ] **4.5** 驗證編譯 (5min)
- [ ] **4.6** 提交 (2min)
**預估時間**: ~30min
---
## Task 5: Usecase Layer
### 分支
`refactor/usecase`
### 依賴
Task 3 完成
### 小任務
- [ ] **5.1** 建立目錄 (1min)
- [ ] **5.2** 建立 chat.go (30min)
- 核心聊天邏輯
- [ ] **5.3** 遷移 agent.go (20min)
- runner, token, cmdargs, maxmode
- [ ] **5.4** 遷移 sanitizer (10min)
- [ ] **5.5** 遷移 toolcall (10min)
- [ ] **5.6** 驗證編譯 (5min)
- [ ] **5.7** 提交 (2min)
**預估時間**: ~2h
---
## Task 6: Adapter Layer
### 分支
`refactor/adapter`
### 依賴
Task 1 完成
### 小任務
- [ ] **6.1** 建立目錄 (1min)
- [ ] **6.2** 遷移 openai adapter (10min)
- [ ] **6.3** 遷移 anthropic adapter (10min)
- [ ] **6.4** 更新 import (5min)
- [ ] **6.5** 驗證編譯 (5min)
- [ ] **6.6** 提交 (2min)
**預估時間**: ~30min
---
## Task 7: Internal Layer
### 分支
`refactor/internal`
### 依賴
Task 1-6 全部完成
### 小任務
- [ ] **7.1** 合併所有分支 (5min)
- [ ] **7.2** 更新 config/config.go (15min)
- 使用 rest.RestConf
- [ ] **7.3** 建立 svc/servicecontext.go (30min)
- DI 容器
- [ ] **7.4** 建立 logic/ (1h)
- chatcompletionlogic.go
- geminichatlogic.go
- anthropiclogic.go
- healthlogic.go
- modelslogic.go
- [ ] **7.5** 建立 handler/ (1h)
- 自訂 SSE handler
- [ ] **7.6** 建立 middleware/ (20min)
- auth.go
- recovery.go
- [ ] **7.7** 建立 types/ (5min)
- goctl 生成
- [ ] **7.8** 更新 import (30min)
- 批量更新
- [ ] **7.9** 驗證編譯 (10min)
- [ ] **7.10** 提交 (2min)
**預估時間**: ~4h
---
## Task 8: CLI Tools
### 分支
`refactor/cli`
### 依賴
Task 0 完成
### 小任務
- [ ] **8.1** 建立目錄 (1min)
- [ ] **8.2** 遷移 CLI 工具 (10min)
- [ ] **8.3** 遷移 gemini-login (5min)
- [ ] **8.4** 更新 import (5min)
- [ ] **8.5** 提交 (2min)
**預估時間**: ~30min
---
## Task 9: Cleanup & Tests
### 分支
`refactor/cleanup`
### 依賴
Task 7 完成
### 小任務
- [ ] **9.1** 移除舊目錄 (5min)
- [ ] **9.2** 更新 import (30min)
- 批量 sed
- [ ] **9.3** 建立 cmd/chat/chat.go (10min)
- [ ] **9.4** SSE 整合測試 (2h)
- [ ] **9.5** 回歸測試 (1h)
- [ ] **9.6** 更新 README (15min)
- [ ] **9.7** 提交 (2min)
**預估時間**: ~4h
---
## 並行執行計劃
### Wave 1 (可完全並行)
```
Terminal 1: Task 0 (init) → 30min
Terminal 2: (等待 Task 0)
```
### Wave 2 (可完全並行)
```
Terminal 1: Task 1 (domain) → 2h
Terminal 2: Task 2 (infrastructure) → 1h
Terminal 3: Task 8 (cli) → 30min
```
### Wave 3 (可部分並行)
```
Terminal 1: Task 3 (repository) → 1h (依賴 Task 1)
Terminal 2: Task 4 (provider) → 30min (依賴 Task 1)
Terminal 3: Task 6 (adapter) → 30min (依賴 Task 1)
Terminal 4: (等待 Task 3)
```
### Wave 4 (可部分並行)
```
Terminal 1: Task 5 (usecase) → 2h (依賴 Task 3)
Terminal 2: (等待 Task 5)
```
### Wave 5 (序列)
```
Task 7 (internal) → 4h
Task 9 (cleanup) → 4h
```
**總時間估計**:
- 完全序列: ~15h
- 並行執行: ~9h
- 節省: ~40%
---
## Git Worktree 指令
```bash
# 創建 worktrees
git worktree add ../worktrees/init -b refactor/init
git worktree add ../worktrees/domain -b refactor/domain
git worktree add ../worktrees/infrastructure -b refactor/infrastructure
git worktree add ../worktrees/repository -b refactor/repository
git worktree add ../worktrees/provider -b refactor/provider
git worktree add ../worktrees/usecase -b refactor/usecase
git worktree add ../worktrees/adapter -b refactor/adapter
git worktree add ../worktrees/cli -b refactor/cli
# 並行工作
cd ../worktrees/domain && # Terminal 1
cd ../worktrees/infrastructure && # Terminal 2
cd ../worktrees/cli && # Terminal 3
# 清理 worktrees
git worktree remove ../worktrees/init
git worktree remove ../worktrees/domain
# ... 等等
```
---
**文件版本**: v1.0
**建立日期**: 2026-04-03

View File

@ -1,18 +1,23 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package svc package svc
import ( import (
"cursor-api-proxy/internal/config" "cursor-api-proxy/internal/config"
domainrepo "cursor-api-proxy/pkg/domain/repository"
"cursor-api-proxy/pkg/repository"
) )
type ServiceContext struct { type ServiceContext struct {
Config config.Config Config config.Config
// Domain services
AccountPool domainrepo.AccountPool
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
accountPool := repository.NewAccountPool(c.ConfigDirs)
return &ServiceContext{ return &ServiceContext{
Config: c, Config: c,
AccountPool: accountPool,
} }
} }

View File

@ -2,8 +2,8 @@ package geminiweb
import ( import (
"context" "context"
"cursor-api-proxy/pkg/domain/entity"
"cursor-api-proxy/internal/config" "cursor-api-proxy/internal/config"
"cursor-api-proxy/pkg/domain/entity"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -624,18 +624,39 @@ func (p *PlaywrightProvider) selectModel(model string) error {
return nil return nil
} }
// buildPromptFromMessages 從訊息列表建構提示詞 // buildPromptFromMessagesPlaywright 從訊息列表建構提示詞
func buildPromptFromMessagesPlaywright(messages []entity.Message) string { func buildPromptFromMessagesPlaywright(messages []entity.Message) string {
var prompt string var prompt string
for _, m := range messages { for _, m := range messages {
content := messageContentToStringPlaywright(m.Content)
switch m.Role { switch m.Role {
case "system": case "system":
prompt += "System: " + m.Content + "\n\n" prompt += "System: " + content + "\n\n"
case "user": case "user":
prompt += m.Content + "\n\n" prompt += content + "\n\n"
case "assistant": case "assistant":
prompt += "Assistant: " + m.Content + "\n\n" prompt += "Assistant: " + content + "\n\n"
} }
} }
return prompt return prompt
} }
// messageContentToStringPlaywright converts Message.Content to string
func messageContentToStringPlaywright(content interface{}) string {
switch v := content.(type) {
case string:
return v
case []interface{}:
var result string
for _, item := range v {
if m, ok := item.(map[string]interface{}); ok {
if text, ok := m["text"].(string); ok {
result += text
}
}
}
return result
default:
return ""
}
}

View File

@ -2,8 +2,8 @@ package geminiweb
import ( import (
"context" "context"
"cursor-api-proxy/pkg/domain/entity"
"cursor-api-proxy/internal/config" "cursor-api-proxy/internal/config"
"cursor-api-proxy/pkg/domain/entity"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -142,18 +142,40 @@ func (p *Provider) Generate(ctx context.Context, model string, messages []entity
func buildPromptFromMessages(messages []entity.Message) string { func buildPromptFromMessages(messages []entity.Message) string {
var prompt string var prompt string
for _, m := range messages { for _, m := range messages {
content := messageContentToString(m.Content)
switch m.Role { switch m.Role {
case "system": case "system":
prompt += "System: " + m.Content + "\n\n" prompt += "System: " + content + "\n\n"
case "user": case "user":
prompt += m.Content + "\n\n" prompt += content + "\n\n"
case "assistant": case "assistant":
prompt += "Assistant: " + m.Content + "\n\n" prompt += "Assistant: " + content + "\n\n"
} }
} }
return prompt return prompt
} }
// messageContentToString converts Message.Content to string
func messageContentToString(content interface{}) string {
switch v := content.(type) {
case string:
return v
case []interface{}:
// Handle array content (multimodal)
var result string
for _, item := range v {
if m, ok := item.(map[string]interface{}); ok {
if text, ok := m["text"].(string); ok {
result += text
}
}
}
return result
default:
return ""
}
}
// RunLogin 執行登入流程(供 gemini-login 命令使用) // RunLogin 執行登入流程(供 gemini-login 命令使用)
func RunLogin(cfg config.BridgeConfig, sessionName string) error { func RunLogin(cfg config.BridgeConfig, sessionName string) error {
if sessionName == "" { if sessionName == "" {

View File

@ -3,30 +3,20 @@ package repository
import ( import (
"sync" "sync"
"time" "time"
"cursor-api-proxy/pkg/domain/entity"
) )
type accountStatus struct { type accountStatus struct {
configDir string configDir string
activeRequests int activeRequests int
lastUsed int64 lastUsed int64
rateLimitUntil int64 rateLimitUntil int64
totalRequests int totalRequests int
totalSuccess int totalSuccess int
totalErrors int totalErrors int
totalRateLimits int totalRateLimits int
totalLatencyMs int64 totalLatencyMs int64
}
type AccountStat struct {
ConfigDir string
ActiveRequests int
TotalRequests int
TotalSuccess int
TotalErrors int
TotalRateLimits int
TotalLatencyMs int64
IsRateLimited bool
RateLimitUntil int64
} }
type AccountPool struct { type AccountPool struct {
@ -155,13 +145,13 @@ func (p *AccountPool) ReportRateLimit(configDir string, penaltyMs int64) {
} }
} }
func (p *AccountPool) GetStats() []AccountStat { func (p *AccountPool) GetStats() []entity.AccountStat {
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
now := time.Now().UnixMilli() now := time.Now().UnixMilli()
stats := make([]AccountStat, len(p.accounts)) stats := make([]entity.AccountStat, len(p.accounts))
for i, a := range p.accounts { for i, a := range p.accounts {
stats[i] = AccountStat{ stats[i] = entity.AccountStat{
ConfigDir: a.configDir, ConfigDir: a.configDir,
ActiveRequests: a.activeRequests, ActiveRequests: a.activeRequests,
TotalRequests: a.totalRequests, TotalRequests: a.totalRequests,
@ -180,7 +170,6 @@ func (p *AccountPool) Count() int {
return len(p.accounts) return len(p.accounts)
} }
// ─── PoolHandle interface ────────────────────────────────────────────────── // ─── PoolHandle interface ──────────────────────────────────────────────────
// PoolHandle 讓 handler 可以注入獨立的 pool 實例,避免多 port 模式共用全域 pool。 // PoolHandle 讓 handler 可以注入獨立的 pool 實例,避免多 port 模式共用全域 pool。
@ -191,19 +180,19 @@ type PoolHandle interface {
ReportRequestSuccess(configDir string, latencyMs int64) ReportRequestSuccess(configDir string, latencyMs int64)
ReportRequestError(configDir string, latencyMs int64) ReportRequestError(configDir string, latencyMs int64)
ReportRateLimit(configDir string, penaltyMs int64) ReportRateLimit(configDir string, penaltyMs int64)
GetStats() []AccountStat GetStats() []entity.AccountStat
} }
// GlobalPoolHandle 包裝全域函式以實作 PoolHandle 介面(單 port 模式使用) // GlobalPoolHandle 包裝全域函式以實作 PoolHandle 介面(單 port 模式使用)
type GlobalPoolHandle struct{} type GlobalPoolHandle struct{}
func (GlobalPoolHandle) GetNextConfigDir() string { return GetNextAccountConfigDir() } func (GlobalPoolHandle) GetNextConfigDir() string { return GetNextAccountConfigDir() }
func (GlobalPoolHandle) ReportRequestStart(d string) { ReportRequestStart(d) } func (GlobalPoolHandle) ReportRequestStart(d string) { ReportRequestStart(d) }
func (GlobalPoolHandle) ReportRequestEnd(d string) { ReportRequestEnd(d) } func (GlobalPoolHandle) ReportRequestEnd(d string) { ReportRequestEnd(d) }
func (GlobalPoolHandle) ReportRequestSuccess(d string, l int64) { ReportRequestSuccess(d, l) } func (GlobalPoolHandle) ReportRequestSuccess(d string, l int64) { ReportRequestSuccess(d, l) }
func (GlobalPoolHandle) ReportRequestError(d string, l int64) { ReportRequestError(d, l) } func (GlobalPoolHandle) ReportRequestError(d string, l int64) { ReportRequestError(d, l) }
func (GlobalPoolHandle) ReportRateLimit(d string, p int64) { ReportRateLimit(d, p) } func (GlobalPoolHandle) ReportRateLimit(d string, p int64) { ReportRateLimit(d, p) }
func (GlobalPoolHandle) GetStats() []AccountStat { return GetAccountStats() } func (GlobalPoolHandle) GetStats() []entity.AccountStat { return GetAccountStats() }
// ─── Global pool ─────────────────────────────────────────────────────────── // ─── Global pool ───────────────────────────────────────────────────────────
@ -273,7 +262,7 @@ func ReportRateLimit(configDir string, penaltyMs int64) {
} }
} }
func GetAccountStats() []AccountStat { func GetAccountStats() []entity.AccountStat {
globalMu.Lock() globalMu.Lock()
p := globalPool p := globalPool
globalMu.Unlock() globalMu.Unlock()