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,6 +3,8 @@ package repository
import ( import (
"sync" "sync"
"time" "time"
"cursor-api-proxy/pkg/domain/entity"
) )
type accountStatus struct { type accountStatus struct {
@ -17,18 +19,6 @@ type accountStatus struct {
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 {
mu sync.Mutex mu sync.Mutex
accounts []*accountStatus accounts []*accountStatus
@ -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,7 +180,7 @@ 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 模式使用)
@ -203,7 +192,7 @@ func (GlobalPoolHandle) ReportRequestEnd(d string) { ReportRequestEn
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()