mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
Desktop Cursor works through the local proxy because it drives the agent
over BidiAppend/RunSSE plus the already-mocked unary endpoints. The
cursor-agent CLI speaks the same agent protocol but calls additional
unary endpoints that had no local handlers, so every request fell into a
wildcard route and came back as HTTP 404, which the Connect client maps
to '[unimplemented] HTTP 404'.
Three independent breaks, one visible symptom:
1. Startup: ServerConfigService/GetServerConfig (only the AiService
variant was mocked), DashboardService/GetTeamAdminSettingsOrEmptyIfNotInTeam
and DashboardService/ListMarketplaces were missing, so the CLI aborted
during session init.
2. Git workspaces: the CLI resolves the repo path-encryption key from
indexingConfig.default{User,Team}PathEncryptionKey in GetServerConfig
when no IDE-stored repo keys exist. The mock returned no indexingConfig,
so repository identity init failed with 'No encryption key found'.
3. Tool execution: every fs tool executor (Ls/Grep/Glob/Shell) consults
the ignore service, which calls getRepoBlockExcludeGlobs() ->
DashboardService/GetTeamReposOrEmptyIfNotInTeam. The 404 propagated as
the tool result error, so model answers arrived but every tool call
returned '[unimplemented] HTTP 404'. Non-git workspaces skip the
repo-block path, which is why tools only failed inside git repos.
Also close the remaining tolerated 404 noise so a CLI session produces
zero unimplemented responses: model listing (GetUsableModels,
GetDefaultModelForCli, GetDefaultModel), dashboard/plugin housekeeping
(GetGlobalCommands, GetEffectiveUserPlugins, RegisterMarketplaceAndPlugins,
GetCliDownloadUrl) and telemetry (AnalyticsService/SubmitLogs,
AnalyticsService/TrackEvents, OTLP /v1/traces).
Additional logging: PolicyMiddleware now includes the request path in the
per-request log line, which is what made this diagnosable from app.log.
239 lines
7.3 KiB
Go
239 lines
7.3 KiB
Go
package upstream
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"cursor/internal/backend/server"
|
|
)
|
|
|
|
type CompatRouteConfig struct {
|
|
Name string
|
|
StatusCode int
|
|
JSONBody map[string]any
|
|
MockProtoType string
|
|
MockBuilder func(*RequestContext) (map[string]any, error)
|
|
ConsoleLog bool
|
|
}
|
|
|
|
func DirectAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleDirect(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func FixedStatusAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleFixedStatus(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func MockJSONAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleMockJSON(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func MockOAuthAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleMockOAuth(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func MockAuthFullStripeProfileAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleMockAuthFullStripeProfile(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func MockAuthStripeProfileAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleMockAuthStripeProfile(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func MockAuthPollAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleMockAuthPoll(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func MockAuthEmailAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleMockAuthEmail(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func MockProtoAction(deps Dependencies, cfg CompatRouteConfig) server.HandlerFunc {
|
|
return func(ctx *server.Context) error {
|
|
reqCtx, route, err := newCompatRouteObjects(ctx, deps, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleMockProto(reqCtx, route)
|
|
}
|
|
}
|
|
|
|
func newCompatRouteObjects(ctx *server.Context, deps Dependencies, cfg CompatRouteConfig) (*RequestContext, *Route, error) {
|
|
if ctx == nil || ctx.Request == nil {
|
|
return nil, nil, nil
|
|
}
|
|
body, err := io.ReadAll(ctx.Request.Body)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ctx.Request.Body = io.NopCloser(bytes.NewReader(body))
|
|
targetURL := ctx.UpstreamURL
|
|
if targetURL == nil && ctx.Request.URL != nil {
|
|
copyURL := *ctx.Request.URL
|
|
targetURL = ©URL
|
|
}
|
|
reqCtx := &RequestContext{
|
|
ResponseWriter: ctx.Writer,
|
|
Request: ctx.Request,
|
|
StartedAt: ctx.StartedAt,
|
|
RawURL: strings.TrimSpace(ctx.Request.Header.Get(server.HeaderServerUpstreamURL)),
|
|
TargetURL: targetURL,
|
|
Method: strings.ToUpper(strings.TrimSpace(ctx.Request.Method)),
|
|
Headers: ctx.Request.Header.Clone(),
|
|
ContentType: strings.TrimSpace(ctx.Request.Header.Get("content-type")),
|
|
RequestBody: body,
|
|
Mode: ctx.Mode,
|
|
Deps: &deps,
|
|
HTTPRequestID: resolveHTTPRequestID(ctx.Request),
|
|
}
|
|
route := &Route{
|
|
Name: cfg.Name,
|
|
Pattern: ctx.Request.URL.Path,
|
|
StatusCode: cfg.StatusCode,
|
|
JSONBody: cfg.JSONBody,
|
|
MockProtoType: cfg.MockProtoType,
|
|
MockPayloadBuilder: cfg.MockBuilder,
|
|
ConsoleLog: cfg.ConsoleLog,
|
|
}
|
|
return reqCtx, route, nil
|
|
}
|
|
|
|
func ServerTimeMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildServerTimePayload(reqCtx)
|
|
}
|
|
|
|
func ServerConfigMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildServerConfigPayload(reqCtx)
|
|
}
|
|
|
|
func AvailableModelsMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildAvailableModelsPayload(reqCtx)
|
|
}
|
|
|
|
func DefaultModelNudgeMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDefaultModelNudgeDataPayload(reqCtx)
|
|
}
|
|
|
|
func UsableModelsMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildUsableModelsPayload(reqCtx)
|
|
}
|
|
|
|
func DefaultModelForCliMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDefaultModelForCliPayload(reqCtx)
|
|
}
|
|
|
|
func DefaultModelMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDefaultModelPayload(reqCtx)
|
|
}
|
|
|
|
func BootstrapStatsigMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildBootstrapStatsigPayload(reqCtx)
|
|
}
|
|
|
|
func FirstWindowStatsigDecisionMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildFirstWindowStatsigDecisionPayload(reqCtx)
|
|
}
|
|
|
|
func DashboardCurrentPeriodUsageMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardCurrentPeriodUsagePayload(reqCtx)
|
|
}
|
|
|
|
func DashboardTeamsMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardTeamsPayload(reqCtx)
|
|
}
|
|
|
|
func DashboardManagedSkillsMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardManagedSkillsPayload(reqCtx)
|
|
}
|
|
|
|
// EmptyMockBuilder возвращает пустой proto-ответ для ручек, где клиенту
|
|
// достаточно успешного "пусто": нет team-настроек, нет репозиториев,
|
|
// нет маркетплейсов/плагинов/команд, телеметрия принята без обработки.
|
|
func EmptyMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return map[string]any{}, nil
|
|
}
|
|
|
|
// SubmitLogsMockBuilder подтверждает приём логов телеметрии без обработки.
|
|
func SubmitLogsMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return map[string]any{"success": true}, nil
|
|
}
|
|
|
|
func DashboardGetMeMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardGetMePayload(reqCtx)
|
|
}
|
|
|
|
func DashboardUserPrivacyModeMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardUserPrivacyModePayload(reqCtx)
|
|
}
|
|
|
|
func DashboardPlanInfoMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardPlanInfoPayload(reqCtx)
|
|
}
|
|
|
|
func DashboardUsageLimitStatusAndActiveGrantsMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardUsageLimitStatusAndActiveGrantsPayload(reqCtx)
|
|
}
|
|
|
|
func DashboardIsOnNewPricingMockBuilder(reqCtx *RequestContext) (map[string]any, error) {
|
|
return buildDashboardIsOnNewPricingPayload(reqCtx)
|
|
}
|
|
|
|
func resolveHTTPRequestID(request *http.Request) string {
|
|
requestID := strings.TrimSpace(request.Header.Get("x-request-id"))
|
|
if requestID != "" {
|
|
return requestID
|
|
}
|
|
return strings.ReplaceAll(time.Now().UTC().Format(time.RFC3339Nano), ":", "-")
|
|
}
|