merge old config

This commit is contained in:
leookun
2026-08-26 15:46:11 +08:00
parent 34f97334dc
commit e1937233ec
70 changed files with 4515 additions and 3764 deletions
+25 -29
View File
@@ -9,8 +9,8 @@ use cursor_server::{
cursor::prompting::{PromptAssets, PromptCompiler},
cursor::{connect, proto::agent::v1 as pb, CursorCommand, CursorSessionRegistry},
model::{
ContentPart, ConversationId, MessageContent, Origin, ProjectedContent,
ProviderEndpointInput, ProviderModelInput, ProviderType, Role, Usage,
ContentPart, ConversationId, MessageContent, ModelConfigInput, ModelType, Origin,
ProjectedContent, Role, Usage, OPENAI_CHAT_ENDPOINT,
},
provider::{FinishReason, ModelEvent},
};
@@ -19,34 +19,30 @@ use prost::Message;
#[tokio::test]
async fn summarize_replaces_model_history_and_preserves_cursor_history() {
let (_directory, store) = fixtures::temp_store().await;
let endpoint = store
.create_provider(&ProviderEndpointInput {
name: "Test".into(),
provider_type: ProviderType::OpenAiChat,
base_url: "https://example.com/v1".into(),
api_key: None,
custom_headers: serde_json::json!({}),
extra_params: serde_json::json!({}),
})
.await
.unwrap();
let model = store
.save_provider_model(
endpoint.provider_id,
&ProviderModelInput {
model_id: "test-model".into(),
display_name: "Test Model".into(),
endpoint_type: ProviderType::OpenAiChat,
request_url: String::new(),
enabled: true,
sort_order: 0,
context_window_tokens: None,
max_output_tokens: None,
reasoning_enabled: false,
reasoning_effort: None,
supports_image_generation: false,
},
)
.create_model(&ModelConfigInput {
sort_order: 0,
display_name: "Test Model".into(),
model_type: ModelType::OpenAi,
base_url: "https://example.com/v1/chat/completions".into(),
use_full_url: true,
api_key: "test-key".into(),
tooltip_data: "Test Model".into(),
model_id: "test-model".into(),
reasoning_effort: None,
openai_endpoint: OPENAI_CHAT_ENDPOINT.into(),
openai_extra_params_enabled: false,
openai_extra_params: serde_json::json!({}),
custom_headers_enabled: false,
custom_headers: serde_json::json!({}),
anthropic_extra_params_enabled: false,
anthropic_extra_params: serde_json::json!({}),
context_window_tokens: None,
max_completion_tokens: None,
anthropic_max_tokens: None,
anthropic_thinking_effort: None,
thinking_budget_tokens: None,
})
.await
.unwrap();
let provider = fake_provider::FakeProvider::default();
@@ -1,7 +1,7 @@
use cursor_server::{
model::{
ConversationId, ModelSpec, NewLlmCall, PreparedRun, PromptSpec, ProviderEndpointInput,
ProviderModelInput, ProviderType, RunAction, RunId, RunKind, Usage,
ConversationId, ModelConfigInput, ModelSpec, ModelType, NewLlmCall, PreparedRun,
PromptSpec, ProviderType, RunAction, RunId, RunKind, Usage, OPENAI_CHAT_ENDPOINT,
},
store::{RunStatus, Store},
};
@@ -13,107 +13,91 @@ async fn store() -> (tempfile::TempDir, Store) {
(directory, store)
}
#[tokio::test]
async fn provider_secret_is_write_only_and_model_hash_is_stable() {
let (_directory, store) = store().await;
let provider = store
.create_provider(&ProviderEndpointInput {
name: "Local".into(),
provider_type: ProviderType::OpenAiChat,
base_url: "https://example.com/v1/".into(),
api_key: Some("secret".into()),
custom_headers: serde_json::json!({"x-route":"one", "authorization":"header-secret"}),
extra_params: serde_json::json!({"temperature":0}),
})
.await
.unwrap();
assert!(provider.has_api_key);
assert!(!serde_json::to_string(&provider).unwrap().contains("secret"));
assert_eq!(
provider.custom_headers["authorization"],
serde_json::Value::Null
);
let updated = store
.update_provider(
provider.provider_id,
&ProviderEndpointInput {
name: "Renamed".into(),
provider_type: provider.provider_type,
base_url: provider.base_url.clone(),
api_key: None,
custom_headers: provider.custom_headers.clone(),
extra_params: provider.extra_params.clone(),
},
)
.await
.unwrap();
assert_eq!(updated.name, "Renamed");
assert_eq!(
store
.provider(provider.provider_id)
.await
.unwrap()
.unwrap()
.custom_headers["authorization"],
"header-secret"
);
fn model_input() -> ModelConfigInput {
ModelConfigInput {
sort_order: 0,
display_name: "Model A".into(),
model_type: ModelType::OpenAi,
base_url: "https://example.com/v1/chat/completions".into(),
use_full_url: true,
api_key: "secret".into(),
tooltip_data: "Model A".into(),
model_id: "model-a".into(),
reasoning_effort: None,
openai_endpoint: OPENAI_CHAT_ENDPOINT.into(),
openai_extra_params_enabled: true,
openai_extra_params: serde_json::json!({"temperature":0}),
custom_headers_enabled: true,
custom_headers: serde_json::json!({"x-route":"one"}),
anthropic_extra_params_enabled: false,
anthropic_extra_params: serde_json::json!({}),
context_window_tokens: None,
max_completion_tokens: None,
anthropic_max_tokens: None,
anthropic_thinking_effort: None,
thinking_budget_tokens: None,
}
}
let model = store
.save_provider_model(
provider.provider_id,
&ProviderModelInput {
model_id: "model-a".into(),
display_name: "Model A".into(),
endpoint_type: ProviderType::OpenAiChat,
request_url: String::new(),
enabled: true,
sort_order: 0,
context_window_tokens: None,
max_output_tokens: None,
reasoning_enabled: false,
reasoning_effort: None,
supports_image_generation: true,
},
)
.await
.unwrap();
assert_eq!(model.model_hash, "bab5019a");
assert!(model.supports_image_generation);
#[tokio::test]
async fn model_configuration_round_trips_and_hash_uses_v0049_identity() {
let (_directory, store) = store().await;
let model = store.create_model(&model_input()).await.unwrap();
assert_eq!(model.model_hash.len(), 16);
assert_eq!(model.api_key, "secret");
assert_eq!(model.custom_headers["x-route"], "one");
let original_hash = model.model_hash.clone();
let mut input = model_input();
input.base_url = "https://example.com/v1/chat/completions".into();
input.sort_order = 3;
input.tooltip_data = "Updated tooltip".into();
let updated = store.update_model(&original_hash, &input).await.unwrap();
assert_eq!(updated.model_hash, original_hash);
assert_eq!(updated.sort_order, 3);
assert_eq!(updated.tooltip_data, "Updated tooltip");
}
#[tokio::test]
async fn arbitrary_request_url_is_independent_from_openai_protocol() {
let (_directory, store) = store().await;
let mut input = model_input();
input.base_url = "https://proxy.example.com/arbitrary/generate?api-version=2026-01-01".into();
let chat = store.create_model(&input).await.unwrap();
assert_eq!(chat.provider_type(), ProviderType::OpenAiChat);
assert_eq!(chat.request_url().unwrap(), input.base_url);
input.openai_endpoint = "/v1/responses".into();
let responses = store.update_model(&chat.model_hash, &input).await.unwrap();
assert_eq!(responses.provider_type(), ProviderType::OpenAiResponses);
assert_eq!(responses.request_url().unwrap(), input.base_url);
}
#[tokio::test]
async fn standard_server_address_resolves_to_the_same_model_identity_as_a_complete_url() {
let (_directory, store) = store().await;
let complete = model_input();
let mut standard = complete.clone();
standard.base_url = "https://example.com/v1".into();
standard.use_full_url = false;
let model = store.create_model(&standard).await.unwrap();
assert!(!model.use_full_url);
assert_eq!(
model.request_url().unwrap(),
"https://example.com/v1/chat/completions"
);
assert_eq!(
model.model_hash,
cursor_server::model::model_hash(&complete).unwrap()
);
}
#[tokio::test]
async fn call_summary_is_always_stored_and_payloads_follow_detailed_setting() {
let (_directory, store) = store().await;
let provider = store
.create_provider(&ProviderEndpointInput {
name: "Local".into(),
provider_type: ProviderType::OpenAiChat,
base_url: "https://example.com/v1".into(),
api_key: None,
custom_headers: serde_json::json!({}),
extra_params: serde_json::json!({}),
})
.await
.unwrap();
let model = store
.save_provider_model(
provider.provider_id,
&ProviderModelInput {
model_id: "model-a".into(),
display_name: "Model A".into(),
endpoint_type: ProviderType::OpenAiChat,
request_url: String::new(),
enabled: true,
sort_order: 0,
context_window_tokens: None,
max_output_tokens: None,
reasoning_enabled: false,
reasoning_effort: None,
supports_image_generation: false,
},
)
.await
.unwrap();
let model = store.create_model(&model_input()).await.unwrap();
let call = NewLlmCall {
call_id: "call-1".into(),
run_id: "run-1".into(),
@@ -121,7 +105,7 @@ async fn call_summary_is_always_stored_and_payloads_follow_detailed_setting() {
provider_call_index: 0,
model_hash: model.model_hash,
provider_type: ProviderType::OpenAiChat,
provider_url: provider.base_url,
provider_url: model.base_url.clone(),
request_type: ProviderType::OpenAiChat,
request_url: "https://example.com/v1/chat/completions".into(),
model_id: model.model_id,
+27 -31
View File
@@ -3,8 +3,8 @@ use std::time::Duration;
use axum::{http::header, response::IntoResponse, routing::post, Router};
use cursor_server::{
model::{
ModelInvocation, ModelRequest, ModelSpec, PromptSpec, ProviderEndpointInput,
ProviderModelInput, ProviderType,
ModelConfigInput, ModelInvocation, ModelRequest, ModelSpec, ModelType, PromptSpec,
OPENAI_CHAT_ENDPOINT,
},
provider::{ModelEvent, Provider, ProviderRouter},
store::Store,
@@ -105,7 +105,7 @@ async fn cursor_trace_links_detailed_artifacts_to_the_logical_run() {
#[tokio::test]
async fn records_one_summary_and_raw_payloads_for_one_provider_request() {
let app = Router::new().route(
"/v1/chat/completions",
"/proxy/generate",
post(|| async {
(
[(header::CONTENT_TYPE, "text/event-stream")],
@@ -124,34 +124,30 @@ async fn records_one_summary_and_raw_payloads_for_one_provider_request() {
let (_directory, store) = test_store("observability.db").await;
store.set_detailed_logging(true).await.unwrap();
let endpoint = store
.create_provider(&ProviderEndpointInput {
name: "test".into(),
provider_type: ProviderType::OpenAiChat,
base_url: format!("http://{address}/v1"),
api_key: Some("not-recorded".into()),
custom_headers: serde_json::json!({"x-safe":"visible","authorization":"hidden"}),
extra_params: serde_json::json!({}),
})
.await
.unwrap();
let model = store
.save_provider_model(
endpoint.provider_id,
&ProviderModelInput {
model_id: "actual-model".into(),
display_name: "Display Model".into(),
endpoint_type: ProviderType::OpenAiChat,
request_url: String::new(),
enabled: true,
sort_order: 0,
context_window_tokens: None,
max_output_tokens: None,
reasoning_enabled: false,
reasoning_effort: None,
supports_image_generation: false,
},
)
.create_model(&ModelConfigInput {
sort_order: 0,
display_name: "Display Model".into(),
model_type: ModelType::OpenAi,
base_url: format!("http://{address}/proxy/generate"),
use_full_url: true,
api_key: "not-recorded".into(),
tooltip_data: "Display Model".into(),
model_id: "actual-model".into(),
reasoning_effort: None,
openai_endpoint: OPENAI_CHAT_ENDPOINT.into(),
openai_extra_params_enabled: false,
openai_extra_params: serde_json::json!({}),
custom_headers_enabled: true,
custom_headers: serde_json::json!({"x-safe":"visible","authorization":"hidden"}),
anthropic_extra_params_enabled: false,
anthropic_extra_params: serde_json::json!({}),
context_window_tokens: None,
max_completion_tokens: None,
anthropic_max_tokens: None,
anthropic_thinking_effort: None,
thinking_budget_tokens: None,
})
.await
.unwrap();
let provider = ProviderRouter::new(store.clone(), Duration::from_secs(5));
@@ -183,7 +179,7 @@ async fn records_one_summary_and_raw_payloads_for_one_provider_request() {
let call = store.llm_call("call-1").await.unwrap().unwrap();
assert_eq!(call.status, "completed");
assert_eq!(call.request_type, "openai-chat");
assert!(call.request_url.ends_with("/v1/chat/completions"));
assert_eq!(call.request_url, format!("http://{address}/proxy/generate"));
assert_eq!(call.total_tokens, Some(12));
assert!(call.ttfb_ms.is_some());
assert!(call.ttft_ms.is_some());
+171
View File
@@ -41,3 +41,174 @@ async fn version_two_database_upgrades_with_cursor_request_mapping() {
.iter()
.any(|column| column.get::<String, _>("name") == "cursor_request_id"));
}
#[tokio::test]
async fn provider_and_model_rows_upgrade_to_flat_model_configuration() {
let directory = tempfile::tempdir().unwrap();
let database = directory.path().join("flat-model-upgrade.db");
let pool = sqlx::SqlitePool::connect_with(
SqliteConnectOptions::new()
.filename(&database)
.create_if_missing(true)
.foreign_keys(true),
)
.await
.unwrap();
let all = sqlx::migrate!("./migrations");
let prior = Migrator {
migrations: Cow::Owned(
all.iter()
.filter(|migration| migration.version <= 3)
.cloned()
.collect(),
),
ignore_missing: false,
locking: true,
no_tx: false,
};
prior.run(&pool).await.unwrap();
sqlx::query(
r#"INSERT INTO provider_endpoints(
name, provider_type, base_url, api_key, custom_headers_json, extra_params_json,
created_at_ms, updated_at_ms
) VALUES ('Example', 'openai-chat', 'https://example.com/v1', 'secret',
'{"x-client":"cursor-byok"}', '{"service_tier":"priority"}', 10, 11)"#,
)
.execute(&pool)
.await
.unwrap();
sqlx::query(
r#"INSERT INTO provider_models(
model_hash, provider_id, model_id, display_name, endpoint_type, request_url,
enabled, sort_order, reasoning_enabled, supports_image_generation,
created_at_ms, updated_at_ms
) VALUES
('rspns001', 1, 'model-b', 'Model B', 'openai-responses',
'https://proxy.example.com/arbitrary/generate?api-version=2026-01-01',
1, 5, 0, 0, 12, 13),
('anthr001', 1, 'model-c', 'Model C', 'anthropic', '/proxy/claude',
1, 6, 0, 0, 12, 13),
('anthstd1', 1, 'model-d', 'Model D', 'anthropic', '',
1, 7, 0, 0, 12, 13)"#,
)
.execute(&pool)
.await
.unwrap();
sqlx::query(
r#"INSERT INTO provider_models(
model_hash, provider_id, model_id, display_name, endpoint_type, request_url,
enabled, sort_order, context_window_tokens, max_output_tokens,
reasoning_enabled, reasoning_effort, supports_image_generation,
created_at_ms, updated_at_ms
) VALUES ('12345678', 1, 'model-a', 'Model A', 'openai-chat', '',
1, 4, 200000, 8192, 1, 'high', 0, 12, 13)"#,
)
.execute(&pool)
.await
.unwrap();
sqlx::query(
r#"INSERT INTO llm_calls(
call_id, run_id, conversation_id, provider_call_index, model_hash,
provider_type, provider_url, request_type, request_url, model_id, display_name,
status, created_at_ms, message_count, tool_count, detailed
) VALUES ('call-1', 'run-1', 'conversation-1', 0, '12345678',
'openai-chat', 'https://example.com/v1', 'openai-chat',
'https://example.com/v1/chat/completions', 'model-a', 'Model A',
'completed', 14, 1, 0, 0)"#,
)
.execute(&pool)
.await
.unwrap();
all.run(&pool).await.unwrap();
drop(pool);
let store = Store::connect(&format!("sqlite://{}", database.display()))
.await
.unwrap();
let row = sqlx::query(
r#"SELECT model_hash, sort_order, display_name, model_type, base_url, api_key,
tooltip_data, model_id, reasoning_effort, openai_endpoint, use_full_url,
openai_extra_params_enabled, openai_extra_params_json,
custom_headers_enabled, custom_headers_json, context_window_tokens,
max_completion_tokens
FROM model_configs WHERE model_hash = '12345678'"#,
)
.fetch_one(store.pool())
.await
.unwrap();
assert_eq!(row.get::<String, _>("model_hash"), "12345678");
assert_eq!(row.get::<i64, _>("sort_order"), 4);
assert_eq!(row.get::<String, _>("model_type"), "openai");
assert_eq!(row.get::<String, _>("base_url"), "https://example.com/v1");
assert_eq!(row.get::<String, _>("api_key"), "secret");
assert_eq!(row.get::<i64, _>("use_full_url"), 0);
assert_eq!(row.get::<String, _>("reasoning_effort"), "high");
assert_eq!(
row.get::<String, _>("openai_endpoint"),
"/v1/chat/completions"
);
assert_eq!(row.get::<i64, _>("openai_extra_params_enabled"), 1);
assert_eq!(
row.get::<String, _>("openai_extra_params_json"),
r#"{"service_tier":"priority"}"#
);
assert_eq!(row.get::<i64, _>("custom_headers_enabled"), 1);
assert_eq!(row.get::<i64, _>("context_window_tokens"), 200000);
assert_eq!(row.get::<i64, _>("max_completion_tokens"), 8192);
let migrated_rows = sqlx::query(
"SELECT model_hash, model_type, base_url, use_full_url, openai_endpoint FROM model_configs WHERE model_hash IN ('rspns001', 'anthr001', 'anthstd1') ORDER BY model_hash",
)
.fetch_all(store.pool())
.await
.unwrap();
assert_eq!(migrated_rows[0].get::<String, _>("model_hash"), "anthr001");
assert_eq!(migrated_rows[0].get::<String, _>("model_type"), "anthropic");
assert_eq!(
migrated_rows[0].get::<String, _>("base_url"),
"https://example.com/v1/proxy/claude"
);
assert_eq!(migrated_rows[0].get::<i64, _>("use_full_url"), 1);
assert_eq!(migrated_rows[0].get::<String, _>("openai_endpoint"), "");
assert_eq!(migrated_rows[1].get::<String, _>("model_hash"), "anthstd1");
assert_eq!(migrated_rows[1].get::<String, _>("model_type"), "anthropic");
assert_eq!(
migrated_rows[1].get::<String, _>("base_url"),
"https://example.com/v1"
);
assert_eq!(migrated_rows[1].get::<i64, _>("use_full_url"), 0);
assert_eq!(migrated_rows[1].get::<String, _>("openai_endpoint"), "");
assert_eq!(migrated_rows[2].get::<String, _>("model_hash"), "rspns001");
assert_eq!(migrated_rows[2].get::<String, _>("model_type"), "openai");
assert_eq!(
migrated_rows[2].get::<String, _>("base_url"),
"https://proxy.example.com/arbitrary/generate?api-version=2026-01-01"
);
assert_eq!(migrated_rows[2].get::<i64, _>("use_full_url"), 1);
assert_eq!(
migrated_rows[2].get::<String, _>("openai_endpoint"),
"/v1/responses"
);
assert_eq!(
sqlx::query_scalar::<_, String>(
"SELECT model_hash FROM llm_calls WHERE call_id = 'call-1'"
)
.fetch_one(store.pool())
.await
.unwrap(),
"12345678"
);
assert!(sqlx::query("SELECT 1 FROM provider_endpoints")
.fetch_one(store.pool())
.await
.is_err());
assert!(sqlx::query("SELECT 1 FROM provider_models")
.fetch_one(store.pool())
.await
.is_err());
assert!(sqlx::query("PRAGMA foreign_key_check")
.fetch_all(store.pool())
.await
.unwrap()
.is_empty());
}
+24 -30
View File
@@ -12,9 +12,7 @@ use cursor_server::{
cursor::prompting::{PromptAssets, PromptCompiler},
cursor::{connect, proto::agent::v1 as pb},
cursor::{CursorCommand, CursorSessionRegistry},
model::{
ProjectedContent, ProviderEndpointInput, ProviderModelInput, ProviderType, Role, Usage,
},
model::{ModelConfigInput, ModelType, ProjectedContent, Role, Usage, OPENAI_CHAT_ENDPOINT},
provider::{FinishReason, ModelEvent},
};
use prost::Message;
@@ -22,34 +20,30 @@ use prost::Message;
#[tokio::test]
async fn text_turn_runs_from_bidi_request_through_checkpoint_and_end_stream() {
let (_directory, store) = fixtures::temp_store().await;
let endpoint = store
.create_provider(&ProviderEndpointInput {
name: "Test".into(),
provider_type: ProviderType::OpenAiChat,
base_url: "https://example.com/v1".into(),
api_key: None,
custom_headers: serde_json::json!({}),
extra_params: serde_json::json!({}),
})
.await
.unwrap();
let configured_model = store
.save_provider_model(
endpoint.provider_id,
&ProviderModelInput {
model_id: "test-model".into(),
display_name: "Test Model".into(),
endpoint_type: ProviderType::OpenAiChat,
request_url: String::new(),
enabled: true,
sort_order: 0,
context_window_tokens: None,
max_output_tokens: None,
reasoning_enabled: false,
reasoning_effort: None,
supports_image_generation: false,
},
)
.create_model(&ModelConfigInput {
sort_order: 0,
display_name: "Test Model".into(),
model_type: ModelType::OpenAi,
base_url: "https://example.com/v1/chat/completions".into(),
use_full_url: true,
api_key: "test-key".into(),
tooltip_data: "Test Model".into(),
model_id: "test-model".into(),
reasoning_effort: None,
openai_endpoint: OPENAI_CHAT_ENDPOINT.into(),
openai_extra_params_enabled: false,
openai_extra_params: serde_json::json!({}),
custom_headers_enabled: false,
custom_headers: serde_json::json!({}),
anthropic_extra_params_enabled: false,
anthropic_extra_params: serde_json::json!({}),
context_window_tokens: None,
max_completion_tokens: None,
anthropic_max_tokens: None,
anthropic_thinking_effort: None,
thinking_budget_tokens: None,
})
.await
.unwrap();
let provider = fake_provider::FakeProvider::default();