mirror of
https://github.com/openai/codex.git
synced 2026-04-29 17:06:51 +00:00
update
This commit is contained in:
@@ -92,6 +92,7 @@ Example (from OpenAI's official VSCode extension):
|
||||
- `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review.
|
||||
- `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation).
|
||||
- `model/list` — list available models (with reasoning effort options and optional `upgrade` model ids).
|
||||
- `experimentalFeature/list` — list experimental feature flags with metadata (flag name, display name, description, announcement, enabled/default-enabled) and cursor pagination.
|
||||
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination).
|
||||
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
|
||||
- `skills/remote/read` — list public remote skills (**under development; do not call from production clients yet**).
|
||||
|
||||
@@ -32,6 +32,9 @@ use codex_app_server_protocol::ConversationGitInfo;
|
||||
use codex_app_server_protocol::ConversationSummary;
|
||||
use codex_app_server_protocol::DynamicToolSpec as ApiDynamicToolSpec;
|
||||
use codex_app_server_protocol::ExecOneOffCommandResponse;
|
||||
use codex_app_server_protocol::ExperimentalFeature as ApiExperimentalFeature;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListParams;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListResponse;
|
||||
use codex_app_server_protocol::FeedbackUploadParams;
|
||||
use codex_app_server_protocol::FeedbackUploadResponse;
|
||||
use codex_app_server_protocol::ForkConversationParams;
|
||||
@@ -165,7 +168,9 @@ use codex_core::default_client::get_codex_user_agent;
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::exec::ExecParams;
|
||||
use codex_core::exec_env::create_env;
|
||||
use codex_core::features::FEATURES;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::features::Stage;
|
||||
use codex_core::find_archived_thread_path_by_id_str;
|
||||
use codex_core::find_thread_path_by_id_str;
|
||||
use codex_core::git_info::git_diff_to_remote;
|
||||
@@ -517,6 +522,9 @@ impl CodexMessageProcessor {
|
||||
Self::list_models(outgoing, thread_manager, config, request_id, params).await;
|
||||
});
|
||||
}
|
||||
ClientRequest::ExperimentalFeatureList { request_id, params } => {
|
||||
self.experimental_feature_list(request_id, params).await;
|
||||
}
|
||||
ClientRequest::CollaborationModeList { request_id, params } => {
|
||||
let outgoing = self.outgoing.clone();
|
||||
let thread_manager = self.thread_manager.clone();
|
||||
@@ -3050,6 +3058,98 @@ impl CodexMessageProcessor {
|
||||
outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
|
||||
async fn experimental_feature_list(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
params: ExperimentalFeatureListParams,
|
||||
) {
|
||||
let ExperimentalFeatureListParams { cursor, limit } = params;
|
||||
let config = match self.load_latest_config().await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let data = FEATURES
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
let Stage::Experimental {
|
||||
name,
|
||||
menu_description,
|
||||
announcement,
|
||||
} = spec.stage
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
Some(ApiExperimentalFeature {
|
||||
flag_name: spec.key.to_string(),
|
||||
display_name: name.to_string(),
|
||||
description: menu_description.to_string(),
|
||||
announcement: announcement.to_string(),
|
||||
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;
|
||||
}
|
||||
|
||||
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 experimental features {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: RequestId,
|
||||
|
||||
@@ -22,6 +22,7 @@ use codex_app_server_protocol::CollaborationModeListParams;
|
||||
use codex_app_server_protocol::ConfigBatchWriteParams;
|
||||
use codex_app_server_protocol::ConfigReadParams;
|
||||
use codex_app_server_protocol::ConfigValueWriteParams;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListParams;
|
||||
use codex_app_server_protocol::FeedbackUploadParams;
|
||||
use codex_app_server_protocol::ForkConversationParams;
|
||||
use codex_app_server_protocol::GetAccountParams;
|
||||
@@ -473,6 +474,15 @@ impl McpProcess {
|
||||
self.send_request("model/list", params).await
|
||||
}
|
||||
|
||||
/// Send an `experimentalFeature/list` JSON-RPC request.
|
||||
pub async fn send_experimental_feature_list_request(
|
||||
&mut self,
|
||||
params: ExperimentalFeatureListParams,
|
||||
) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
self.send_request("experimentalFeature/list", params).await
|
||||
}
|
||||
|
||||
/// Send an `app/list` JSON-RPC request.
|
||||
pub async fn send_apps_list_request(&mut self, params: AppsListParams) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use app_test_support::McpProcess;
|
||||
use app_test_support::to_response;
|
||||
use codex_app_server_protocol::ExperimentalFeature;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListParams;
|
||||
use codex_app_server_protocol::ExperimentalFeatureListResponse;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_core::features::FEATURES;
|
||||
use codex_core::features::Stage;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
async fn experimental_feature_list_returns_experimental_feature_metadata() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_experimental_feature_list_request(ExperimentalFeatureListParams::default())
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let actual = to_response::<ExperimentalFeatureListResponse>(response)?;
|
||||
let expected_data = FEATURES
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
let Stage::Experimental {
|
||||
name,
|
||||
menu_description,
|
||||
announcement,
|
||||
} = spec.stage
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(ExperimentalFeature {
|
||||
flag_name: spec.key.to_string(),
|
||||
display_name: name.to_string(),
|
||||
description: menu_description.to_string(),
|
||||
announcement: announcement.to_string(),
|
||||
enabled: spec.default_enabled,
|
||||
default_enabled: spec.default_enabled,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let expected = ExperimentalFeatureListResponse {
|
||||
data: expected_data,
|
||||
next_cursor: None,
|
||||
};
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
Ok(())
|
||||
}
|
||||
@@ -6,6 +6,7 @@ mod compaction;
|
||||
mod config_rpc;
|
||||
mod dynamic_tools;
|
||||
mod experimental_api;
|
||||
mod experimental_feature_list;
|
||||
mod initialize;
|
||||
mod model_list;
|
||||
mod output_schema;
|
||||
|
||||
Reference in New Issue
Block a user