Gate CLI Daybreak controls and selection behind an opt-in feature (#51207)

## What changed

Add `features.cli_daybreak`, disabled by default, to gate Daybreak controls and automatic access-program selection in the TUI and `codex exec`. Apply the gate to resumed sessions and background task turns, and hide `/daybreak` when disabled. Explicit `--cyber-access-program` overrides remain available.

## Testing

Extend tests to cover disabled controls, history replay, foreground and background turn requests, exec resume/fork behavior, and explicit access-program overrides with the feature disabled.

GitOrigin-RevId: ae96a4a8c7ab97c619716b9f39ea9f295df6e5a6
This commit is contained in:
Brian Lee
2026-10-06 00:16:44 +00:00
committed by copyberry
parent c19525e55e
commit 5ad6891696
21 changed files with 161 additions and 31 deletions
+6
View File
@@ -791,6 +791,9 @@
"chronicle": {
"type": "boolean"
},
"cli_daybreak": {
"type": "boolean"
},
"code_mode": {
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
},
@@ -6961,6 +6964,9 @@
"chronicle": {
"type": "boolean"
},
"cli_daybreak": {
"type": "boolean"
},
"code_mode": {
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
},
+6 -2
View File
@@ -875,7 +875,10 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
cyber_access_program,
} = args;
if config.daybreak_enabled && !matches!(&command, Some(ExecCommand::Review(_))) {
if config.features.enabled(Feature::CliDaybreak)
&& config.daybreak_enabled
&& !matches!(&command, Some(ExecCommand::Review(_)))
{
anyhow::ensure!(
!oss || matches!(
&command,
@@ -1208,7 +1211,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
} => {
let cyber_access_program = match cyber_access_program {
Some(program) => Some(program),
None => {
None if config.features.enabled(Feature::CliDaybreak) => {
daybreak::program_for_turn(
&client,
&mut request_ids,
@@ -1218,6 +1221,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
)
.await?
}
None => None,
};
let response: TurnStartResponse = send_request_with_response(
&client,
+27 -13
View File
@@ -56,7 +56,10 @@ async fn exec_rejects_daybreak_for_api_keys_and_allows_an_override() -> anyhow::
async fn exec_rejects_daybreak_for_non_openai_provider() -> anyhow::Result<()> {
let test = test_codex_exec();
let server = responses::start_mock_server().await;
std::fs::write(test.home_path().join("config.toml"), "daybreak = true\n")?;
std::fs::write(
test.home_path().join("config.toml"),
"daybreak = true\nfeatures.cli_daybreak = true\n",
)?;
let response = responses::mount_sse_once(&server, responses::sse_completed("response")).await;
test.cmd_with_server(&server)
@@ -110,7 +113,7 @@ async fn configured_daybreak_exec() -> anyhow::Result<(TestCodexExecBuilder, wir
std::fs::write(
test.home_path().join("config.toml"),
format!(
"cli_auth_credentials_store = 'file'\nchatgpt_base_url = '{}/backend-api'\nmodel_catalog_json = '{}'\nmodel = 'gpt-test'\ndaybreak = true\n",
"cli_auth_credentials_store = 'file'\nchatgpt_base_url = '{}/backend-api'\nmodel_catalog_json = '{}'\nmodel = 'gpt-test'\ndaybreak = true\nfeatures.cli_daybreak = true\n",
server.uri(),
catalog.display()
),
@@ -154,7 +157,7 @@ async fn exec_resumes_and_forks_the_saved_daybreak_choice() -> anyhow::Result<()
std::fs::write(
test.home_path().join("config.toml"),
format!(
"cli_auth_credentials_store = 'file'\nchatgpt_base_url = '{}/backend-api'\nmodel_catalog_json = '{}'\nmodel = 'gpt-test'\n",
"cli_auth_credentials_store = 'file'\nchatgpt_base_url = '{}/backend-api'\nmodel_catalog_json = '{}'\nmodel = 'gpt-test'\nfeatures.cli_daybreak = true\n",
server.uri(),
test.home_path().join("catalog.json").display()
),
@@ -168,19 +171,26 @@ async fn exec_resumes_and_forks_the_saved_daybreak_choice() -> anyhow::Result<()
thread_id.as_str(),
"continue",
],
"standard",
Some("standard"),
),
(
vec!["resume", thread_id.as_str(), "continue"],
"daybreak_blue",
Some("daybreak_blue"),
),
(
vec!["fork", thread_id.as_str(), "continue"],
"daybreak_blue",
Some("daybreak_blue"),
),
(
vec!["--ephemeral", "fork", thread_id.as_str(), "continue"],
"daybreak_blue",
vec![
"-c",
"features.cli_daybreak=false",
"--ephemeral",
"fork",
thread_id.as_str(),
"continue",
],
None,
),
(
vec![
@@ -191,7 +201,7 @@ async fn exec_resumes_and_forks_the_saved_daybreak_choice() -> anyhow::Result<()
thread_id.as_str(),
"continue",
],
"standard",
Some("standard"),
),
] {
let response =
@@ -205,8 +215,8 @@ async fn exec_resumes_and_forks_the_saved_daybreak_choice() -> anyhow::Result<()
.assert()
.success();
assert_eq!(
response.single_request().body_json()["access_programs"],
json!({"cyber": expected})
response.single_request().body_json().get("access_programs"),
expected.map(|program| json!({"cyber": program})).as_ref()
);
}
Ok(())
@@ -225,6 +235,8 @@ async fn exec_explicit_cyber_program_overrides_daybreak_for_one_turn() -> anyhow
.args([
"--skip-git-repo-check",
"--json",
"-c",
"features.cli_daybreak=false",
"--cyber-access-program",
"daybreak_red",
"hello",
@@ -266,14 +278,16 @@ async fn exec_resume_override_does_not_change_a_standard_thread() -> anyhow::Res
"--skip-git-repo-check",
"--json",
"-c",
"features.cli_daybreak=false",
"-c",
"daybreak=false",
"hello",
])
.output()?;
let thread_id = started_thread(output)?;
assert_eq!(
response.single_request().body_json()["access_programs"],
json!({"cyber": "standard"})
response.single_request().body_json().get("access_programs"),
None
);
for (args, expected) in [
(
+8
View File
@@ -98,6 +98,8 @@ pub enum Feature {
ApiKeyModelDiscovery,
/// Forward explicit programs with builtin OpenAI API keys.
ApiKeyCyberAccessPrograms,
/// Enable Daybreak controls and automatic access-program selection in CLI clients.
CliDaybreak,
/// Deprecated no-op; use `tui.fullscreen_transcript` instead.
TranscriptV2,
// Stable.
@@ -1338,6 +1340,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::Stable,
default_enabled: true,
},
FeatureSpec {
id: Feature::CliDaybreak,
key: "cli_daybreak",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::ApiKeyCyberAccessPrograms,
key: "api_key_cyber_access_programs",
@@ -622,11 +622,13 @@ impl App {
app_server_client
.thread_tool_transport()
.configure(&mut thread_start_params);
let features = self.config.features.get().clone();
let task = tokio::spawn(async move {
let response = crate::dynamic_tools::execute(
request_handle,
params,
thread_start_params,
features,
status_updates,
Some(&app_event_tx),
)
+7 -1
View File
@@ -9,7 +9,13 @@ impl App {
thread_id: ThreadId,
enabled: bool,
) {
if self.active_thread_id != Some(thread_id) {
if !self
.chat_widget
.config_ref()
.features
.enabled(Feature::CliDaybreak)
|| self.active_thread_id != Some(thread_id)
{
return;
}
if enabled
@@ -184,6 +184,7 @@ async fn reconnect_restores_history_permissions_and_resumes_unsent_input() -> Re
crate::dynamic_tools_mcp::DynamicToolMcpServer::start(
session.request_handle(),
codex_app_server_protocol::ThreadStartParams::default(),
app.config.features.get().clone(),
app.app_event_tx.clone(),
app.dynamic_tool_status_updates.clone(),
/*managed_requirement*/ None,
@@ -39,12 +39,16 @@ fn error_notification(
#[tokio::test]
async fn misalignment_continuation_requires_current_review_and_submits_once() -> Result<()> {
for (reject, preserve, daybreak) in [
(false, false, false),
(false, true, true),
(true, false, false),
for (reject, preserve, daybreak, rollout_enabled) in [
(false, false, false, true),
(false, true, true, true),
(true, false, false, true),
(false, false, false, false),
(false, true, true, false),
] {
let (mut app, mut rx, _) = make_test_app_with_channels().await;
app.chat_widget
.set_feature_enabled(Feature::CliDaybreak, rollout_enabled);
let (mut server, requests, proxy) = start_recording_app_server(
&app.config,
/*blocked_thread_list*/ None,
@@ -226,7 +230,7 @@ async fn misalignment_continuation_requires_current_review_and_submits_once() ->
approval_policy: Some(AskForApproval::OnRequest),
approvals_reviewer: Some(config.approvals_reviewer.into()),
sandbox_policy: (!preserve).then(|| config.legacy_sandbox_policy().into()),
cyber_access_program: Some(if daybreak {
cyber_access_program: rollout_enabled.then_some(if daybreak {
CyberAccessProgram::DaybreakBlue.into()
} else {
CyberAccessProgram::Standard.into()
@@ -784,6 +784,8 @@ goals = true
/*status_account_display*/ None, /*plan_type*/ None,
/*has_chatgpt_account*/ true, /*has_codex_backend_auth*/ false,
);
app.chat_widget
.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
app.chat_widget.set_daybreak_enabled(/*enabled*/ true);
Box::pin(app.retry_safety_buffered_turn(
&mut tui,
@@ -49,6 +49,9 @@ async fn daybreak_command_persists_and_confirms_each_selection() -> Result<()> {
use codex_protocol::turn_input::CyberAccessProgram;
let (mut app, mut events, mut ops) = make_test_app_with_channels().await;
app.config.features.enable(Feature::CliDaybreak)?;
app.chat_widget
.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
let (mut server, requests, proxy) = start_recording_app_server(
&app.config,
/*blocked_thread_list*/ None,
@@ -175,6 +178,18 @@ async fn daybreak_command_persists_and_confirms_each_selection() -> Result<()> {
assert_eq!(turns[0]["cyberAccessProgram"], "standard");
assert_eq!(turns[1]["cyberAccessProgram"], "daybreakBlue");
assert_eq!(turns[2]["cyberAccessProgram"], "standard");
app.chat_widget
.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ false);
for target in [thread_id, background_thread_id] {
app.submit_thread_op(&mut server, target, turn.clone())
.await?;
}
let turns = recorded_params(&requests, "turn/start");
assert!(turns[3]["cyberAccessProgram"].is_null());
assert!(turns[4]["cyberAccessProgram"].is_null());
app.chat_widget
.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
app.chat_widget.set_daybreak_enabled(/*enabled*/ true);
app.chat_widget.update_account_state(
Some(crate::status::StatusAccountDisplay::ApiKey),
/*plan_type*/ None,
@@ -190,8 +205,8 @@ async fn daybreak_command_persists_and_confirms_each_selection() -> Result<()> {
app.chat_widget.set_daybreak_enabled(/*enabled*/ false);
app.submit_thread_op(&mut server, thread_id, turn).await?;
let turns = recorded_params(&requests, "turn/start");
assert_eq!(turns[3]["cyberAccessProgram"], "daybreakBlue");
assert!(turns[4]["cyberAccessProgram"].is_null());
assert_eq!(turns[5]["cyberAccessProgram"], "daybreakBlue");
assert!(turns[6]["cyberAccessProgram"].is_null());
while events.try_recv().is_ok() {}
let missing_thread_id = ThreadId::new();
@@ -1004,11 +1019,13 @@ fn spawn_approved_task_tool_call(
app_server
.thread_tool_transport()
.configure(&mut thread_start_params);
let features = app.config.features.get().clone();
tokio::spawn(async move {
let response = crate::dynamic_tools::execute(
request_handle,
params,
thread_start_params,
features,
status_updates,
Some(&app_event_tx),
)
@@ -1772,7 +1789,15 @@ async fn embedded_server_rejects_unowned_dynamic_tool_calls() -> Result<()> {
#[tokio::test]
async fn dynamic_tool_requests_ignore_other_namespaces_and_dispatch_tui_namespace() -> Result<()> {
check_dynamic_tool_requests(/*rollout_enabled*/ true).await?;
check_dynamic_tool_requests(/*rollout_enabled*/ false).await
}
async fn check_dynamic_tool_requests(rollout_enabled: bool) -> Result<()> {
let (mut app, mut events, _ops) = make_test_app_with_channels().await;
app.config
.features
.set_enabled(Feature::CliDaybreak, rollout_enabled)?;
let codex_home = tempdir()?;
let backend = wiremock::MockServer::start().await;
let backend_url = format!("{}/backend-api", backend.uri());
@@ -2085,7 +2110,7 @@ async fn dynamic_tool_requests_ignore_other_namespaces_and_dispatch_tui_namespac
);
assert_eq!(
recorded_params(&requests, "thread/start").last().unwrap()["daybreakEnabled"],
true
serde_json::json!(rollout_enabled.then_some(true))
);
assert_eq!(
recorded_params(&requests, "thread/start")
@@ -2100,7 +2125,10 @@ async fn dynamic_tool_requests_ignore_other_namespaces_and_dispatch_tui_namespac
let turn = recorded_params(&requests, "turn/start")
.pop()
.expect("background task turn request");
assert_eq!(turn["cyberAccessProgram"], "daybreakBlue");
assert_eq!(
turn["cyberAccessProgram"],
serde_json::json!(rollout_enabled.then_some("daybreakBlue"))
);
assert_eq!(turn["input"], serde_json::json!([]));
assert_eq!(
turn["toolOutput"],
@@ -2182,7 +2210,10 @@ async fn dynamic_tool_requests_ignore_other_namespaces_and_dispatch_tui_namespac
};
assert!(response.success, "{response:?}");
let turn = &recorded_params(&requests, "turn/start")[1];
assert_eq!(turn["cyberAccessProgram"], "daybreakBlue");
assert_eq!(
turn["cyberAccessProgram"],
serde_json::json!(rollout_enabled.then_some("daybreakBlue"))
);
assert_eq!(turn["input"], serde_json::json!([]));
assert_eq!(
turn["toolOutput"],
@@ -2686,6 +2717,7 @@ async fn remote_legacy_history_start_negotiates_once_for_resume_and_fork() -> Re
}),
},
codex_app_server_protocol::ThreadStartParams::default(),
app.config.features.get().clone(),
status_updates,
/*app_event_tx*/ None,
)
+1
View File
@@ -547,6 +547,7 @@ impl AppServerSession {
DynamicToolMcpServer::start(
self.request_handle(),
thread_start_params,
config.features.get().clone(),
app_event_tx,
status_updates,
managed_requirement,
+2 -1
View File
@@ -72,7 +72,8 @@ impl ChatWidget {
let connector_scope_changed = previous_thread_id != Some(session.thread_id)
|| self.config.cwd.as_path() != session.cwd.as_path();
self.thread_id = Some(session.thread_id);
self.daybreak_enabled = session.daybreak_enabled
self.daybreak_enabled = self.config.features.enabled(Feature::CliDaybreak)
&& session.daybreak_enabled
&& !matches!(display, SessionConfiguredDisplay::SideConversation);
#[cfg(target_os = "windows")]
if self.windows_sandbox_local_server
+7 -2
View File
@@ -6,14 +6,15 @@ use crate::chatwidget::rate_limits::RATE_LIMIT_SWITCH_PROMPT_VIEW_ID;
impl ChatWidget {
pub(crate) fn set_daybreak_enabled(&mut self, enabled: bool) {
self.daybreak_enabled = enabled;
self.daybreak_enabled = enabled && self.config.features.enabled(Feature::CliDaybreak);
self.bottom_pane
.set_daybreak_command_description(self.daybreak_command_description());
}
/// UI eligibility only; the catalog and server still determine model/program access.
pub(crate) fn daybreak_account_eligible(&self) -> bool {
self.config.model_provider_id == "openai"
self.config.features.enabled(Feature::CliDaybreak)
&& self.config.model_provider_id == "openai"
&& (self.has_chatgpt_account
|| matches!(
self.status_account_display,
@@ -111,6 +112,10 @@ impl ChatWidget {
);
}
let enabled = self.config.features.enabled(feature);
if feature == Feature::CliDaybreak {
self.set_daybreak_enabled(self.daybreak_enabled);
self.refresh_status_surfaces();
}
if feature == Feature::FastMode {
self.refresh_effective_service_tier();
self.sync_service_tier_commands();
@@ -189,6 +189,9 @@ impl ChatWidget {
match cmd {
SlashCommand::Daybreak => {
if !self.config.features.enabled(Feature::CliDaybreak) {
return;
}
if !self.daybreak_enabled {
if !self.daybreak_account_eligible() {
self.add_error_message("Daybreak requires the OpenAI provider with either ChatGPT sign-in or an API key with Daybreak support enabled.".into());
@@ -19,7 +19,7 @@ async fn resumed_initial_messages_render_history() {
let thread_id = ThreadId::new();
let rollout_file = NamedTempFile::new().unwrap();
let configured = crate::session_state::ThreadSessionState {
let mut configured = crate::session_state::ThreadSessionState {
daybreak_enabled: true,
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
thread_id,
@@ -43,6 +43,18 @@ async fn resumed_initial_messages_render_history() {
rollout_path: Some(rollout_file.path().to_path_buf()),
};
chat.handle_thread_session(configured.clone());
assert!(!chat.daybreak_enabled);
insta::assert_snapshot!(
drain_insert_history(&mut rx).into_iter().flatten()
.map(|line| line.to_string())
.filter(|line| line.contains("Daybreak"))
.collect::<Vec<_>>().join("\n"),
@""
);
chat.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
configured.thread_id = ThreadId::new();
chat.handle_thread_session(configured);
assert!(chat.daybreak_enabled);
replay_user_message_text(
@@ -2168,6 +2168,7 @@ async fn slash_copy_picker_remains_available_from_parent_owned_threads() {
#[tokio::test]
async fn slash_daybreak_offers_an_application_when_unavailable() {
let (mut chat, mut rx, _ops) = make_chatwidget_manual(/*model_override*/ None).await;
chat.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
chat.has_chatgpt_account = true;
chat.config.model_provider_id = "openai".into();
let mut model = crate::test_support::TEST_MODEL_PRESETS[0].clone();
@@ -2250,6 +2251,12 @@ async fn slash_daybreak_offers_an_application_when_unavailable() {
.unwrap()
);
}
chat.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ false);
chat.set_daybreak_enabled(/*enabled*/ true);
assert!(!chat.daybreak_enabled);
assert!(chat.daybreak_command_description().is_none());
chat.dispatch_command(SlashCommand::Daybreak);
assert!(drain_insert_history(&mut rx).is_empty());
}
#[tokio::test]
@@ -148,6 +148,7 @@ async fn daybreak_refusal_offers_enable_for_the_next_turn() {
FrameRequester::test_dummy(),
)
.await;
chat.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
let thread_id = ThreadId::new();
chat.thread_id = Some(thread_id);
let mut model = crate::test_support::TEST_MODEL_PRESETS[0].clone();
@@ -446,6 +446,7 @@ async fn status_line_setup_popup_rate_limits_snapshot() {
#[tokio::test]
async fn status_line_setup_popup_mixed_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
chat.status_line_branch = Some("feature/mixed-preview".to_string());
chat.thread_name = Some("Mixed preview thread".to_string());
chat.set_daybreak_enabled(/*enabled*/ true);
@@ -500,6 +501,7 @@ async fn terminal_title_setup_popup_hardcoded_only_snapshot() {
#[tokio::test]
async fn terminal_title_setup_popup_mixed_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.set_feature_enabled(Feature::CliDaybreak, /*enabled*/ true);
chat.thread_name = Some("Mixed preview thread".to_string());
chat.set_daybreak_enabled(/*enabled*/ true);
chat.local_settings.tui.terminal_title = Some(vec![
+14 -1
View File
@@ -54,6 +54,8 @@ use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::TurnToolOutput;
use codex_app_server_protocol::UserInput;
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::ThreadId;
use codex_protocol::models::FunctionCallOutputBody;
use serde::Deserialize;
@@ -290,6 +292,7 @@ pub(crate) async fn execute(
request_handle: AppServerRequestHandle,
params: DynamicToolCallParams,
thread_start_params: ThreadStartParams,
features: Features,
status_updates: broadcast::Receiver<ThreadStatusChangedNotification>,
app_event_tx: Option<&AppEventSender>,
) -> DynamicToolCallResponse {
@@ -297,6 +300,7 @@ pub(crate) async fn execute(
request_handle,
params,
thread_start_params,
features,
status_updates,
app_event_tx,
)
@@ -320,6 +324,7 @@ async fn execute_inner(
handle: AppServerRequestHandle,
params: DynamicToolCallParams,
mut thread_start_params: ThreadStartParams,
features: Features,
mut status_updates: broadcast::Receiver<ThreadStatusChangedNotification>,
app_event_tx: Option<&AppEventSender>,
) -> Result<Value, String> {
@@ -485,7 +490,9 @@ async fn execute_inner(
thread_start_params.cwd = Some(source_thread.cwd.to_string_lossy().into_owned());
thread_start_params.project_id = source_thread.project_id.clone();
thread_start_params.ephemeral = Some(source_thread.ephemeral);
if thread_start_params.daybreak_enabled.is_none() {
if features.enabled(Feature::CliDaybreak)
&& thread_start_params.daybreak_enabled.is_none()
{
let defaults = crate::config_update::read_effective_config(
handle.clone(),
source_thread.cwd.to_string_lossy().into_owned(),
@@ -554,6 +561,7 @@ async fn execute_inner(
let daybreak_enabled = started.thread.daybreak_enabled.unwrap_or(false);
register_background_thread(app_event_tx, started.thread, task_tools_available).await?;
let cyber_access_program = background_turn_program(
&features,
&handle,
&started.model,
&started.model_provider,
@@ -704,6 +712,7 @@ async fn execute_inner(
)
.await?;
let cyber_access_program = background_turn_program(
&features,
&handle,
arguments.model.as_deref().unwrap_or(&resumed.model),
&resumed.model_provider,
@@ -1247,11 +1256,15 @@ async fn start_turn(
}
async fn background_turn_program(
features: &Features,
handle: &AppServerRequestHandle,
model: &str,
provider: &str,
enabled: bool,
) -> Result<Option<codex_app_server_protocol::CyberAccessProgram>, String> {
if !features.enabled(Feature::CliDaybreak) {
return Ok(None);
}
let (account, models) = tokio::join!(
request::<GetAccountResponse>(handle, |request_id| ClientRequest::GetAccount {
request_id,
+5
View File
@@ -21,6 +21,7 @@ use codex_app_server_protocol::ThreadStatusChangedNotification;
use codex_config::McpServerConfig;
use codex_config::McpServerRequirement;
use codex_config::RawMcpServerConfig;
use codex_features::Features;
use rmcp::ErrorData as McpError;
use rmcp::handler::server::ServerHandler;
use rmcp::model::CallToolRequestParams;
@@ -139,6 +140,7 @@ impl DynamicToolMcpServer {
pub(crate) async fn start(
request_handle: AppServerRequestHandle,
mut thread_start_params: ThreadStartParams,
features: Features,
app_event_tx: AppEventSender,
status_updates: broadcast::Sender<ThreadStatusChangedNotification>,
managed_requirement: Option<&McpServerRequirement>,
@@ -180,6 +182,7 @@ impl DynamicToolMcpServer {
let handler = DynamicToolMcpHandler {
connection: Arc::clone(&connection),
thread_start_params,
features,
status_updates,
server_config: server_config.clone(),
services,
@@ -236,6 +239,7 @@ async fn require_authorization(
struct DynamicToolMcpHandler {
connection: ToolConnection,
thread_start_params: ThreadStartParams,
features: Features,
status_updates: broadcast::Sender<ThreadStatusChangedNotification>,
server_config: Value,
services: ToolServices,
@@ -370,6 +374,7 @@ impl ServerHandler for DynamicToolMcpHandler {
request_handle,
params,
thread_start_params,
self.features.clone(),
self.status_updates.subscribe(),
Some(&app_event_tx),
)
+1
View File
@@ -125,6 +125,7 @@ async fn call_tool(
ephemeral: Some(true),
..ThreadStartParams::default()
},
Features::with_defaults(),
status_receiver,
/*app_event_tx*/ None,
)