mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
Allow per-turn overrides for direct-only tool namespaces (#59481)
## What changed - Add `direct_only_tool_namespaces` to `TurnStartOptions`. Omission inherits the thread configuration; an empty list clears direct-only routing for that turn. Steering ignores the option. - Persist explicit overrides in turn context and restore them when recovering an interrupted turn, including turns started by standalone tool output. - Keep subsequent turns and spawned or resumed child agents on the thread's routing defaults. ## Testing Add coverage for routing changes across turns in Responses and Responses Lite, recovery without carrying overrides into subsequent turns, and child-agent inheritance. GitOrigin-RevId: cea1a997424572d4cd372cd15b098626904c6796
This commit is contained in:
@@ -1950,6 +1950,23 @@ impl Session {
|
||||
} = self
|
||||
.reconstruct_history_from_rollout(turn_context, rollout_items)
|
||||
.await;
|
||||
// An automatic turn can have a persisted routing override without establishing the
|
||||
// user-turn context baseline. Recover its own latest context by surviving turn ID.
|
||||
let restored_turn_routing = turn_attribution.as_ref().and_then(|turn| {
|
||||
rollout_items
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|item| match item {
|
||||
RolloutItem::TurnContext(context)
|
||||
if context.turn_id.as_deref() == Some(turn.turn_id.as_str()) =>
|
||||
{
|
||||
Some(context)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.and_then(|context| context.direct_only_tool_namespaces.clone())
|
||||
.map(|namespaces| (turn.turn_id.clone(), namespaces))
|
||||
});
|
||||
// Keep the recorded rollout unchanged. Prepare its reconstructed history before
|
||||
// installing it, so legacy media is processed once for this resume or fork and
|
||||
// will be processed again if the rollout is reconstructed in a future session.
|
||||
@@ -2015,6 +2032,7 @@ impl Session {
|
||||
);
|
||||
state.last_started_turn_id = last_started_turn_id;
|
||||
state.turn_attribution = turn_attribution;
|
||||
state.restored_turn_routing = restored_turn_routing;
|
||||
if let Some(world_state) = world_state_baseline {
|
||||
state.history.set_world_state_baseline(world_state);
|
||||
}
|
||||
|
||||
@@ -466,6 +466,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: None,
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
@@ -512,6 +513,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
||||
let mut previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: None,
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
@@ -2167,6 +2169,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: Some("root-turn".to_string()),
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
@@ -2265,6 +2268,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
serde_json::to_value(Some(TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: Some("root-turn".to_string()),
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
@@ -2295,6 +2299,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: None,
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
@@ -2438,6 +2443,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
let current_context_item = TurnContextItem {
|
||||
turn_id: Some(current_turn_id.clone()),
|
||||
root_turn_id: None,
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
@@ -2573,6 +2579,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: None,
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
@@ -2758,6 +2765,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: None,
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
|
||||
@@ -4439,6 +4439,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
root_turn_id: None,
|
||||
direct_only_tool_namespaces: None,
|
||||
disabled_plugin_ids: None,
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
|
||||
@@ -335,6 +335,7 @@ impl std::fmt::Debug for TurnEnvironment {
|
||||
/// Request options for one turn, separate from persistent thread settings.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct NewTurnContextOptions {
|
||||
pub(crate) direct_only_tool_namespaces: Option<Vec<String>>,
|
||||
pub(crate) final_output_json_schema: Option<Value>,
|
||||
pub(crate) prompt_cache_ttl: Option<crate::PromptCacheTtl>,
|
||||
pub(crate) cyber_access_program: Option<CyberAccessProgram>,
|
||||
@@ -351,6 +352,8 @@ pub struct TurnContext {
|
||||
/// Turn-scoped configuration. Read step-specific settings such as service tier and
|
||||
/// approvals reviewer from the corresponding `StepContext` instead.
|
||||
pub config: Arc<Config>,
|
||||
/// Thread defaults displaced by an explicit routing override for this turn.
|
||||
pub(crate) overridden_direct_only_tool_namespaces: Option<Vec<String>>,
|
||||
/// Preferences captured before token-budget defaults from the turn's initial model.
|
||||
pub(crate) configured_token_budget: Option<TokenBudgetConfig>,
|
||||
/// Captured once so later steps do not re-read config layers to detect user preferences.
|
||||
@@ -451,6 +454,7 @@ impl TurnContext {
|
||||
trace_id: current_span_trace_id(),
|
||||
realtime_active: self.realtime_active,
|
||||
code_mode_available: self.code_mode_available,
|
||||
overridden_direct_only_tool_namespaces: None,
|
||||
configured_token_budget: config.token_budget.clone(),
|
||||
use_model_token_budget_defaults: config.features.enabled(Feature::TokenBudget)
|
||||
&& !has_explicit_settings(&config),
|
||||
@@ -894,6 +898,9 @@ impl TurnContext {
|
||||
realtime_active: self.realtime_active,
|
||||
code_mode_available: self.code_mode_available,
|
||||
config: Arc::new(config),
|
||||
overridden_direct_only_tool_namespaces: self
|
||||
.overridden_direct_only_tool_namespaces
|
||||
.clone(),
|
||||
configured_token_budget: self.configured_token_budget.clone(),
|
||||
use_model_token_budget_defaults: self.use_model_token_budget_defaults,
|
||||
auth_manager: self.auth_manager.clone(),
|
||||
@@ -962,6 +969,10 @@ impl TurnContext {
|
||||
TurnContextItem {
|
||||
turn_id: Some(self.sub_id.clone()),
|
||||
root_turn_id: self.turn_metadata_state.root_turn_id(),
|
||||
direct_only_tool_namespaces: self
|
||||
.overridden_direct_only_tool_namespaces
|
||||
.as_ref()
|
||||
.map(|_| self.config.code_mode.direct_only_tool_namespaces.clone()),
|
||||
disabled_plugin_ids: Some(self.disabled_plugin_ids.clone()),
|
||||
cwd,
|
||||
approval_policy: self.approval_policy(),
|
||||
@@ -1180,6 +1191,7 @@ impl Session {
|
||||
realtime_active: false,
|
||||
code_mode_available: true,
|
||||
config: per_turn_config,
|
||||
overridden_direct_only_tool_namespaces: None,
|
||||
configured_token_budget,
|
||||
use_model_token_budget_defaults,
|
||||
auth_manager,
|
||||
@@ -1345,8 +1357,15 @@ impl Session {
|
||||
git_enrichment_policy: GitEnrichmentPolicy,
|
||||
) -> Arc<TurnContext> {
|
||||
let primary_turn_environment = turn_environments.primary();
|
||||
let per_turn_config =
|
||||
let mut per_turn_config =
|
||||
self.build_per_turn_config(&session_configuration, turn_environments.local_selection());
|
||||
let overridden_direct_only_tool_namespaces =
|
||||
options.direct_only_tool_namespaces.map(|namespaces| {
|
||||
std::mem::replace(
|
||||
&mut per_turn_config.code_mode.direct_only_tool_namespaces,
|
||||
namespaces,
|
||||
)
|
||||
});
|
||||
// Legacy turn consumers still refer to the primary executor's repository.
|
||||
let cwd = primary_turn_environment
|
||||
.and_then(|environment| environment.cwd().to_abs_path().ok())
|
||||
@@ -1475,6 +1494,8 @@ impl Session {
|
||||
if let Some(discovery) = skill_discovery {
|
||||
turn_context.extension_data.insert(discovery);
|
||||
}
|
||||
turn_context.overridden_direct_only_tool_namespaces =
|
||||
overridden_direct_only_tool_namespaces;
|
||||
turn_context.code_mode_available = self.services.code_mode_service.is_available();
|
||||
turn_context.extension_data.insert(trusted_plugin_roots);
|
||||
turn_context.active_host_plugin_identities = Some(
|
||||
|
||||
@@ -151,6 +151,7 @@ impl PreparedTurnInputSettings {
|
||||
let TurnStartOptions {
|
||||
turn_trigger,
|
||||
final_output_json_schema,
|
||||
direct_only_tool_namespaces,
|
||||
service_tier,
|
||||
prompt_cache_ttl,
|
||||
parent_turn_id,
|
||||
@@ -168,6 +169,7 @@ impl PreparedTurnInputSettings {
|
||||
updates.service_tier_for_turn = service_tier;
|
||||
|
||||
let options = NewTurnContextOptions {
|
||||
direct_only_tool_namespaces,
|
||||
final_output_json_schema,
|
||||
prompt_cache_ttl,
|
||||
cyber_access_program,
|
||||
|
||||
Reference in New Issue
Block a user