diff --git a/.env.example b/.env.example deleted file mode 100644 index 84e251a..0000000 --- a/.env.example +++ /dev/null @@ -1,40 +0,0 @@ -# ────────────────────────────────────────────────────────────── -# cursor-api-proxy 設定範例 -# 複製為 .env 後填入你的設定:cp .env.example .env -# ────────────────────────────────────────────────────────────── - -# ── 伺服器設定 ──────────────────────────────────────────────── -# Docker 模式固定使用 0.0.0.0;本機直接執行時可改 127.0.0.1 -CURSOR_BRIDGE_HOST=0.0.0.0 -CURSOR_BRIDGE_PORT=8766 -CURSOR_BRIDGE_API_KEY= -CURSOR_BRIDGE_TIMEOUT_MS=3600000 -CURSOR_BRIDGE_MULTI_PORT=false -CURSOR_BRIDGE_VERBOSE=false - -# ── Agent / 模型設定 ────────────────────────────────────────── -# Docker 模式:容器內 agent 路徑(預設掛載至 /usr/local/bin/agent) -CURSOR_AGENT_BIN=/usr/local/bin/agent -CURSOR_BRIDGE_DEFAULT_MODEL=auto -CURSOR_BRIDGE_STRICT_MODEL=true -CURSOR_BRIDGE_MAX_MODE=false -CURSOR_BRIDGE_FORCE=false -CURSOR_BRIDGE_APPROVE_MCPS=false - -# ── 工作區與帳號 ────────────────────────────────────────────── -CURSOR_BRIDGE_WORKSPACE=/workspace -CURSOR_BRIDGE_CHAT_ONLY_WORKSPACE=true -# 多帳號設定目錄(逗號分隔),留空則自動探索 ~/.cursor-api-proxy/accounts/ -CURSOR_CONFIG_DIRS= - -# ── TLS / HTTPS(選用)──────────────────────────────────────── -CURSOR_BRIDGE_TLS_CERT= -CURSOR_BRIDGE_TLS_KEY= - -# ── Docker 專用:宿主機路徑對映 ────────────────────────────── -# 宿主機上 Cursor agent 二進位檔的實際路徑 -CURSOR_AGENT_HOST_BIN=/usr/local/bin/agent -# 宿主機上的帳號資料目錄(會掛載至容器的 /root/.cursor-api-proxy) -CURSOR_ACCOUNTS_DIR=~/.cursor-api-proxy -# 宿主機上要掛載進容器的工作區目錄 -WORKSPACE_DIR=/tmp/workspace diff --git a/.gitignore b/.gitignore index 4f6a5c7..3b5a836 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea/ cursor-api-proxy* -.env \ No newline at end of file +.env +.opencode diff --git a/etc/chat-api.yaml b/etc/chat-api.yaml index ec5d26e..f228bc9 100644 --- a/etc/chat-api.yaml +++ b/etc/chat-api.yaml @@ -1,12 +1,12 @@ -Name: cursor-api-proxy +Name: api-proxy Host: 0.0.0.0 -Port: 8080 +Port: 8766 # Cursor Agent 配置 AgentBin: cursor -DefaultModel: claude-3.5-sonnet +DefaultModel: claude-4.5-sonnet Provider: cursor -TimeoutMs: 300000 +TimeoutMs: 36000000 # 多帳號池配置 ConfigDirs: @@ -19,7 +19,7 @@ TLSKeyPath: "" # 日誌 SessionsLogPath: "" -Verbose: false +# Verbose 使用 RestConf 預設值 # Gemini Web Provider 配置 GeminiAccountDir: ~/.cursor-api-proxy/gemini-accounts diff --git a/internal/config/config.go b/internal/config/config.go index 82c1459..68cc633 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -29,7 +29,7 @@ type Config struct { // 日誌 SessionsLogPath string - Verbose bool + // Verbose is inherited from rest.RestConf // Gemini GeminiAccountDir string diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 5b60227..ed2df48 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -21,19 +21,17 @@ func TestConfigToBridgeConfig(t *testing.T) { func TestConfigToBridgeConfigWithValues(t *testing.T) { cfg := config.Config{ - AgentBin: "cursor", - DefaultModel: "claude-3.5-sonnet", - Provider: "cursor", - TimeoutMs: 300000, - Force: true, - ApproveMcps: true, - StrictModel: true, - Workspace: "/tmp/test", - ChatOnlyWorkspace: true, - Verbose: true, - GeminiAccountDir: "/tmp/gemini", - GeminiBrowserVisible: true, - GeminiMaxSessions: 5, + AgentBin: "cursor", + DefaultModel: "claude-3.5-sonnet", + Provider: "cursor", + TimeoutMs: 300000, + Force: true, + ApproveMcps: true, + StrictModel: true, + Workspace: "/tmp/test", + ChatOnlyWorkspace: true, + GeminiAccountDir: "/tmp/gemini", + GeminiMaxSessions: 5, } bc := cfg.ToBridgeConfig() diff --git a/internal/handler/chat/anthropic_messages_handler.go b/internal/handler/chat/anthropic_messages_handler.go index be993d3..1d83c45 100644 --- a/internal/handler/chat/anthropic_messages_handler.go +++ b/internal/handler/chat/anthropic_messages_handler.go @@ -4,20 +4,33 @@ package chat import ( + "encoding/json" + "io" "net/http" "cursor-api-proxy/internal/logic/chat" "cursor-api-proxy/internal/svc" "cursor-api-proxy/internal/types" - "github.com/zeromicro/go-zero/rest/httpx" + "cursor-api-proxy/pkg/infrastructure/httputil" ) func AnthropicMessagesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + // Read raw body first + rawBody, err := io.ReadAll(r.Body) + if err != nil { + httputil.WriteJSON(w, 400, map[string]interface{}{ + "error": map[string]string{"type": "invalid_request_error", "message": "failed to read body"}, + }, nil) + return + } + var req types.AnthropicRequest - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) + if err := json.Unmarshal(rawBody, &req); err != nil { + httputil.WriteJSON(w, 400, map[string]interface{}{ + "error": map[string]string{"type": "invalid_request_error", "message": "invalid JSON body"}, + }, nil) return } @@ -31,7 +44,9 @@ func AnthropicMessagesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } else { err := l.AnthropicMessages(&req, w, r.Method, r.URL.Path) if err != nil { - httpx.ErrorCtx(r.Context(), w, err) + httputil.WriteJSON(w, 500, map[string]interface{}{ + "error": map[string]string{"type": "api_error", "message": err.Error()}, + }, nil) } } } diff --git a/internal/handler/chat/chat_completions_handler.go b/internal/handler/chat/chat_completions_handler.go index 7f0f083..988bdc9 100644 --- a/internal/handler/chat/chat_completions_handler.go +++ b/internal/handler/chat/chat_completions_handler.go @@ -4,20 +4,33 @@ package chat import ( + "encoding/json" + "io" "net/http" "cursor-api-proxy/internal/logic/chat" "cursor-api-proxy/internal/svc" "cursor-api-proxy/internal/types" - "github.com/zeromicro/go-zero/rest/httpx" + "cursor-api-proxy/pkg/infrastructure/httputil" ) func ChatCompletionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + // Read raw body first + rawBody, err := io.ReadAll(r.Body) + if err != nil { + httputil.WriteJSON(w, 400, map[string]interface{}{ + "error": map[string]string{"message": "failed to read body", "code": "bad_request"}, + }, nil) + return + } + var req types.ChatCompletionRequest - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) + if err := json.Unmarshal(rawBody, &req); err != nil { + httputil.WriteJSON(w, 400, map[string]interface{}{ + "error": map[string]string{"message": "invalid JSON body", "code": "bad_request"}, + }, nil) return } @@ -31,9 +44,11 @@ func ChatCompletionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } else { resp, err := l.ChatCompletions(&req) if err != nil { - httpx.ErrorCtx(r.Context(), w, err) + httputil.WriteJSON(w, 500, map[string]interface{}{ + "error": map[string]string{"message": err.Error(), "code": "internal_error"}, + }, nil) } else { - httpx.OkJsonCtx(r.Context(), w, resp) + httputil.WriteJSON(w, 200, resp, nil) } } }