Extract catalog routes from processor

This commit is contained in:
Meta Vens 2026-05-17 21:16:47 +01:00
commit 72ab5fffcf
5 changed files with 211 additions and 206 deletions

View file

@ -46,6 +46,7 @@ remembers doctrine; the branch remembers the blade.
- One-off command execution and command stream write/resize/terminate route handling now live in `codex_message_processor/command_routes.rs`; `CommandExecManager` remains the lifecycle owner, while the root processor is about 7,800 lines.
- Git diff-to-origin, fuzzy file search/session routes, feedback upload, and Windows sandbox setup now live in `codex_message_processor/utility_routes.rs`; the root processor is about 7,397 lines.
- Realtime conversation routes now live in `codex_message_processor/realtime_routes.rs`, and review-start orchestration now lives in `codex_message_processor/review_routes.rs`; the root processor is about 6,938 lines.
- Model/catalog routes now live in `codex_message_processor/catalog_routes.rs`: model list, collaboration-mode list, experimental feature list, and mock experimental method. The root processor is about 6,738 lines.
- The May 2026 foundation migration in `notes/epiphany-ideal-architecture-rebuild-plan.md` is complete enough to defend. Read `notes/epiphany-architectural-teardown.md` before touching Epiphany control-plane code, but do not treat it as an open-ended excuse to keep shaving the altar. The rebuild moved scene projection, freshness derivation, pressure policy, targeted context shards, bounded graph traversal, job/progress view derivation, planning view derivation, reorientation resume/regather verdict policy, pure CRRC recommendation policy, fixed-lane coordinator decision policy, role-board projection policy, role/reorient result interpretation, role self-persistence review policy, and role/reorient acceptance bundle policy into `epiphany-core`; typed acceptance receipts replaced summary-string identity for live accept paths, runtime links are dual-written on launch, and result read-back now prefers runtime links.
- Read `notes/epiphany-ideal-architecture-rebuild-plan.md` immediately after the teardown. It defines the smallest coherent replacement architecture: durable `EpiphanyState`, runtime-spine-owned `RuntimeState`, separate role memory/heartbeat state, `epiphany-core` as policy owner, app-server as adapter, Aquarium as reflector, typed acceptance receipts, and derived view lenses instead of a protocol verb zoo.
- Do not continue Aquarium UI, bridge, Face, or dogfood expansion until the teardown has a source-grounded cleanup slice plan. Epiphany is the foundation; patches on patches are not a purification rite, they are how the altar becomes load-bearing garbage.

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,5 @@
mod auth_routes;
mod catalog_routes;
mod command_routes;
mod epiphany_automation;
mod epiphany_mutation_routes;
@ -4250,207 +4251,6 @@ impl CodexMessageProcessor {
Ok((items, next_cursor))
}
async fn list_models(
outgoing: Arc<OutgoingMessageSender>,
thread_manager: Arc<ThreadManager>,
request_id: ConnectionRequestId,
params: ModelListParams,
) {
let ModelListParams {
limit,
cursor,
include_hidden,
} = params;
let models = supported_models(thread_manager, include_hidden.unwrap_or(false)).await;
let total = models.len();
if total == 0 {
let response = ModelListResponse {
data: Vec::new(),
next_cursor: None,
};
outgoing.send_response(request_id, response).await;
return;
}
let effective_limit = limit.unwrap_or(total as u32).max(1) as usize;
let effective_limit = effective_limit.min(total);
let start = match cursor {
Some(cursor) => match cursor.parse::<usize>() {
Ok(idx) => idx,
Err(_) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("invalid cursor: {cursor}"),
data: None,
};
outgoing.send_error(request_id, error).await;
return;
}
},
None => 0,
};
if start > total {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("cursor {start} exceeds total models {total}"),
data: None,
};
outgoing.send_error(request_id, error).await;
return;
}
let end = start.saturating_add(effective_limit).min(total);
let items = models[start..end].to_vec();
let next_cursor = if end < total {
Some(end.to_string())
} else {
None
};
let response = ModelListResponse {
data: items,
next_cursor,
};
outgoing.send_response(request_id, response).await;
}
async fn list_collaboration_modes(
outgoing: Arc<OutgoingMessageSender>,
thread_manager: Arc<ThreadManager>,
request_id: ConnectionRequestId,
params: CollaborationModeListParams,
) {
let CollaborationModeListParams {} = params;
let items = thread_manager
.list_collaboration_modes()
.into_iter()
.map(Into::into)
.collect();
let response = CollaborationModeListResponse { data: items };
outgoing.send_response(request_id, response).await;
}
async fn experimental_feature_list(
&self,
request_id: ConnectionRequestId,
params: ExperimentalFeatureListParams,
) {
let ExperimentalFeatureListParams { cursor, limit } = params;
let config = match self.load_latest_config(/*fallback_cwd*/ None).await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
return;
}
};
let data = FEATURES
.iter()
.map(|spec| {
let (stage, display_name, description, announcement) = match spec.stage {
Stage::Experimental {
name,
menu_description,
announcement,
} => (
ApiExperimentalFeatureStage::Beta,
Some(name.to_string()),
Some(menu_description.to_string()),
Some(announcement.to_string()),
),
Stage::UnderDevelopment => (
ApiExperimentalFeatureStage::UnderDevelopment,
None,
None,
None,
),
Stage::Stable => (ApiExperimentalFeatureStage::Stable, None, None, None),
Stage::Deprecated => {
(ApiExperimentalFeatureStage::Deprecated, None, None, None)
}
Stage::Removed => (ApiExperimentalFeatureStage::Removed, None, None, None),
};
ApiExperimentalFeature {
name: spec.key.to_string(),
stage,
display_name,
description,
announcement,
enabled: config.features.enabled(spec.id),
default_enabled: spec.default_enabled,
}
})
.collect::<Vec<_>>();
let total = data.len();
if total == 0 {
self.outgoing
.send_response(
request_id,
ExperimentalFeatureListResponse {
data: Vec::new(),
next_cursor: None,
},
)
.await;
return;
}
// Clamp to 1 so limit=0 cannot return a non-advancing page.
let effective_limit = limit.unwrap_or(total as u32).max(1) as usize;
let effective_limit = effective_limit.min(total);
let start = match cursor {
Some(cursor) => match cursor.parse::<usize>() {
Ok(idx) => idx,
Err(_) => {
self.send_invalid_request_error(
request_id,
format!("invalid cursor: {cursor}"),
)
.await;
return;
}
},
None => 0,
};
if start > total {
self.send_invalid_request_error(
request_id,
format!("cursor {start} exceeds total feature flags {total}"),
)
.await;
return;
}
let end = start.saturating_add(effective_limit).min(total);
let data = data[start..end].to_vec();
let next_cursor = if end < total {
Some(end.to_string())
} else {
None
};
self.outgoing
.send_response(
request_id,
ExperimentalFeatureListResponse { data, next_cursor },
)
.await;
}
async fn mock_experimental_method(
&self,
request_id: ConnectionRequestId,
params: MockExperimentalMethodParams,
) {
let MockExperimentalMethodParams { value } = params;
let response = MockExperimentalMethodResponse { echoed: value };
self.outgoing.send_response(request_id, response).await;
}
async fn send_invalid_request_error(&self, request_id: ConnectionRequestId, message: String) {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,

View file

@ -0,0 +1,204 @@
use super::*;
impl CodexMessageProcessor {
pub(super) async fn list_models(
outgoing: Arc<OutgoingMessageSender>,
thread_manager: Arc<ThreadManager>,
request_id: ConnectionRequestId,
params: ModelListParams,
) {
let ModelListParams {
limit,
cursor,
include_hidden,
} = params;
let models = supported_models(thread_manager, include_hidden.unwrap_or(false)).await;
let total = models.len();
if total == 0 {
let response = ModelListResponse {
data: Vec::new(),
next_cursor: None,
};
outgoing.send_response(request_id, response).await;
return;
}
let effective_limit = limit.unwrap_or(total as u32).max(1) as usize;
let effective_limit = effective_limit.min(total);
let start = match cursor {
Some(cursor) => match cursor.parse::<usize>() {
Ok(idx) => idx,
Err(_) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("invalid cursor: {cursor}"),
data: None,
};
outgoing.send_error(request_id, error).await;
return;
}
},
None => 0,
};
if start > total {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("cursor {start} exceeds total models {total}"),
data: None,
};
outgoing.send_error(request_id, error).await;
return;
}
let end = start.saturating_add(effective_limit).min(total);
let items = models[start..end].to_vec();
let next_cursor = if end < total {
Some(end.to_string())
} else {
None
};
let response = ModelListResponse {
data: items,
next_cursor,
};
outgoing.send_response(request_id, response).await;
}
pub(super) async fn list_collaboration_modes(
outgoing: Arc<OutgoingMessageSender>,
thread_manager: Arc<ThreadManager>,
request_id: ConnectionRequestId,
params: CollaborationModeListParams,
) {
let CollaborationModeListParams {} = params;
let items = thread_manager
.list_collaboration_modes()
.into_iter()
.map(Into::into)
.collect();
let response = CollaborationModeListResponse { data: items };
outgoing.send_response(request_id, response).await;
}
pub(super) async fn experimental_feature_list(
&self,
request_id: ConnectionRequestId,
params: ExperimentalFeatureListParams,
) {
let ExperimentalFeatureListParams { cursor, limit } = params;
let config = match self.load_latest_config(/*fallback_cwd*/ None).await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
return;
}
};
let data = FEATURES
.iter()
.map(|spec| {
let (stage, display_name, description, announcement) = match spec.stage {
Stage::Experimental {
name,
menu_description,
announcement,
} => (
ApiExperimentalFeatureStage::Beta,
Some(name.to_string()),
Some(menu_description.to_string()),
Some(announcement.to_string()),
),
Stage::UnderDevelopment => (
ApiExperimentalFeatureStage::UnderDevelopment,
None,
None,
None,
),
Stage::Stable => (ApiExperimentalFeatureStage::Stable, None, None, None),
Stage::Deprecated => {
(ApiExperimentalFeatureStage::Deprecated, None, None, None)
}
Stage::Removed => (ApiExperimentalFeatureStage::Removed, None, None, None),
};
ApiExperimentalFeature {
name: spec.key.to_string(),
stage,
display_name,
description,
announcement,
enabled: config.features.enabled(spec.id),
default_enabled: spec.default_enabled,
}
})
.collect::<Vec<_>>();
let total = data.len();
if total == 0 {
self.outgoing
.send_response(
request_id,
ExperimentalFeatureListResponse {
data: Vec::new(),
next_cursor: None,
},
)
.await;
return;
}
// Clamp to 1 so limit=0 cannot return a non-advancing page.
let effective_limit = limit.unwrap_or(total as u32).max(1) as usize;
let effective_limit = effective_limit.min(total);
let start = match cursor {
Some(cursor) => match cursor.parse::<usize>() {
Ok(idx) => idx,
Err(_) => {
self.send_invalid_request_error(
request_id,
format!("invalid cursor: {cursor}"),
)
.await;
return;
}
},
None => 0,
};
if start > total {
self.send_invalid_request_error(
request_id,
format!("cursor {start} exceeds total feature flags {total}"),
)
.await;
return;
}
let end = start.saturating_add(effective_limit).min(total);
let data = data[start..end].to_vec();
let next_cursor = if end < total {
Some(end.to_string())
} else {
None
};
self.outgoing
.send_response(
request_id,
ExperimentalFeatureListResponse { data, next_cursor },
)
.await;
}
pub(super) async fn mock_experimental_method(
&self,
request_id: ConnectionRequestId,
params: MockExperimentalMethodParams,
) {
let MockExperimentalMethodParams { value } = params;
let response = MockExperimentalMethodResponse { echoed: value };
self.outgoing.send_response(request_id, response).await;
}
}