mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Interrupt cyber model turns after one Guardian denial (#37190)
## What changed - Add a Guardian circuit-breaker policy for models whose catalog specialty is `cyber`. - Interrupt those models after the first Guardian denial while retaining the existing denial thresholds for other models. - Centralize the `cyber` model-specialty identifier for use across core, protocol, app-server, and TUI code. ## Testing - Cover the cyber-specific threshold in the circuit-breaker unit tests. - Verify end to end that a denied command is not executed and the turn is interrupted after one denial. GitOrigin-RevId: fad24c9e7aa280432580db1f0e39d13535779710
This commit is contained in:
@@ -16,6 +16,7 @@ use codex_app_server_protocol::ModelUpgradeInfo;
|
||||
use codex_app_server_protocol::ReasoningEffortOption;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
@@ -157,7 +158,7 @@ async fn list_models_uses_chatgpt_remote_catalog_as_source_of_truth() -> Result<
|
||||
"slug": "chatgpt-remote-only",
|
||||
"display_name": "ChatGPT Remote Only",
|
||||
"description": "Remote-only model for app-server model/list coverage",
|
||||
"model_specialty": "cyber",
|
||||
"model_specialty": MODEL_SPECIALTY_CYBER,
|
||||
"default_reasoning_level": "max",
|
||||
"supported_reasoning_levels": [
|
||||
{"effort": "max", "description": "Maximum"},
|
||||
|
||||
@@ -47,7 +47,9 @@ pub(crate) use review_session::prompt_cache_key_override_for_review_session;
|
||||
|
||||
pub(crate) const GUARDIAN_REVIEW_TIMEOUT: Duration = Duration::from_secs(90);
|
||||
pub(crate) const GUARDIAN_REVIEWER_NAME: &str = "guardian";
|
||||
pub(crate) const MAX_CONSECUTIVE_CYBER_GUARDIAN_DENIALS_PER_TURN: u32 = 1;
|
||||
pub(crate) const MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN: u32 = 3;
|
||||
pub(crate) const MAX_RECENT_CYBER_AUTO_REVIEW_DENIALS_PER_TURN: u32 = 1;
|
||||
pub(crate) const MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN: u32 = 10;
|
||||
pub(crate) const AUTO_REVIEW_DENIAL_WINDOW_SIZE: usize = 50;
|
||||
pub(crate) const AUTO_REVIEW_DENIED_ACTION_APPROVAL_DEVELOPER_PREFIX: &str =
|
||||
@@ -81,6 +83,12 @@ struct GuardianRejectionCircuitBreakerTurn {
|
||||
interrupt_triggered: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum GuardianRejectionCircuitBreakerPolicy {
|
||||
Standard,
|
||||
CyberModel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum GuardianRejectionCircuitBreakerAction {
|
||||
Continue,
|
||||
@@ -95,14 +103,28 @@ impl GuardianRejectionCircuitBreaker {
|
||||
self.turns.remove(turn_id);
|
||||
}
|
||||
|
||||
pub(crate) fn record_denial(&mut self, turn_id: &str) -> GuardianRejectionCircuitBreakerAction {
|
||||
pub(crate) fn record_denial(
|
||||
&mut self,
|
||||
turn_id: &str,
|
||||
policy: GuardianRejectionCircuitBreakerPolicy,
|
||||
) -> GuardianRejectionCircuitBreakerAction {
|
||||
let turn = self.turns.entry(turn_id.to_string()).or_default();
|
||||
turn.consecutive_denials = turn.consecutive_denials.saturating_add(1);
|
||||
Self::record_recent_review(turn, /*denied*/ true);
|
||||
let recent_denials = turn.recent_denials.iter().filter(|denied| **denied).count() as u32;
|
||||
let (max_consecutive_denials, max_recent_denials) = match policy {
|
||||
GuardianRejectionCircuitBreakerPolicy::Standard => (
|
||||
MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN,
|
||||
MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN,
|
||||
),
|
||||
GuardianRejectionCircuitBreakerPolicy::CyberModel => (
|
||||
MAX_CONSECUTIVE_CYBER_GUARDIAN_DENIALS_PER_TURN,
|
||||
MAX_RECENT_CYBER_AUTO_REVIEW_DENIALS_PER_TURN,
|
||||
),
|
||||
};
|
||||
if !turn.interrupt_triggered
|
||||
&& (turn.consecutive_denials >= MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN
|
||||
|| recent_denials >= MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN)
|
||||
&& (turn.consecutive_denials >= max_consecutive_denials
|
||||
|| recent_denials >= max_recent_denials)
|
||||
{
|
||||
turn.interrupt_triggered = true;
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
|
||||
@@ -7,6 +7,7 @@ use codex_analytics::GuardianReviewTrackContext;
|
||||
use codex_analytics::GuardianReviewedAction;
|
||||
use codex_core_plugins::PluginCommandAttribution;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::CodexErrorInfo;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -38,6 +39,7 @@ use super::GuardianApprovalRequest;
|
||||
use super::GuardianAssessment;
|
||||
use super::GuardianAssessmentOutcome;
|
||||
use super::GuardianRejectionCircuitBreakerAction;
|
||||
use super::GuardianRejectionCircuitBreakerPolicy;
|
||||
use super::approval_request::guardian_assessment_action;
|
||||
use super::approval_request::guardian_request_target_item_id;
|
||||
use super::approval_request::guardian_request_turn_id;
|
||||
@@ -235,12 +237,17 @@ async fn record_guardian_non_denial(session: &Arc<Session>, turn_id: &str) {
|
||||
}
|
||||
|
||||
async fn record_guardian_denial(session: &Arc<Session>, turn: &Arc<TurnContext>, turn_id: &str) {
|
||||
let policy = if turn.model_info.model_specialty.as_deref() == Some(MODEL_SPECIALTY_CYBER) {
|
||||
GuardianRejectionCircuitBreakerPolicy::CyberModel
|
||||
} else {
|
||||
GuardianRejectionCircuitBreakerPolicy::Standard
|
||||
};
|
||||
let action = session
|
||||
.services
|
||||
.guardian_rejection_circuit_breaker
|
||||
.lock()
|
||||
.await
|
||||
.record_denial(turn_id);
|
||||
.record_denial(turn_id, policy);
|
||||
let GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials,
|
||||
recent_denials,
|
||||
|
||||
@@ -139,22 +139,38 @@ impl codex_extension_api::ContextContributor for GuardianMemoryContextProbe {
|
||||
fn guardian_rejection_circuit_breaker_interrupts_after_three_consecutive_denials() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: 3,
|
||||
recent_denials: 3,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_rejection_circuit_breaker_interrupts_cyber_models_after_one_denial() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::CyberModel),
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: 1,
|
||||
recent_denials: 1,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::CyberModel),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
}
|
||||
@@ -163,20 +179,20 @@ fn guardian_rejection_circuit_breaker_interrupts_after_three_consecutive_denials
|
||||
fn guardian_rejection_circuit_breaker_resets_consecutive_denials_on_non_denial() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
circuit_breaker.record_non_denial("turn-1");
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: 3,
|
||||
recent_denials: 4,
|
||||
@@ -189,13 +205,14 @@ fn auto_review_rejection_circuit_breaker_interrupts_after_ten_recent_denials() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
for _ in 0..9 {
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker
|
||||
.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
circuit_breaker.record_non_denial("turn-1");
|
||||
}
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: 1,
|
||||
recent_denials: 10,
|
||||
@@ -208,7 +225,8 @@ fn auto_review_rejection_circuit_breaker_forgets_denials_outside_recent_review_w
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
for _ in 0..9 {
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker
|
||||
.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
circuit_breaker.record_non_denial("turn-1");
|
||||
@@ -217,7 +235,7 @@ fn auto_review_rejection_circuit_breaker_forgets_denials_outside_recent_review_w
|
||||
circuit_breaker.record_non_denial("turn-1");
|
||||
}
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial("turn-1"),
|
||||
circuit_breaker.record_denial("turn-1", GuardianRejectionCircuitBreakerPolicy::Standard),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use codex_features::Feature;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -23,6 +24,7 @@ use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::RolloutLine;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::fs_wait;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
@@ -748,6 +750,127 @@ async fn guardian_denial_rejects_tool_call_with_rationale() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn cyber_model_guardian_denial_interrupts_turn_immediately() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
skip_if_sandbox!(Ok(()));
|
||||
skip_if_wine_exec!(
|
||||
Ok(()),
|
||||
"Guardian approval actions require host-native paths"
|
||||
);
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let approval_policy = AskForApproval::OnRequest;
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let sandbox_policy_for_config = sandbox_policy.clone();
|
||||
|
||||
let mut builder = test_codex()
|
||||
.with_model_info_override("gpt-5.4", |model| {
|
||||
model.model_specialty = Some(MODEL_SPECIALTY_CYBER.to_string());
|
||||
})
|
||||
.with_config(move |config| {
|
||||
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config
|
||||
.set_legacy_sandbox_policy(sandbox_policy_for_config)
|
||||
.expect("set sandbox policy");
|
||||
});
|
||||
let test = builder.build_with_auto_env(&server).await?;
|
||||
|
||||
let output_file = test.cwd.path().join("cyber-guardian-denied.txt");
|
||||
let command = format!("printf should-not-run > {}", output_file.display());
|
||||
let tool_args = json!({
|
||||
"cmd": command,
|
||||
"yield_time_ms": 1_000_u64,
|
||||
"sandbox_permissions": SandboxPermissions::RequireEscalated,
|
||||
"justification": "Exercise immediate Guardian interruption for cyber models.",
|
||||
});
|
||||
let responses = mount_sse_sequence(
|
||||
&server,
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_response_created("resp-cyber-parent-tool-denied"),
|
||||
ev_function_call(
|
||||
"exec-cyber-call-denied",
|
||||
"exec_command",
|
||||
&serde_json::to_string(&tool_args)?,
|
||||
),
|
||||
ev_completed("resp-cyber-parent-tool-denied"),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_response_created("resp-cyber-guardian-denied"),
|
||||
ev_assistant_message(
|
||||
"msg-cyber-guardian-denied",
|
||||
&json!({
|
||||
"risk_level": "high",
|
||||
"user_authorization": "low",
|
||||
"outcome": "deny",
|
||||
"rationale": "The requested command has unacceptable test risk.",
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
ev_completed("resp-cyber-guardian-denied"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
items: vec![UserInput::Text {
|
||||
text: "run a command that Guardian should deny for a cyber model".into(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: None,
|
||||
additional_context: Default::default(),
|
||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
||||
approval_policy: Some(approval_policy),
|
||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
..Default::default()
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
let warning = wait_for_event(&test.codex, |event| {
|
||||
matches!(
|
||||
event,
|
||||
EventMsg::GuardianWarning(warning)
|
||||
if warning.message.contains("too many approval requests")
|
||||
)
|
||||
})
|
||||
.await;
|
||||
let EventMsg::GuardianWarning(warning) = warning else {
|
||||
unreachable!("wait_for_event returned a non-warning event")
|
||||
};
|
||||
assert!(
|
||||
warning
|
||||
.message
|
||||
.contains("1 consecutive, 1 in the last 50 reviews")
|
||||
);
|
||||
|
||||
let aborted = wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TurnAborted(_))
|
||||
})
|
||||
.await;
|
||||
let EventMsg::TurnAborted(aborted) = aborted else {
|
||||
unreachable!("wait_for_event returned a non-abort event")
|
||||
};
|
||||
assert_eq!(aborted.reason, TurnAbortReason::Interrupted);
|
||||
assert_eq!(responses.requests().len(), 2);
|
||||
assert!(
|
||||
!output_file.exists(),
|
||||
"Guardian-denied cyber-model command unexpectedly executed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn guardian_review_session_does_not_inherit_legacy_notify() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
@@ -32,6 +32,8 @@ use crate::config_types::Verbosity;
|
||||
use crate::protocol::MultiAgentVersion;
|
||||
|
||||
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
|
||||
/// Backend model-catalog specialty identifying cybersecurity-focused models.
|
||||
pub const MODEL_SPECIALTY_CYBER: &str = "cyber";
|
||||
pub const SPEED_TIER_FAST: &str = "fast";
|
||||
|
||||
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
|
||||
|
||||
@@ -106,6 +106,7 @@ use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::MAX_THREAD_GOAL_OBJECTIVE_CHARS;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
@@ -7248,7 +7249,7 @@ async fn selecting_cyber_model_defaults_active_thread_to_auto_review() {
|
||||
.into_iter()
|
||||
.find(|model| model.model == "gpt-5.4")
|
||||
.expect("gpt-5.4 model");
|
||||
model.model_specialty = Some("cyber".to_string());
|
||||
model.model_specialty = Some(MODEL_SPECIALTY_CYBER.to_string());
|
||||
app.model_catalog = Arc::new(ModelCatalog::new(vec![model]));
|
||||
|
||||
let mut app_server =
|
||||
@@ -7311,7 +7312,7 @@ async fn changing_cyber_model_reasoning_preserves_selected_permissions() {
|
||||
.into_iter()
|
||||
.find(|model| model.model == model_name)
|
||||
.expect("current model");
|
||||
model.model_specialty = Some("cyber".to_string());
|
||||
model.model_specialty = Some(MODEL_SPECIALTY_CYBER.to_string());
|
||||
app.model_catalog = Arc::new(ModelCatalog::new(vec![model]));
|
||||
|
||||
assert!(
|
||||
@@ -7414,7 +7415,7 @@ async fn selecting_cyber_model_falls_back_to_user_when_auto_review_is_unavailabl
|
||||
.into_iter()
|
||||
.find(|model| model.model == "gpt-5.4")
|
||||
.expect("gpt-5.4 model");
|
||||
model.model_specialty = Some("cyber".to_string());
|
||||
model.model_specialty = Some(MODEL_SPECIALTY_CYBER.to_string());
|
||||
app.model_catalog = Arc::new(ModelCatalog::new(vec![model]));
|
||||
let _ = app.config.features.disable(Feature::GuardianApproval);
|
||||
app.chat_widget
|
||||
@@ -7469,7 +7470,7 @@ async fn selecting_cyber_model_respects_auto_review_requirements() {
|
||||
.into_iter()
|
||||
.find(|model| model.model == "gpt-5.4")
|
||||
.expect("gpt-5.4 model");
|
||||
model.model_specialty = Some("cyber".to_string());
|
||||
model.model_specialty = Some(MODEL_SPECIALTY_CYBER.to_string());
|
||||
app.model_catalog = Arc::new(ModelCatalog::new(vec![model]));
|
||||
|
||||
let mut app_server =
|
||||
|
||||
@@ -15,6 +15,7 @@ use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
||||
|
||||
impl App {
|
||||
pub(super) async fn sync_active_thread_model_setting(
|
||||
@@ -50,7 +51,8 @@ impl App {
|
||||
let thread_id = self.active_thread_id?;
|
||||
let is_cyber_model = self.model_catalog.try_list_models().is_ok_and(|models| {
|
||||
models.iter().any(|preset| {
|
||||
preset.model == model && preset.model_specialty.as_deref() == Some("cyber")
|
||||
preset.model == model
|
||||
&& preset.model_specialty.as_deref() == Some(MODEL_SPECIALTY_CYBER)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! `windows_sandbox_prompts`.
|
||||
|
||||
use super::*;
|
||||
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
||||
|
||||
impl ChatWidget {
|
||||
/// Open the permissions popup.
|
||||
@@ -410,7 +411,7 @@ impl ChatWidget {
|
||||
let is_cyber_model = self.model_catalog.try_list_models().is_ok_and(|models| {
|
||||
models.iter().any(|model| {
|
||||
model.model == self.current_model()
|
||||
&& model.model_specialty.as_deref() == Some("cyber")
|
||||
&& model.model_specialty.as_deref() == Some(MODEL_SPECIALTY_CYBER)
|
||||
})
|
||||
});
|
||||
let title_line = Line::from("Enable full access?").bold();
|
||||
|
||||
Reference in New Issue
Block a user