feat(cursor): update model handling to use agentv1 responses and enhance CLI model details

- Refactored response handling in newProtoMessage to utilize agentv1 for GetUsableModelsResponse and GetDefaultModelForCliResponse.
- Added new tests for buildCLIModelDetails and encodeCLIModels to ensure correct model details and encoding.
- Updated buildUsableModelsPayload and buildDefaultModelForCliPayload to leverage buildCLIModelDetails for improved model data structure.
This commit is contained in:
leookun
2026-08-03 22:56:17 +08:00
parent 424235f11d
commit 5df3e8f512
3 changed files with 78 additions and 12 deletions
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
"cursor/gen/agentv1"
"cursor/gen/aiserverv1" "cursor/gen/aiserverv1"
"cursor/internal/logger" "cursor/internal/logger"
"cursor/internal/netproxy" "cursor/internal/netproxy"
@@ -433,9 +434,9 @@ func newProtoMessage(typeName string) (proto.Message, error) {
case "aiserver.v1.ListMarketplacesResponse": case "aiserver.v1.ListMarketplacesResponse":
return &aiserverv1.ListMarketplacesResponse{}, nil return &aiserverv1.ListMarketplacesResponse{}, nil
case "aiserver.v1.GetUsableModelsResponse": case "aiserver.v1.GetUsableModelsResponse":
return &aiserverv1.GetUsableModelsResponse{}, nil return &agentv1.GetUsableModelsResponse{}, nil
case "aiserver.v1.GetDefaultModelForCliResponse": case "aiserver.v1.GetDefaultModelForCliResponse":
return &aiserverv1.GetDefaultModelForCliResponse{}, nil return &agentv1.GetDefaultModelForCliResponse{}, nil
case "aiserver.v1.GetDefaultModelResponse": case "aiserver.v1.GetDefaultModelResponse":
return &aiserverv1.GetDefaultModelResponse{}, nil return &aiserverv1.GetDefaultModelResponse{}, nil
case "aiserver.v1.GetGlobalCommandsResponse": case "aiserver.v1.GetGlobalCommandsResponse":
+28 -9
View File
@@ -432,7 +432,7 @@ func buildServerTimePayload(*RequestContext) (map[string]any, error) {
func buildServerConfigPayload(*RequestContext) (map[string]any, error) { func buildServerConfigPayload(*RequestContext) (map[string]any, error) {
return map[string]any{ return map[string]any{
"configVersion": "local_cli_sandbox_defaults_disabled_v2", "configVersion": "local_cli_sandbox_defaults_disabled_v2",
// "http2Config": "HTTP2_CONFIG_FORCE_ALL_DISABLED", "http2Config": "HTTP2_CONFIG_FORCE_ALL_DISABLED",
"cliSandboxDefaultEnabled": true, "cliSandboxDefaultEnabled": true,
"indexingConfig": map[string]any{ "indexingConfig": map[string]any{
"defaultUserPathEncryptionKey": localPathEncryptionKey, "defaultUserPathEncryptionKey": localPathEncryptionKey,
@@ -451,6 +451,7 @@ func buildAvailableModelsPayload(reqCtx *RequestContext) (map[string]any, error)
if len(modelRefs) > 0 { if len(modelRefs) > 0 {
defaultModel = modelRefs[0] defaultModel = modelRefs[0]
} }
modelEntries := buildAvailableModelEntries(adapters)
return map[string]any{ return map[string]any{
"backgroundComposerModelConfig": map[string]any{ "backgroundComposerModelConfig": map[string]any{
"bestOfNDefaultModels": append([]string(nil), modelRefs...), "bestOfNDefaultModels": append([]string(nil), modelRefs...),
@@ -470,7 +471,7 @@ func buildAvailableModelsPayload(reqCtx *RequestContext) (map[string]any, error)
"defaultModel": defaultModel, "defaultModel": defaultModel,
}, },
"disableUnusedModelsAfterNHours": availableModelsDisableUnusedHours, "disableUnusedModelsAfterNHours": availableModelsDisableUnusedHours,
"models": buildAvailableModelEntries(adapters), "models": modelEntries,
"planExecutionModelConfig": map[string]any{ "planExecutionModelConfig": map[string]any{
"defaultModel": defaultModel, "defaultModel": defaultModel,
"fallbackModels": append([]string(nil), modelRefs...), "fallbackModels": append([]string(nil), modelRefs...),
@@ -502,12 +503,7 @@ func buildUsableModelsPayload(reqCtx *RequestContext) (map[string]any, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
refs := collectModelAdapterRefs(adapters) return map[string]any{"models": buildCLIModelDetails(adapters)}, nil
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) { func buildDefaultModelForCliPayload(reqCtx *RequestContext) (map[string]any, error) {
@@ -515,7 +511,11 @@ func buildDefaultModelForCliPayload(reqCtx *RequestContext) (map[string]any, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
return map[string]any{"model": map[string]any{"modelName": firstModelAdapterRef(adapters)}}, nil models := buildCLIModelDetails(adapters)
if len(models) == 0 {
return map[string]any{"model": map[string]any{}}, nil
}
return map[string]any{"model": models[0]}, nil
} }
func buildDefaultModelPayload(reqCtx *RequestContext) (map[string]any, error) { func buildDefaultModelPayload(reqCtx *RequestContext) (map[string]any, error) {
@@ -720,6 +720,25 @@ func buildAvailableModelEntries(adapters []legacyruntime.ModelAdapterConfig) []m
return output return output
} }
func buildCLIModelDetails(adapters []legacyruntime.ModelAdapterConfig) []map[string]any {
models := make([]map[string]any, 0, len(adapters))
for _, adapter := range adapters {
channelID := strings.TrimSpace(adapter.ID)
if channelID == "" {
continue
}
models = append(models, map[string]any{
"modelId": channelID,
"displayModelId": channelID,
"apiKeyCredentials": map[string]any{
"apiKey": strings.TrimSpace(adapter.APIKey),
"baseUrl": strings.TrimSpace(adapter.BaseURL),
},
})
}
return models
}
func buildThinkingEffortParameterDefinitions(adapterType string) []map[string]any { func buildThinkingEffortParameterDefinitions(adapterType string) []map[string]any {
values := thinkingEffortValuesForAdapter(adapterType) values := thinkingEffortValuesForAdapter(adapterType)
options := make([]map[string]any, 0, len(values)) options := make([]map[string]any, 0, len(values))
@@ -2,9 +2,55 @@ package upstream
import ( import (
"encoding/json" "encoding/json"
"reflect"
"testing" "testing"
"cursor/gen/agentv1"
legacyruntime "cursor/internal/runtime"
"google.golang.org/protobuf/proto"
) )
func TestBuildCLIModelDetailsPreservesChannelCredentials(t *testing.T) {
adapters := []legacyruntime.ModelAdapterConfig{
{ID: " channel-a ", ModelID: "model-a", APIKey: "provider-secret-a", BaseURL: "https://provider-a.example/v1"},
{ID: "channel-b", ModelID: "model-a"},
{ID: "", ModelID: "model-c"},
}
got := buildCLIModelDetails(adapters)
want := []map[string]any{
{"modelId": "channel-a", "displayModelId": "channel-a", "apiKeyCredentials": map[string]any{"apiKey": "provider-secret-a", "baseUrl": "https://provider-a.example/v1"}},
{"modelId": "channel-b", "displayModelId": "channel-b", "apiKeyCredentials": map[string]any{"apiKey": "", "baseUrl": ""}},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("build CLI model details: got %v, want %v", got, want)
}
}
func TestEncodeCLIModelsUsesAgentModelDetailsWireFormat(t *testing.T) {
payload := map[string]any{"models": buildCLIModelDetails([]legacyruntime.ModelAdapterConfig{{ID: "channel-a", APIKey: "provider-secret", BaseURL: "https://provider.example/v1"}})}
encoded, err := encodeMockProto("aiserver.v1.GetUsableModelsResponse", payload)
if err != nil {
t.Fatalf("encode CLI models: %v", err)
}
response := &agentv1.GetUsableModelsResponse{}
if err := proto.Unmarshal(encoded, response); err != nil {
t.Fatalf("decode CLI models with agent proto: %v", err)
}
if len(response.Models) != 1 {
t.Fatalf("decoded model count: got %d, want 1", len(response.Models))
}
model := response.Models[0]
if model.GetModelId() != "channel-a" || model.GetDisplayModelId() != "channel-a" {
t.Fatalf("decoded channel IDs: model=%q display=%q", model.GetModelId(), model.GetDisplayModelId())
}
if credentials := model.GetApiKeyCredentials(); credentials == nil || credentials.GetApiKey() != "provider-secret" || credentials.GetBaseUrl() != "https://provider.example/v1" {
t.Fatalf("decoded relay credentials: %#v", credentials)
}
}
func TestBuildBootstrapStatsigConfigJSONDisablesAlwaysLocalDecompositionGate(t *testing.T) { func TestBuildBootstrapStatsigConfigJSONDisablesAlwaysLocalDecompositionGate(t *testing.T) {
payload, err := buildBootstrapStatsigConfigJSON(12345, "test-auth-id") payload, err := buildBootstrapStatsigConfigJSON(12345, "test-auth-id")
if err != nil { if err != nil {