Expose model settings in app-server thread metadata (#42151)

## What changed

- Add nullable `model` and `reasoningEffort` fields to the shared app-server `Thread` object and generated schemas.
- Report current settings for loaded threads and the latest persisted settings for unloaded threads across thread read, list, start, resume, rollback, metadata update, and notification paths.
- Keep unavailable legacy or filesystem-only settings nullable, and allow metadata reads without loading a thread or dispatching queued work.

## Testing

- Cover live-setting precedence, persisted settings after restart, null legacy values, and model metadata returned by thread lifecycle operations.

GitOrigin-RevId: 22ebe8a8d1910a820d90a97730d812a00d8c1372
This commit is contained in:
Brent Traut authored and copyberry committed 2026-09-01 22:56:18 +00:00
1 parent 6b59cefcbb
commit 986ff1cc7c
50 files changed
+633 -33

No files matched your search

+2 -2
View File
@@ -164,8 +164,8 @@ pub(super) fn stored_thread_from_rollout_item(
.model_provider
.filter(|provider| !provider.is_empty())
.unwrap_or_else(|| default_provider.to_string()),
model: None,
reasoning_effort: None,
model: item.model,
reasoning_effort: item.reasoning_effort,
created_at,
updated_at,
recency_at,
+20 -1
View File
@@ -32,7 +32,11 @@ pub(super) async fn read_thread(
params: ReadThreadParams,
) -> ThreadStoreResult<StoredThread> {
let thread_id = params.thread_id;
if let Some(metadata) = read_sqlite_metadata(store, thread_id).await
let sqlite_metadata = read_sqlite_metadata(store, thread_id).await;
let persisted_model_settings = sqlite_metadata
.as_ref()
.map(|metadata| (metadata.model.clone(), metadata.reasoning_effort.clone()));
if let Some(metadata) = sqlite_metadata
&& (params.include_archived
|| (metadata.archived_at.is_none()
&& !rollout_path_is_archived(
@@ -66,6 +70,8 @@ pub(super) async fn read_thread(
rollout_thread.name = thread.name;
}
rollout_thread.project_id = thread.project_id;
rollout_thread.model = thread.model;
rollout_thread.reasoning_effort = thread.reasoning_effort;
rollout_thread.git_info = thread.git_info;
rollout_thread.permission_profile = permission_profile_from_metadata_value(
&metadata_sandbox_policy,
@@ -91,6 +97,10 @@ pub(super) async fn read_thread(
})?;
let mut thread = read_thread_from_rollout_path(store, path).await?;
if let Some((model, reasoning_effort)) = persisted_model_settings {
thread.model = model;
thread.reasoning_effort = reasoning_effort;
}
if !params.include_archived && thread.archived_at.is_some() {
return Err(ThreadStoreError::InvalidRequest {
message: format!("thread {} is archived", thread.thread_id),
@@ -141,6 +151,8 @@ pub(super) async fn read_thread_by_rollout_path(
thread.section_position = metadata.section_position;
thread.section_entered_at = metadata.section_entered_at;
thread.project_id = metadata.project_id;
thread.model = metadata.model;
thread.reasoning_effort = metadata.reasoning_effort;
if !metadata.cwd.as_os_str().is_empty()
&& resolve_requested_rollout_path(store, metadata.rollout_path.clone())
.await
@@ -529,6 +541,7 @@ mod tests {
use codex_protocol::ThreadId;
use codex_protocol::items::TurnItem;
use codex_protocol::items::UserMessageItem;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ItemCompletedEvent;
use codex_protocol::protocol::SandboxPolicy;
@@ -1266,6 +1279,8 @@ mod tests {
builder.model_provider = Some("stale-sqlite-provider".to_string());
let mut metadata = builder.build(config.default_model_provider_id.as_str());
metadata.first_user_message = Some("stale sqlite preview".to_string());
metadata.model = Some("persisted-model".to_string());
metadata.reasoning_effort = Some(ReasoningEffort::High);
runtime
.upsert_thread(&metadata)
.await
@@ -1284,6 +1299,10 @@ mod tests {
assert_eq!(thread.rollout_path, Some(rollout_path));
assert_eq!(thread.preview, "Hello from user");
assert_eq!(thread.model_provider, config.default_model_provider_id);
assert_eq!(
(thread.model.as_deref(), thread.reasoning_effort),
(Some("persisted-model"), Some(ReasoningEffort::High))
);
let history = thread.history.expect("history should load");
assert_eq!(history.thread_id, thread_id);
assert_eq!(history.items.len(), 2);