mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
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.
This commit is contained in:
@@ -17,6 +17,13 @@ const (
|
||||
|
||||
modelRuntimeThinkingEffortParameterID = "thinking_effort"
|
||||
|
||||
// localPathEncryptionKey — стабильный ключ шифрования путей для индексации
|
||||
// репозитория, который cursor-agent CLI запрашивает через GetServerConfig
|
||||
// (indexingConfig.default{User,Team}PathEncryptionKey). Без него CLI
|
||||
// не может инициализировать repo identity в git-воркспейсе и вызовы
|
||||
// файловых инструментов падают с "[unimplemented] HTTP 404".
|
||||
localPathEncryptionKey = "6f6e63652d6c6f63616c2d706174682d656e6372797074696f6e2d6b6579"
|
||||
|
||||
localUltraMembershipType = "ultra"
|
||||
localUltraPaymentID = "local_ultra"
|
||||
localUltraSubscriptionStatus = "active"
|
||||
@@ -427,6 +434,10 @@ func buildServerConfigPayload(*RequestContext) (map[string]any, error) {
|
||||
"configVersion": "local_cli_sandbox_defaults_disabled_v2",
|
||||
// "http2Config": "HTTP2_CONFIG_FORCE_ALL_DISABLED",
|
||||
"cliSandboxDefaultEnabled": true,
|
||||
"indexingConfig": map[string]any{
|
||||
"defaultUserPathEncryptionKey": localPathEncryptionKey,
|
||||
"defaultTeamPathEncryptionKey": localPathEncryptionKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -486,6 +497,36 @@ func buildDefaultModelNudgeDataPayload(reqCtx *RequestContext) (map[string]any,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildUsableModelsPayload(reqCtx *RequestContext) (map[string]any, error) {
|
||||
adapters, err := loadConfiguredModelAdapters(reqCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refs := collectModelAdapterRefs(adapters)
|
||||
models := make([]map[string]any, 0, len(refs))
|
||||
for _, ref := range refs {
|
||||
models = append(models, map[string]any{"modelName": ref})
|
||||
}
|
||||
return map[string]any{"models": models}, nil
|
||||
}
|
||||
|
||||
func buildDefaultModelForCliPayload(reqCtx *RequestContext) (map[string]any, error) {
|
||||
adapters, err := loadConfiguredModelAdapters(reqCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"model": map[string]any{"modelName": firstModelAdapterRef(adapters)}}, nil
|
||||
}
|
||||
|
||||
func buildDefaultModelPayload(reqCtx *RequestContext) (map[string]any, error) {
|
||||
adapters, err := loadConfiguredModelAdapters(reqCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultModel := firstModelAdapterRef(adapters)
|
||||
return map[string]any{"model": defaultModel, "thinkingModel": defaultModel}, nil
|
||||
}
|
||||
|
||||
func buildBootstrapStatsigPayload(reqCtx *RequestContext) (map[string]any, error) {
|
||||
generatedAtMs := uint64(time.Now().UnixMilli())
|
||||
authID := resolveBootstrapStatsigAuthID(reqCtx)
|
||||
@@ -823,6 +864,16 @@ func collectModelAdapterRefs(adapters []legacyruntime.ModelAdapterConfig) []stri
|
||||
return output
|
||||
}
|
||||
|
||||
// firstModelAdapterRef возвращает канал первого адаптера или пустую строку,
|
||||
// если ни один адаптер не сконфигурирован.
|
||||
func firstModelAdapterRef(adapters []legacyruntime.ModelAdapterConfig) string {
|
||||
refs := collectModelAdapterRefs(adapters)
|
||||
if len(refs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return refs[0]
|
||||
}
|
||||
|
||||
func resolveBootstrapStatsigAuthID(reqCtx *RequestContext) string {
|
||||
if reqCtx != nil {
|
||||
if authID := authIDFromBearer(reqCtx.Headers.Get("authorization")); authID != "" {
|
||||
|
||||
Reference in New Issue
Block a user