Files
cursor-byok/internal/backend/server/middleware.go
T
warelik 2c650b3765 fix(backend): cover cursor-agent CLI local-mode endpoint surface
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.
2026-08-01 10:58:19 +03:00

102 lines
2.4 KiB
Go

package server
import (
"cursor/internal/logger"
"errors"
"fmt"
"net/http"
"runtime/debug"
"strings"
serverconfig "cursor/internal/backend/server/config"
legacyruntime "cursor/internal/runtime"
)
func Recover() Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(ctx *Context) (err error) {
defer func() {
if recovered := recover(); recovered != nil {
err = fmt.Errorf("panic: %v\n%s", recovered, string(debug.Stack()))
}
}()
return next(ctx)
}
}
}
func ServerContext() Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(ctx *Context) error {
if ctx == nil {
return fmt.Errorf("server context is nil")
}
if err := ctx.ParseUpstreamURL(); err != nil {
return err
}
return next(ctx)
}
}
}
func PolicyMiddleware(configs *serverconfig.Manager) Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(ctx *Context) error {
ctx.Mode = parseExecutionMode(configs.RouteMode(ctx.UpstreamURL != nil))
path := ""
if ctx.Request != nil && ctx.Request.URL != nil {
path = ctx.Request.URL.Path
}
logger.Infof("ctx.Mode=%s upstream=%t path=%s", ctx.Mode, ctx.UpstreamURL != nil, path)
return next(ctx)
}
}
}
func ErrorEncoder() Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(ctx *Context) error {
if ctx != nil {
ctx.LastError = nil
}
if err := next(ctx); err != nil {
if ctx != nil {
ctx.LastError = err
}
if ctx == nil || ctx.Writer == nil {
return err
}
writeServerError(ctx.Writer, err)
return nil
}
return nil
}
}
}
func writeServerError(writer http.ResponseWriter, err error) {
if responseWriterHasWrittenHeader(writer) {
return
}
status := http.StatusBadGateway
message := "bad gateway"
switch {
case err == nil:
status = http.StatusOK
message = ""
case strings.TrimSpace(err.Error()) == "empty raw url":
status = http.StatusBadRequest
message = "invalid raw url"
case errors.Is(err, ErrInvalidBidiAppendPayload):
status = http.StatusBadRequest
message = "invalid bidi append payload"
case errors.Is(err, legacyruntime.ErrInvalidSystemSetting):
status = http.StatusInternalServerError
message = "invalid system setting"
case errors.Is(err, legacyruntime.ErrChannelNotAvailable):
status = http.StatusServiceUnavailable
message = "no available channel"
}
http.Error(writer, message, status)
}